Spring Cloud Gateway适配Angular的CSRF配置问题及解决方案咨询
问题回顾
我尝试将Spring Security官方文档中的配置适配到响应式应用(以Spring Cloud Gateway作为BFF,配置为OAuth2客户端),配置代码如下:
http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) .csrfTokenRequestHandler(new XorServerCsrfTokenRequestAttributeHandler()::handle);
但仍出现“An expected CSRF token cannot be found”错误,且浏览器调试工具中找不到XSRF-TOKEN Cookie。
随后定义了如下WebFilter:
@Bean WebFilter csrfCookieWebFilter() { return (exchange, chain) -> { Mono<CsrfToken> csrfToken = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty()); return csrfToken.doOnSuccess(token -> { }).then(chain.filter(exchange)); }; }
现在浏览器能获取到XSRF-TOKEN Cookie,却出现“Invalid CSRF Token”错误。
我的疑问:
CookieServerCsrfTokenRepository到底是什么?- 为何在Spring Cloud Gateway中使用它无法生成CSRF令牌Cookie?
- 如何配置Spring Cloud Gateway以允许Angular应用向
/logout端点发送PUT请求?
补充说明:此前1个半月有类似问题未得到解答,已在Spring Security提交工单。
疑问解答
1. CookieServerCsrfTokenRepository是什么?
CookieServerCsrfTokenRepository是Spring Security提供的CSRF令牌存储实现,核心职责包括:
- 生成CSRF令牌并将其存储到名为
XSRF-TOKEN的Cookie中(默认名称可自定义) - 从请求头
X-XSRF-TOKEN中提取令牌,完成验证逻辑 - 支持配置Cookie的HttpOnly、Secure等属性,
withHttpOnlyFalse()方法专门用于允许前端JavaScript读取Cookie,适配Angular这类自动携带X-XSRF-TOKEN头的框架。
2. Spring Cloud Gateway中无法自动生成CSRF Cookie的原因
在响应式Web应用(如Spring Cloud Gateway)中,CSRF令牌采用懒加载机制:只有当服务器需要验证CSRF令牌,或者显式订阅了CsrfToken的Mono时,才会触发令牌生成和Cookie写入。
初始配置中没有触发令牌生成的逻辑——Spring Cloud Gateway作为BFF,若请求未进入需要CSRF验证的端点(如POST/PUT/DELETE等写操作端点),服务器不会主动生成CSRF令牌,这就是一开始浏览器中没有XSRF-TOKEN Cookie的原因。
3. 解决Invalid CSRF Token错误的配置方案
添加的WebFilter触发了令牌生成,但出现验证错误,是因为XorServerCsrfTokenRequestAttributeHandler会对令牌进行异或处理(用于BREACH攻击防护),而Angular发送的是Cookie中原始的令牌值,两者不匹配。
正确配置需兼顾BREACH防护和Angular的自动令牌携带逻辑,步骤如下:
步骤1:配置正确的CSRF处理器组合
使用XorServerCsrfTokenRequestAttributeHandler配合默认逻辑,允许读取原始令牌进行验证:
@Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { XorServerCsrfTokenRequestAttributeHandler delegate = new XorServerCsrfTokenRequestAttributeHandler(); // 允许从请求头中读取未异或的原始令牌 delegate.setCsrfRequestAttributeName(null); http.csrf(csrf -> csrf .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) .csrfTokenRequestHandler(delegate)); // 补充你的OAuth2客户端等其他配置 return http.build(); }
步骤2:保留触发令牌生成的WebFilter
你定义的WebFilter会主动订阅CsrfToken,触发令牌生成和Cookie写入,需保留:
@Bean WebFilter csrfCookieWebFilter() { return (exchange, chain) -> { Mono<CsrfToken> csrfToken = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty()); return csrfToken.doOnSuccess(token -> { // 订阅后会自动写入Cookie,无需额外操作 }).then(chain.filter(exchange)); }; }
4. 允许Angular向/logout发送PUT请求
Spring Security默认的/logout端点接受GET请求,Angular通常发送PUT/POST等写请求,可通过两种方式配置:
方案1:配置/logout接受PUT方法并验证CSRF(推荐)
让logout端点支持PUT方法,同时依赖之前的CSRF配置完成验证:
http.logout(logout -> logout .logoutUrl("/logout") .allowedMethods(HttpMethod.PUT, HttpMethod.POST, HttpMethod.GET));
此时Angular会自动从XSRF-TOKEN Cookie中提取令牌,放入X-XSRF-TOKEN请求头,配合CSRF配置即可通过验证。
方案2:忽略/logout的CSRF验证(仅适合特定场景)
若无需对logout请求做CSRF验证,可直接忽略:
http.csrf(csrf -> csrf .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) .csrfTokenRequestHandler(delegate) .ignoringAntMatchers("/logout"));
内容的提问来源于stack exchange,提问作者ch4mp

