You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 06:16:08