Spring Cloud Gateway配置CSRF保护遇"未找到预期CSRF令牌"错误求助
Spring Cloud Gateway中CSRF保护验证失败问题
我在Spring Cloud Gateway中配置CSRF保护时,浏览器已成功获取到XSRF-TOKEN Cookie,但将该Cookie值通过请求头X-XSRF-TOKEN: somevalue携带发送请求时,始终收到错误:An expected CSRF token cannot be found。
安全配置代码
import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository; import org.springframework.security.web.server.csrf.CsrfToken; @Configuration @EnableWebFluxSecurity public class SecurityConfiguration { @Bean public SecurityWebFilterChain springSecurityFilterChain( ServerHttpSecurity http, ReactiveClientRegistrationRepository clientRegistrationRepository) { http.csrf( csrfSpec -> csrfSpec .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) .csrfTokenRequestHandler(new SpaServerCsrfTokenRequestHandler())) .authorizeExchange( authorizeExchangeSpec -> authorizeExchangeSpec .anyExchange() .authenticated()) .oauth2Login(Customizer.withDefaults()) .logout(); return http.build(); } @Bean WebFilter csrfCookieWebFilter() { return (exchange, chain) -> { exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty()).subscribe(); return chain.filter(exchange); }; } }
SpaServerCsrfTokenRequestHandler代码
import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; public class SpaServerCsrfTokenRequestHandler extends ServerCsrfTokenRequestAttributeHandler { private final ServerCsrfTokenRequestAttributeHandler delegate = new XorServerCsrfTokenRequestAttributeHandler(); @Override public void handle(ServerWebExchange exchange, Mono<CsrfToken> csrfToken) { this.delegate.handle(exchange, csrfToken); } @Override public Mono<String> resolveCsrfTokenValue(ServerWebExchange exchange, CsrfToken csrfToken) { if (exchange.getRequest().getHeaders().getOrEmpty(csrfToken.getHeaderName()).stream() .anyMatch(StringUtils::hasText)) { return super.resolveCsrfTokenValue(exchange, csrfToken); } return this.delegate.resolveCsrfTokenValue(exchange, csrfToken); } }
参考了Spring Security 6 CSRF保护失效问题的相关讨论以及官方文档中SPA应用的CSRF集成指南。
问题排查与解决方案
1. 核心问题:Token加密逻辑不匹配
XorServerCsrfTokenRequestAttributeHandler会对Cookie中的CSRF Token做异或加密,浏览器拿到的XSRF-TOKEN是加密后的值。你的请求处理器逻辑是:当请求头存在X-XSRF-TOKEN时直接读取明文验证,但存储的Token是加密后的,自然匹配失败。
2. 修正请求处理器逻辑
修改SpaServerCsrfTokenRequestHandler,确保请求头传入的加密Token能被正确解密验证:
import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; public class SpaServerCsrfTokenRequestHandler extends ServerCsrfTokenRequestAttributeHandler { private final XorServerCsrfTokenRequestAttributeHandler delegate = new XorServerCsrfTokenRequestAttributeHandler(); @Override public void handle(ServerWebExchange exchange, Mono<CsrfToken> csrfToken) { this.delegate.handle(exchange, csrfToken); } @Override public Mono<String> resolveCsrfTokenValue(ServerWebExchange exchange, CsrfToken csrfToken) { String headerToken = exchange.getRequest().getHeaders().getFirst(csrfToken.getHeaderName()); // 优先读取请求头,并用delegate统一解密 if (StringUtils.hasText(headerToken)) { return this.delegate.resolveCsrfTokenValue(exchange, csrfToken); } return this.delegate.resolveCsrfTokenValue(exchange, csrfToken); } }
3. 优化CsrfCookieWebFilter的异步处理
原代码直接subscribe()可能导致Token未加载完成就进入后续链路,改用then确保异步操作完成:
@Bean WebFilter csrfCookieWebFilter() { return (exchange, chain) -> { Mono<CsrfToken> csrfTokenMono = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty()); return csrfTokenMono.then(chain.filter(exchange)); }; }
4. 检查跨域配置(若存在跨域场景)
如果前端和网关跨域部署,需确保CORS允许携带X-XSRF-TOKEN请求头和XSRF-TOKEN Cookie:
@Bean CorsWebFilter corsWebFilter() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOriginPattern("*"); // 替换为实际允许的域名 config.addAllowedHeader("*"); config.addAllowedMethod("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); }
5. 验证请求头名称一致性
确认请求头名称与CsrfToken.getHeaderName()返回值一致,默认是X-XSRF-TOKEN,避免拼写错误。
内容的提问来源于stack exchange,提问作者vishal
相关产品推荐
相关产品推荐

