Spring Cloud Gateway后Angular应用POST请求CSRF令牌无效问题
问题:Spring Cloud Gateway 部署Angular应用时POST请求CSRF验证失败
我正尝试通过Spring Cloud Gateway部署Angular应用,GET请求正常,但向资源服务发送POST请求时,因Invalid CSRF token报错返回403 Forbidden,推测是配置问题但未定位到,官方文档对该场景帮助有限。
当前现象
POST请求返回403 Forbidden,错误信息如下:
error: "Invalid CSRF Token" message: "Http failure response for http://gateway:8000/api/post: 403 Forbidden" name: "HttpErrorResponse" ok: false status: 403 statusText: "Forbidden" url: "http://gateway:8000/api/post"
期望效果
不关闭Spring Security的CSRF防护,POST请求可正常执行。
现有配置
网关Spring Security配置
@Configuration @EnableWebFluxSecurity public class SecurityConfig { @Bean SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) throws Exception { return http .authorizeExchange(exchange -> exchange .pathMatchers("/**").permitAll() .anyExchange().authenticated()) .csrf(csrf -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())) .build(); } }
网关自定义WebFilter
@Component @Configuration public class CsrfCookieWebFilter implements WebFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { String key = CsrfToken.class.getName(); Mono<CsrfToken> csrfToken = null != exchange.getAttribute(key) ? exchange.getAttribute(key) : Mono.empty(); return csrfToken.doOnSuccess(token -> { ResponseCookie cookie = ResponseCookie.from("XSRF-TOKEN", token.getToken()) .maxAge(Duration.ofHours(1)) .httpOnly(false) .path("/") .sameSite(Cookie.SameSite.LAX.attributeValue()) .build(); exchange.getResponse().getCookies().add("XSRF-TOKEN", cookie); }).then(chain.filter(exchange)); } }
Angular配置
Angular应用的AppModule已导入HttpClientXsrfModule。
解决方案
问题根源
- Spring Cloud Gateway的
CookieServerCsrfTokenRepository默认生成的CSRF Cookie名与AngularHttpClientXsrfModule默认读取的Cookie名不匹配,导致Angular无法获取有效令牌 - 自定义
CsrfCookieWebFilter逻辑存在漏洞:当请求中无CsrfToken属性时,不会生成并返回XSRF-TOKENCookie,Angular拿不到令牌无法携带到请求头 - 网关与Angular的CSRF令牌传递、验证规则未对齐
修复步骤
1. 调整Spring Security CSRF配置,对齐Angular规则
移除自定义WebFilter,直接通过CookieServerCsrfTokenRepository显式配置与Angular匹配的Cookie和请求头名称:
@Configuration @EnableWebFluxSecurity public class SecurityConfig { @Bean SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) throws Exception { CookieServerCsrfTokenRepository csrfTokenRepository = CookieServerCsrfTokenRepository.withHttpOnlyFalse(); // 匹配Angular默认的Cookie名称 csrfTokenRepository.setCookieName("XSRF-TOKEN"); // 匹配Angular默认的请求头名称 csrfTokenRepository.setHeaderName("X-XSRF-TOKEN"); return http .authorizeExchange(exchange -> exchange .pathMatchers("/**").permitAll() .anyExchange().authenticated()) .csrf(csrf -> csrf.csrfTokenRepository(csrfTokenRepository)) .build(); } }
2. 删除自定义CsrfCookieWebFilter
CookieServerCsrfTokenRepository已自动处理CSRF令牌的Cookie生成与传递,自定义过滤器会导致逻辑冲突或重复操作。
3. 确认Angular HttpClientXsrfModule配置
默认配置已与上述网关配置对齐,若需显式声明可在AppModule中添加:
import { HttpClientXsrfModule } from '@angular/common/http'; @NgModule({ imports: [ HttpClientXsrfModule.withOptions({ cookieName: 'XSRF-TOKEN', headerName: 'X-XSRF-TOKEN', }), // 其他模块 ], }) export class AppModule { }
4. 跨域场景补充配置(若Angular与网关不同域)
如果存在跨域,需在网关添加CORS配置,允许携带凭证和自定义请求头:
@Bean CorsWebFilter corsWebFilter() { CorsConfiguration corsConfig = new CorsConfiguration(); corsConfig.setAllowedOrigins(Arrays.asList("http://你的Angular域名")); corsConfig.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); corsConfig.setAllowedHeaders(Arrays.asList("*")); corsConfig.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfig); return new CorsWebFilter(source); }
5. 验证流程
- 发送GET请求时,浏览器应收到
XSRF-TOKENCookie - 发送POST请求时,Angular自动在请求头中携带
X-XSRF-TOKEN,值与Cookie一致 - 网关Spring Security验证头中令牌与服务器端存储的令牌匹配,通过验证
内容的提问来源于stack exchange,提问作者Giovanni Piccolo
相关产品推荐
相关产品推荐

