Spring Boot 3.1中如何处理AccessDenied权限拒绝问题
我参考过相关文章,但Spring Boot 3.1.4里多数方法已被弃用或不可用。我想在权限被拒绝时返回自定义错误响应,目前通过自定义ErrorController把所有错误重定向到/error:
@RestController public class ErrorControllerImpl implements ErrorController { @RequestMapping("/error") public ResponseEntity<GeneralResponse> handleError(final HttpServletRequest request, final HttpServletResponse response) { HttpStatusCode httpStatus = response.getStatus() == 0 ? HttpStatus.BAD_REQUEST : HttpStatusCode.valueOf(response.getStatus()); return new ResponseEntity<>(new GeneralResponse("Error from filter"), httpStatus); } @RequestMapping("/error/access-denied") public ResponseEntity<GeneralResponse> handleAccessDenied(final HttpServletRequest request, final HttpServletResponse response) { return new ResponseEntity<>(new GeneralResponse("Access denied hwo "), HttpStatus.GONE); } }
我希望所有AccessDenied场景都由handleAccessDenied()处理,但现在所有错误都走/error,返回的都是“Error from filter”。
我尝试自定义org.springframework.security.web.access.AccessDeniedHandler实现,并通过.exceptionHandling()配置,但没生效:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests( customizer -> customizer .requestMatchers("/error/**") .permitAll() .requestMatchers("/eod") .hasRole("ADMIN") ..... .anyRequest() .authenticated() ).exceptionHandling(x -> x.accessDeniedHandler(accessDeniedHandler)) .csrf(AbstractHttpConfigurer::disable) .cors(Customizer.withDefaults()) .sessionManagement(sessionManagement -> sessionManagement .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ).authenticationProvider(authenticationProvider) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) .logout(customizer -> customizer .logoutUrl("/api/v1/users/signout") .permitAll() .addLogoutHandler(logoutHandler) .logoutSuccessHandler(logoutSuccessHandler) .clearAuthentication(true) ); return http.build(); }
我希望在过滤阶段正确处理这类场景,但找不到适配Spring Boot新版本的有效方案,现寻求帮助。
更新:我的AccessDeniedHandler实现
@Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exc) throws IOException { response.sendRedirect("/error/access-denied"); } }
SecurityConfiguration.java 相关代码
@Configuration @EnableWebSecurity public class SecurityConfiguration { private final CustomAccessDeniedHandler accessDeniedHandler; @Autowired public SecurityConfiguration(CustomAccessDeniedHandler accessDeniedHandler) { this.accessDeniedHandler = accessDeniedHandler; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // 上述已分享部分代码 } }
问题出在你用了response.sendRedirect(),在无状态(STATELESS)的会话策略下,重定向会触发新的请求,而Spring Boot的默认错误处理机制会优先捕获这类请求,导致最终还是走到/error。
修复步骤:
- 修改
CustomAccessDeniedHandler,直接在响应中写入自定义内容,不要重定向:
@Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { private final ObjectMapper objectMapper; // 注入ObjectMapper用于序列化响应体 public CustomAccessDeniedHandler(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exc) throws IOException { // 设置响应状态码 response.setStatus(HttpStatus.FORBIDDEN.value()); // 设置响应内容类型 response.setContentType(MediaType.APPLICATION_JSON_VALUE); // 写入自定义响应体 GeneralResponse errorResponse = new GeneralResponse("Access denied"); objectMapper.writeValue(response.getWriter(), errorResponse); } }
- 移除
ErrorController中多余的/error/access-denied映射(现在不需要通过重定向处理了):
@RestController public class ErrorControllerImpl implements ErrorController { @RequestMapping("/error") public ResponseEntity<GeneralResponse> handleError(final HttpServletRequest request, final HttpServletResponse response) { HttpStatusCode httpStatus = response.getStatus() == 0 ? HttpStatus.BAD_REQUEST : HttpStatusCode.valueOf(response.getStatus()); return new ResponseEntity<>(new GeneralResponse("Error from filter"), httpStatus); } }
- 确保
SecurityFilterChain配置正确:- 确认
/error/**已经被允许访问(你当前的配置已经包含这一点) - 确认
CustomAccessDeniedHandler被正确注入到SecurityConfiguration中(你的构造注入写法是对的,注意不要出现重复注入的问题)
- 确认
原因说明
在无状态会话模式下,sendRedirect会生成一个302响应,客户端会发起新的请求到/error/access-denied,但此时Spring Security的过滤器链仍然会处理这个新请求,同时Spring Boot的ErrorMvcAutoConfiguration会优先捕获错误请求,导致你的自定义映射被覆盖。直接在AccessDeniedHandler中写入响应可以避免这个问题,因为它直接在当前请求的上下文里返回了错误内容,不会触发额外的重定向请求。
内容的提问来源于stack exchange,提问作者mig001

