如何避免AccessDeniedHandler执行后触发AuthenticationEntryPoint?
问题解决:Spring Security 6.2.3 中 JWT 认证与权限异常的正确状态码返回
核心问题分析
你的问题根源在于Spring Security 异常处理逻辑的执行顺序冲突,以及过滤器中异常抛出/处理方式不正确,导致:
- 权限不足(
AccessDeniedException)时,accessDeniedHandler执行后,authenticationEntryPoint被二次触发,最终返回401而非403 - 无效JWT(
AuthenticationException)时,authenticationEntryPoint被重复调用
以下是针对性的解决方案:
1. 修正 JwtRequestFilter 异常抛出逻辑
确保过滤器仅在JWT无效/验证失败时抛出AuthenticationException,且不手动处理响应,完全交给Spring Security的异常处理机制:
@Component public class JwtRequestFilter extends OncePerRequestFilter { private final JwtTokenUtil jwtTokenUtil; private final UserDetailsService userDetailsService; public JwtRequestFilter(JwtTokenUtil jwtTokenUtil, UserDetailsService userDetailsService) { this.jwtTokenUtil = jwtTokenUtil; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwt = null; if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwt = authorizationHeader.substring(7); try { username = jwtTokenUtil.extractUsername(jwt); } catch (JwtException e) { // 无效JWT直接抛出AuthenticationException,不手动处理响应 throw new BadCredentialsException("Invalid JWT token", e); } } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (!jwtTokenUtil.validateToken(jwt, userDetails)) { throw new BadCredentialsException("Invalid JWT token"); } // 验证通过,设置认证信息到上下文 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } chain.doFilter(request, response); } }
2. 正确配置 SecurityFilterChain 异常处理
在Spring Security 6.x中,通过exceptionHandling()明确划分authenticationEntryPoint(处理未认证/无效JWT)和accessDeniedHandler(处理权限不足)的职责,确保配置顺序正确,且无重复处理逻辑:
@Configuration @EnableWebSecurity @EnableMethodSecurity(jsr250Enabled = true) // 启用@RolesAllowed注解支持 public class WebSecurityConfig { private final JwtRequestFilter jwtRequestFilter; public WebSecurityConfig(JwtRequestFilter jwtRequestFilter) { this.jwtRequestFilter = jwtRequestFilter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .exceptionHandling(exceptions -> exceptions // 处理未认证、无效JWT场景,返回401 .authenticationEntryPoint(unauthorizedEntryPoint()) // 处理权限不足(@RolesAllowed校验失败)场景,返回403 .accessDeniedHandler(accessDeniedHandler()) ) .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } // 自定义401响应处理器 @Bean public AuthenticationEntryPoint unauthorizedEntryPoint() { return (request, response, ex) -> { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json"); response.getWriter().write("{\"error\":\"Unauthorized\",\"message\":\"" + ex.getMessage() + "\"}"); }; } // 自定义403响应处理器 @Bean public AccessDeniedHandler accessDeniedHandler() { return (request, response, ex) -> { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentType("application/json"); response.getWriter().write("{\"error\":\"Forbidden\",\"message\":\"" + ex.getMessage() + "\"}"); }; } }
3. 移除冲突的异常处理逻辑
如果保留了@ControllerAdvice作为临时方案,需确保它不处理Spring Security相关异常,避免与Security内置异常处理器冲突:
@ControllerAdvice public class GlobalExceptionHandler { // 仅处理业务异常,排除AuthenticationException和AccessDeniedException @ExceptionHandler(BusinessException.class) public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) { return new ResponseEntity<>(new ErrorResponse(ex.getMessage()), HttpStatus.BAD_REQUEST); } }
关键排查点
- 检查
JwtRequestFilter中是否存在手动调用response.sendError()或写入响应的代码,这类操作会触发后续异常处理器重复执行 - 确认
@EnableMethodSecurity的jsr250Enabled属性为true,否则@RolesAllowed注解不会生效 - 避免同时配置多个异常处理机制(如Security内置处理器+
@ControllerAdvice处理Security异常)
内容的提问来源于stack exchange,提问作者Alexei Sischin
相关产品推荐
相关产品推荐

