Spring Boot 2.7.9中authorizeHttpRequests下自定义AuthenticationProvider未触发
问题1:改用authorizeHttpRequests()后自定义AuthenticationProvider不被调用的原因
Spring Security从authorizeRequests()切换到authorizeHttpRequests()时,授权流程的触发逻辑发生了变化。你的实现中,自定义Filter将未认证的UsernamePasswordAuthenticationToken放入SecurityContext,在旧版authorizeRequests()的逻辑下,授权检查阶段会发现该Authentication未完成认证,从而主动调用AuthenticationManager触发AuthenticationProvider的认证流程。但在新版authorizeHttpRequests()对应的Servlet 3.1+配置模型中,默认不会对SecurityContext中已存在的Authentication(哪怕未认证)触发二次认证,导致你的JwtAuthenticationProvider不会被调用。
另外,你的实现逻辑本身存在不规范的地方:Filter不应该直接将未认证的Authentication放入SecurityContext,正确的做法是通过AuthenticationManager完成认证后再设置已认证的对象。
修复方案:
修改JwtAuthenticationFilter,让AuthenticationManager处理认证
去掉直接设置SecurityContext的代码,改为调用AuthenticationManager.authenticate()方法,将返回的已认证对象放入SecurityContext:public class JwtAuthenticationFilter extends OncePerRequestFilter { private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class); @Autowired private JwtProcessor jwtProcessor; @Autowired private AuthenticationManager authenticationManager; // 注入AuthenticationManager @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { logger.info("filter is working on.."); String authHeader = request.getHeader("Authorization"); if(authHeader != null) { logger.info("found an authorization header in my filter.."); String jwt = authHeader.substring("bearer: ".length()); Map<String, Object> claims = jwtProcessor.validateJwt(jwt); UsernamePasswordAuthenticationToken unauthenticatedToken = createToken(jwt, claims); // 调用AuthenticationManager触发认证 Authentication authenticatedToken = authenticationManager.authenticate(unauthenticatedToken); // 设置已认证的对象到SecurityContext SecurityContextHolder.getContext().setAuthentication(authenticatedToken); } filterChain.doFilter(request, response); } private UsernamePasswordAuthenticationToken createToken(String jwt, Map<String, Object> claims) { UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(jwt, claims); token.setAuthenticated(false); return token; } }在SecurityConfig中暴露AuthenticationManager Bean
Spring Security 5.7+使用组件式配置,需要显式暴露AuthenticationManager:@EnableWebSecurity @Configuration public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/user/signup", "/user/login").permitAll() .anyRequest().authenticated()) .authenticationProvider(jwtAuthenticationProvider()) .csrf().disable() .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } // 暴露AuthenticationManager @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } @Bean public OncePerRequestFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } @Bean public AuthenticationProvider jwtAuthenticationProvider() { return new JwtAuthenticationProvider(); } }
这样修改后,无论使用authorizeRequests()还是authorizeHttpRequests(),AuthenticationManager都会主动调用你的JwtAuthenticationProvider完成认证流程。
问题2:官方JWT示例中如何处理JWT的scope变量
在你提供的Spring Security官方JWT配置中,使用了oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt),可以通过自定义JwtAuthenticationConverter来提取并处理JWT中的scope变量,实现额外逻辑。
实现方式:
自定义JwtAuthenticationConverter,处理scope
@Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); // 自定义权限转换逻辑,同时处理scope converter.setJwtGrantedAuthoritiesConverter(jwt -> { // 提取scope List<String> scopes = jwt.getClaimAsStringList("scope"); if (scopes == null) { scopes = Collections.emptyList(); } // 这里可以添加对scope的额外处理,比如校验特定scope是否存在、记录日志等 scopes.forEach(scope -> { // 示例:如果包含admin scope,做额外处理 if ("admin".equals(scope)) { // 你的业务逻辑,比如记录权限日志、触发特定操作等 } }); // 将scope转换为GrantedAuthority(默认前缀是SCOPE_) return scopes.stream() .map(scope -> new SimpleGrantedAuthority("SCOPE_" + scope)) .collect(Collectors.toList()); }); return converter; }在SecurityConfig中配置该Converter
修改oauth2ResourceServer的配置,指定自定义的JwtAuthenticationConverter:@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/sign-up", "/login").permitAll() .anyRequest().authenticated() ) .cors().configurationSource(corsConfiguration()).and() .csrf().disable() .httpBasic(Customizer.withDefaults()) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())) // 配置自定义Converter ) .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .exceptionHandling((exceptions) -> exceptions .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) .accessDeniedHandler(new BearerTokenAccessDeniedHandler()) ) .logout() .logoutUrl("/logout") .clearAuthentication(true) .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()); return http.build(); }
通过这种方式,你可以在JWT转换为Authentication的过程中,直接提取scope变量进行任意业务处理,同时不影响默认的权限转换逻辑。
内容的提问来源于stack exchange,提问作者aaabbbccc

