Spring Security中如何处理Authentication Provider抛出的异常?
问题描述
我实现了一个自定义Authentication Provider,它会抛出自定义AuthenticationException,该Provider会在每次请求控制器前验证Token。控制器中的异常可通过Controller Advice处理,但由于Provider的执行时机早于控制器,Controller Advice无法捕获它抛出的异常,请问该如何处理这类异常?
自定义Authentication Provider代码
@Component @RequiredArgsConstructor public class BearerTokenAuthenticationProvider implements AuthenticationProvider { private final Wso2TokenVerificationClient client; @Override public Authentication authenticate( Authentication authentication ) { BearerTokenAuthenticationToken token = (BearerTokenAuthenticationToken) authentication; Map<String, String> requestBody = new HashMap<>(); requestBody.put( "token", token.getToken() ); Wso2TokenValidationResponse tokenValidationResponse = client.introspectToken( requestBody ); if( !Boolean.parseBoolean( tokenValidationResponse.getActive() ) ) { throw new AuthenticationException( "Token not valid", HttpStatus.UNAUTHORIZED ); } DecodedJWT jwt = JWT.decode(token.getToken()); UserDetails details = new UserDetails(); details.setId( Long.parseLong(jwt.getClaim( OidcUserClaims.USER_ID ).asString()) ); details.setEmail( jwt.getClaim( OidcUserClaims.EMAIL ).asString() ); token.setDetails( details ); return token; } @Override public boolean supports( Class<?> aClass ) { return BearerTokenAuthenticationToken.class.equals( aClass ); } }
Security Config代码
@Configuration @RequiredArgsConstructor public class CommonWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { private final BearerTokenAuthenticationProvider bearerTokenProvider; @Override protected void configure(HttpSecurity http) throws Exception { http.headers().contentSecurityPolicy("script-src 'self'"); http .csrf().disable() .authorizeRequests(auth -> auth .antMatchers("/public/**").not().hasAuthority("ROLE_ANONYMOUS") ) .and() .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); } @Override protected void configure( AuthenticationManagerBuilder auth ) throws Exception { auth.authenticationProvider( bearerTokenProvider ); } }
解决方案
自定义Authentication Provider的异常发生在Spring Security过滤器链阶段,早于Controller执行,因此Controller Advice无法捕获。可以通过以下两种标准方式处理:
1. 自定义AuthenticationEntryPoint(推荐)
这是Spring Security处理认证失败的标准扩展点,专门用于处理未认证或认证失败的场景:
@Component public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); // 构造符合业务格式的响应体 Map<String, Object> body = new HashMap<>(); body.put("code", HttpStatus.UNAUTHORIZED.value()); body.put("message", authException.getMessage()); ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(response.getOutputStream(), body); } }
在Security Config中配置该EntryPoint:
// 注入自定义EntryPoint private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; @Override protected void configure(HttpSecurity http) throws Exception { http.headers().contentSecurityPolicy("script-src 'self'"); http .csrf().disable() .authorizeRequests(auth -> auth .antMatchers("/public/**").not().hasAuthority("ROLE_ANONYMOUS") ) .exceptionHandling() .authenticationEntryPoint(customAuthenticationEntryPoint) // 绑定自定义处理逻辑 .and() .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); }
2. 自定义Filter捕获异常
在过滤器链中添加一个前置Filter,捕获认证阶段抛出的异常:
@Component public class AuthenticationExceptionFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { filterChain.doFilter(request, response); } catch (AuthenticationException ex) { // 统一处理认证异常 response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); Map<String, Object> body = new HashMap<>(); body.put("code", HttpStatus.UNAUTHORIZED.value()); body.put("message", ex.getMessage()); ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(response.getOutputStream(), body); } } }
在Security Config中注册该Filter:
// 注入自定义Filter private final AuthenticationExceptionFilter authenticationExceptionFilter; @Override protected void configure(HttpSecurity http) throws Exception { http.headers().contentSecurityPolicy("script-src 'self'"); http .csrf().disable() .addFilterBefore(authenticationExceptionFilter, UsernamePasswordAuthenticationFilter.class) // 放在认证过滤器之前 .authorizeRequests(auth -> auth .antMatchers("/public/**").not().hasAuthority("ROLE_ANONYMOUS") ) .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); }
注意事项
- 优先使用
AuthenticationEntryPoint,它完全贴合Spring Security的认证流程设计,逻辑更清晰。 - 自定义响应时要和业务系统的统一错误格式保持一致,避免前端多格式适配。
内容的提问来源于stack exchange,提问作者Montana
相关产品推荐
相关产品推荐

