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

Spring Boot中设置响应状态触发AccessDeniedException的解决咨询

Spring Boot自定义认证过滤器响应已提交导致的AccessDenied异常处理问题

问题场景

我在Spring Boot项目中实现了基于Cookie令牌的自定义认证过滤器CookieAuthenticationFilter,但在过滤器中设置响应状态码和自定义错误消息后,访问需要认证的接口会触发如下错误:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Unable to handle the Spring Security Exception because the response is already committed.] with root cause

org.springframework.security.access.AccessDeniedException: Access Denied

调试后发现,无论是否设置响应内容,都会抛出AccessDeniedException,但设置响应后由于response已提交,ExceptionTranslationFilter无法处理该异常,进而抛出上述Servlet异常。

相关代码

安全配置类SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final CookieAuthenticationFilter cookieAuthenticationFilter;

    public SecurityConfig(CookieAuthenticationFilter customFilter) {
        this.cookieAuthenticationFilter = customFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .cors(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(
                        s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .authorizeHttpRequests(
                        a -> a
                                .requestMatchers("/un/**").permitAll()
                                .anyRequest().authenticated()
                )
                .addFilterBefore(cookieAuthenticationFilter, BasicAuthenticationFilter.class);
        return http.build();
    }
}

自定义认证过滤器CookieAuthenticationFilter

@Component
public class CookieAuthenticationFilter extends OncePerRequestFilter {
    private final AuthService authService;
    private final ObjectMapper objectMapper;

    public CookieAuthenticationFilter(
            AuthService authService,
            ObjectMapper objectMapper) {
        this.authService = authService;
        this.objectMapper = objectMapper;
    }

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        // 提取认证Cookie的代码(省略)

        // 通过令牌获取用户信息
        UserDto user = null;
        try {
            user = authService.getUserFromAuthenticationToken(
                    new AuthenticationTokenValueDto(authCookie.getValue())
            );
        } catch (CustomAuthException e) {
            // 写入自定义错误响应
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            objectMapper.writeValue(response.getOutputStream(), CustomAuthException.MESSAGE);

            // 原代码调用了filterChain,导致后续过滤器处理异常
            filterChain.doFilter(request, response); return;
        }

        // 将认证信息存入上下文
        Authentication authentication = new PreAuthenticatedAuthenticationToken(
                user,
                authCookie.getValue(),
                List.of()
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);

        // 执行后续过滤器
        filterChain.doFilter(request, response);
    }
}

解决方案

方案1:停止后续过滤器执行(快速修复)

当你已经向response写入自定义错误内容后,不要继续调用filterChain.doFilter(request, response),直接return终止过滤器链即可。因为此时response已提交,后续过滤器(如ExceptionTranslationFilter)无法再修改响应,继续执行只会引发冲突。

修改过滤器的catch块代码:

catch (CustomAuthException e) {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    objectMapper.writeValue(response.getOutputStream(), CustomAuthException.MESSAGE);
    
    // 直接返回,不再执行后续过滤器
    return;
}

方案2:统一异常处理(更规范的实践)

将认证异常的响应处理委托给Spring Security的异常处理机制,让过滤器只专注于认证逻辑,职责更清晰:

  1. 让CustomAuthException继承Spring Security的AuthenticationException(确保被Security的异常处理器识别):
public class CustomAuthException extends AuthenticationException {
    public static final String MESSAGE = "自定义认证错误信息";
    
    public CustomAuthException(String msg, Throwable t) {
        super(msg, t);
    }
}
  1. 在SecurityConfig中配置自定义的AuthenticationEntryPoint,处理未认证异常:
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final CookieAuthenticationFilter cookieAuthenticationFilter;
    private final ObjectMapper objectMapper;

    public SecurityConfig(CookieAuthenticationFilter customFilter, ObjectMapper objectMapper) {
        this.cookieAuthenticationFilter = customFilter;
        this.objectMapper = objectMapper;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .cors(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(a -> a
                        .requestMatchers("/un/**").permitAll()
                        .anyRequest().authenticated()
                )
                .addFilterBefore(cookieAuthenticationFilter, BasicAuthenticationFilter.class)
                // 配置自定义认证异常处理器
                .exceptionHandling(e -> e.authenticationEntryPoint((request, response, authException) -> {
                    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
                    objectMapper.writeValue(response.getOutputStream(), CustomAuthException.MESSAGE);
                }));
        return http.build();
    }
}
  1. 修改过滤器的catch块,直接抛出异常,交给Security处理:
catch (CustomAuthException e) {
    // 抛出AuthenticationException,由配置的authenticationEntryPoint处理响应
    throw e;
}

方案3:关闭ExceptionTranslationFilter(不推荐)

不建议直接关闭该过滤器,因为它负责处理Spring Security的核心异常(如认证失败、权限不足),关闭后会丢失默认的异常处理能力,除非你完全自定义了所有异常处理逻辑。

问题根源

当你在过滤器中调用response.setStatus()或写入响应体后,response的committed状态会被标记为true(响应已提交)。后续的ExceptionTranslationFilter尝试处理AccessDeniedException时,发现无法再修改响应,因此抛出"Unable to handle the Spring Security Exception because the response is already committed"的异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 01:07:02