修改CSRF Cookie值后请求仍有效?Spring Security 6.x CSRF疑问
问题背景
正在理解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进行比对,会导致验证失效。
解决方案
调整过滤器顺序
将CsrfTokenResponseHeaderBindingFilter移至CsrfFilter之前执行,确保获取到当前请求的原始CSRF Token:.addFilterBefore(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class)开启调试日志排查验证过程
添加Spring Security调试日志配置,查看CsrfFilter的执行细节,确认Token比对逻辑是否正常触发:logging.level.org.springframework.security=DEBUG重点关注日志中
Validating CSRF token相关内容,确认Cookie中的Token与请求提交的Token是否一致。确认Token提交位置匹配
Angular默认从Cookie读取XSRF-TOKEN,并在请求头携带X-XSRF-TOKEN;CookieCsrfTokenRepository默认的Cookie名称为XSRF-TOKEN、Header名称为X-XSRF-TOKEN,需确保两者配置匹配,避免Spring Security无法正确提取请求中的Token。验证测试流程
修改Cookie中的Token后,需确保请求提交的Header/参数中的Token也同步修改,避免测试时仅修改Cookie但请求仍使用旧Token的情况。
总结
修改Cookie中CSRF Token后请求仍生效属于异常情况,通过调整过滤器顺序、开启调试日志排查验证逻辑、确认Token提交位置匹配,可定位并解决问题。
内容的提问来源于stack exchange,提问作者dmance

