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

Spring Security ExceptionTranslationFilter未处理自定义过滤器抛出的异常

问题:JWT无效时AuthenticationEntryPoint未触发

配置Spring Security后,当JWT无效时parseAccessToken()会抛出继承自AuthenticationException的InvalidTokenException,但配置的JwtAuthenticationEntryPoint的commence方法并未执行,无相关日志输出。


相关代码

SecurityConfig配置

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtAuthenticationFilter jwtAuthFilter;
private final JwtAuthenticationEntryPoint jwtFilterAuthenticationEntryPoint;
private final AuthzAccessDeniedHandler authzAccessDeniedHandler;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
            .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers(USER_PATH + USER_ID_PATH_VAR).authenticated()
                    .requestMatchers(HttpMethod.POST, EVENT_PATH).hasRole("ORGANIZER")
                    .requestMatchers(HttpMethod.GET, TICKET_PATH).authenticated()
                    .requestMatchers(AUTH_PATH + "/logout/{userUuid}").authenticated()
                    .anyRequest().permitAll()
            )
            .sessionManagement(session -> session
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
            .cors(Customizer.withDefaults())
            .exceptionHandling(handler -> handler
                    .authenticationEntryPoint(jwtFilterAuthenticationEntryPoint)
                    .accessDeniedHandler(authzAccessDeniedHandler)
            )
            .csrf(AbstractHttpConfigurer::disable)
            .httpBasic(AbstractHttpConfigurer::disable)
            .formLogin(AbstractHttpConfigurer::disable)
            .logout(AbstractHttpConfigurer::disable)
    ;

    return http.build();
}

//other omited code...

JwtAuthenticationFilter代码

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final String AUTHZ_HEADER_PREFIX = "Bearer ";

private final JwtService jwtService;

@Override
protected void doFilterInternal(
        @NonNull HttpServletRequest request,
        @NonNull HttpServletResponse response,
        @NonNull FilterChain filterChain
) throws ServletException, IOException {
    String authzHeader = request.getHeader("Authorization");

    if (authzHeader == null || !authzHeader.startsWith(AUTHZ_HEADER_PREFIX)) {
        filterChain.doFilter(request, response);
        return;
    }

    AccessToken accessToken = jwtService.parseAccessToken(
            authzHeader.substring(AUTHZ_HEADER_PREFIX.length())
    );
    authenticateIfCurrentlyNot(accessToken, request);

    filterChain.doFilter(request, response);
}

private void authenticateIfCurrentlyNot(AccessToken accessToken, HttpServletRequest request) {
    if (SecurityContextHolder.getContext().getAuthentication() == null) {
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
                accessToken.claims().sub(),
                null,
                List.of(convertRoleToSimpleGrantedAuthority(
                        accessToken.claims().role()
                ))
        );
        token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

        SecurityContextHolder.getContext().setAuthentication(token);
    }
}

private SimpleGrantedAuthority convertRoleToSimpleGrantedAuthority(UserRole role) {
    return new SimpleGrantedAuthority("ROLE_" + role.name());
}
}

自定义异常与AuthenticationEntryPoint代码

public class InvalidTokenException extends AuthenticationException {

public InvalidTokenException(String message) {
    super(message);
}

public static String errorCode() {
    return "invalid.token";
}
}

@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;

@Override
public void commence(
        HttpServletRequest request,
        HttpServletResponse response,
        AuthenticationException authException
) {
    log.error(authException.getMessage(), authException);
    resolver.resolveException(request, response, null, authException);
}
}

问题原因与修复方案

原因

ExceptionTranslationFilter仅会在后续过滤器或请求处理流程中抛出AuthenticationException时介入处理。而当前JwtAuthenticationFilter中直接抛出的InvalidTokenException会被Servlet容器的异常处理机制捕获,而非Spring Security的ExceptionTranslationFilter,因此AuthenticationEntryPoint不会被触发。

修复方案

方案一:在过滤器中捕获异常并手动触发AuthenticationEntryPoint

修改JwtAuthenticationFilter,注入JwtAuthenticationEntryPoint,捕获InvalidTokenException后直接调用其commence方法:

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final String AUTHZ_HEADER_PREFIX = "Bearer ";

private final JwtService jwtService;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; // 新增注入

@Override
protected void doFilterInternal(
        @NonNull HttpServletRequest request,
        @NonNull HttpServletResponse response,
        @NonNull FilterChain filterChain
) throws ServletException, IOException {
    String authzHeader = request.getHeader("Authorization");

    if (authzHeader == null || !authzHeader.startsWith(AUTHZ_HEADER_PREFIX)) {
        filterChain.doFilter(request, response);
        return;
    }

    try {
        AccessToken accessToken = jwtService.parseAccessToken(
                authzHeader.substring(AUTHZ_HEADER_PREFIX.length())
        );
        authenticateIfCurrentlyNot(accessToken, request);
        filterChain.doFilter(request, response);
    } catch (InvalidTokenException e) {
        jwtAuthenticationEntryPoint.commence(request, response, e);
    }
}

// 其余代码不变...
}

方案二:将异常绑定到请求,交由ExceptionTranslationFilter处理

在过滤器中捕获异常后,将其绑定到请求属性,让后续的ExceptionTranslationFilter识别并处理:

@Override
protected void doFilterInternal(
        @NonNull HttpServletRequest request,
        @NonNull HttpServletResponse response,
        @NonNull FilterChain filterChain
) throws ServletException, IOException {
    String authzHeader = request.getHeader("Authorization");

    if (authzHeader == null || !authzHeader.startsWith(AUTHZ_HEADER_PREFIX)) {
        filterChain.doFilter(request, response);
        return;
    }

    try {
        AccessToken accessToken = jwtService.parseAccessToken(
                authzHeader.substring(AUTHZ_HEADER_PREFIX.length())
        );
        authenticateIfCurrentlyNot(accessToken, request);
    } catch (InvalidTokenException e) {
        request.setAttribute("javax.servlet.error.exception", e);
    }

    filterChain.doFilter(request, response);
}

额外检查

确认SecurityFilterChain中过滤器顺序正确:JwtAuthenticationFilter通过addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)添加,而ExceptionTranslationFilter默认在UsernamePasswordAuthenticationFilter之前,因此顺序无需调整。


内容的提问来源于stack exchange,提问作者momopoi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 21:18:11