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

修改CSRF Cookie值后请求仍有效?Spring Security 6.x CSRF疑问

Spring Security 6.x 中修改Cookie内CSRF Token后请求仍生效的问题

问题背景

正在理解Spring Security 6.x的CSRF保护机制,现有一个Angular单页应用通过formLogin方式登录Spring Boot 3.0后端。后端已正确向前端Cookie设置UUID格式的CSRF Token,请求也会携带该Token,但修改前端Cookie中的CSRF Token值后,请求依然能正常通过,不确定这是否符合预期。

现有配置

SecurityConfiguration 核心代码

public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
        CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
             requestHandler.setCsrfRequestAttributeName("_csrf");
        return http.csrf((csrf) ->
                           csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                               .csrfTokenRequestHandler(requestHandler)
                               .ignoringRequestMatchers("/login", "/logout")
                   )
                   .cors(cors -> cors.configurationSource(corsConfigurationSourceWithCsfr()))
                   .authorizeHttpRequests(authRequest ->
                           authRequest
                                   .requestMatchers(HttpMethod.OPTIONS)
                                   .permitAll()
                                   .requestMatchers("/login", "/logout")
                                   .permitAll()
                                   .anyRequest()
                                   .authenticated()
                   )
                   .formLogin(formLogin -> formLogin
                           .successHandler((request, response, authentication) ->
                                   response.setStatus(HttpStatus.OK.value())
                           )
                           .failureHandler((request, response, exception) ->
                                   response.setStatus(HttpStatus.FORBIDDEN.value())
                           )
                           .loginProcessingUrl("/login")
                           .permitAll())
                   .logout(logout -> logout.logoutUrl("/logout")
                                           .clearAuthentication(true)
                                           .addLogoutHandler((request, response, authentication) -> {
                                               response.setStatus(HttpStatus.OK.value());
                                           })
                                           .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
                                           .deleteCookies("JSESSIONID"))
                   .sessionManagement(sessionManager ->
                           sessionManager
                                   .sessionCreationPolicy(SessionCreationPolicy.ALWAYS))
                   .authenticationProvider(authProvider)
                .addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class)
                   .build();
    }

CsrfTokenResponseHeaderBindingFilter 实现代码

public class CsrfTokenResponseHeaderBindingFilter extends OncePerRequestFilter {

    protected static final String REQUEST_ATTRIBUTE_NAME = "_csrf";
    protected static final String RESPONSE_HEADER_NAME = "X-CSRF-HEADER";
    protected static final String RESPONSE_PARAM_NAME = "X-CSRF-PARAM";
    protected static final String RESPONSE_TOKEN_NAME = "X-CSRF-TOKEN";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        CsrfToken token = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME);

        if (token != null) {
            response.setHeader(RESPONSE_HEADER_NAME, token.getHeaderName());
            response.setHeader(RESPONSE_PARAM_NAME, token.getParameterName());
            response.setHeader(RESPONSE_TOKEN_NAME, token.getToken());
            log.info("for request: {}", request.getRequestURI());
            log.info("added token {}", token.getToken());
        }

        filterChain.doFilter(request, response);
    }

}

原因分析

这种情况不符合CSRF保护的预期,正常修改Cookie中的CSRF Token后,请求应该被拒绝。可能的问题点包括:

  • 过滤器顺序错误:当前CsrfTokenResponseHeaderBindingFilter在CsrfFilter之后执行,拿到的CsrfToken是验证后的结果,而非当前请求的Token,日志输出会造成误导。
  • 请求未触发CSRF验证:需确认测试的请求路径不在ignoringRequestMatchers排除列表中(当前仅排除/login、/logout)。
  • Token提取/比对逻辑异常:若Angular提交的请求头Token未同步修改,或Spring Security未正确从请求中提取Token进行比对,会导致验证失效。

解决方案

  1. 调整过滤器顺序
    将CsrfTokenResponseHeaderBindingFilter移至CsrfFilter之前执行,确保获取到当前请求的原始CSRF Token:

    .addFilterBefore(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class)
    
  2. 开启调试日志排查验证过程
    添加Spring Security调试日志配置,查看CsrfFilter的执行细节,确认Token比对逻辑是否正常触发:

    logging.level.org.springframework.security=DEBUG
    

    重点关注日志中Validating CSRF token相关内容,确认Cookie中的Token与请求提交的Token是否一致。

  3. 确认Token提交位置匹配
    Angular默认从Cookie读取XSRF-TOKEN,并在请求头携带X-XSRF-TOKEN;CookieCsrfTokenRepository默认的Cookie名称为XSRF-TOKEN、Header名称为X-XSRF-TOKEN,需确保两者配置匹配,避免Spring Security无法正确提取请求中的Token。

  4. 验证测试流程
    修改Cookie中的Token后,需确保请求提交的Header/参数中的Token也同步修改,避免测试时仅修改Cookie但请求仍使用旧Token的情况。

总结

修改Cookie中CSRF Token后请求仍生效属于异常情况,通过调整过滤器顺序、开启调试日志排查验证逻辑、确认Token提交位置匹配,可定位并解决问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 12:27:34