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

Spring Boot 3.1中如何处理AccessDenied权限拒绝问题

问题:Spring Boot 3.1.4 中自定义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。

修复步骤:

  1. 修改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);
    }
}
  1. 移除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);
    }
}
  1. 确保SecurityFilterChain配置正确:
    • 确认/error/**已经被允许访问(你当前的配置已经包含这一点)
    • 确认CustomAccessDeniedHandler被正确注入到SecurityConfiguration中(你的构造注入写法是对的,注意不要出现重复注入的问题)

原因说明

在无状态会话模式下,sendRedirect会生成一个302响应,客户端会发起新的请求到/error/access-denied,但此时Spring Security的过滤器链仍然会处理这个新请求,同时Spring Boot的ErrorMvcAutoConfiguration会优先捕获错误请求,导致你的自定义映射被覆盖。直接在AccessDeniedHandler中写入响应可以避免这个问题,因为它直接在当前请求的上下文里返回了错误内容,不会触发额外的重定向请求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 22:30:55