Spring Cloud Gateway启用CSRF后POST请求丢失formData致400错误
问题根源
Spring Cloud Gateway基于Reactor(响应式)实现,请求体(ServerHttpRequest的body)是Flux<DataBuffer>,只能被订阅消费一次。默认的CsrfWebFilter在处理POST类请求时,会尝试解析请求体以查找CSRF令牌(即便你的令牌放在请求头里),这会直接消费请求体,导致后续路由到上游服务时请求体已为空,上游用@RequestParam接收不到参数,最终返回400错误。
你尝试忽略FORM URL ENCODED类型但未生效,大概率是匹配规则配置错误,或是过滤器执行顺序导致忽略逻辑未触发。
解决方案
1. 正确配置CSRF过滤器,仅检查请求头令牌且不消费form-urlencoded请求体
修改网关的CSRF配置,指定令牌仅从请求头获取,同时排除application/x-www-form-urlencoded类型的请求,避免过滤器解析这类请求的body:
@Configuration public class GatewayCsrfConfig { @Bean public CsrfWebFilter csrfWebFilter() { CsrfWebFilter filter = new CsrfWebFilter(); // 指定CSRF令牌从请求头获取(Angular默认用X-XSRF-TOKEN) filter.setCsrfTokenRepository(new XorCsrfTokenRepository()); filter.setRequestAttributeName(CsrfToken.class.getName()); // 配置需要CSRF校验的请求规则:排除form-urlencoded类型,仅针对非安全HTTP方法 filter.setRequiresCsrfMatcher(new AndServerWebExchangeMatcher( ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE), exchange -> { MediaType contentType = exchange.getRequest().getHeaders().getContentType(); return contentType == null || !MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType); } )); return filter; } }
2. 确保自定义CsrfHeaderFilter不消费请求体
你的自定义过滤器仅需往响应头添加令牌,不要读取或修改请求体:
@Order(-100) @Component public class CsrfHeaderFilter implements WebFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { CsrfToken csrfToken = exchange.getAttribute(CsrfToken.class.getName()); if (csrfToken != null) { exchange.getResponse().getHeaders().add("X-XSRF-TOKEN", csrfToken.getToken()); } // 直接传递请求,不触碰请求体 return chain.filter(exchange); } }
3. 调整过滤器执行顺序
通过@Order注解指定顺序:
- 自定义
CsrfHeaderFilter设置@Order(-100),确保在响应处理阶段执行,不干扰请求体传递 - 默认
CsrfWebFilter的默认顺序是Ordered.LOWEST_PRECEDENCE - 100,无需额外调整,会在路由过滤器之前执行
4. 验证上游服务参数接收配置
确认上游Servlet服务的POST端点正确配置:
@PostMapping(value = "/api/endpoint", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) public ResponseEntity<String> handlePost(@RequestParam("param1") String param1, @RequestParam("param2") String param2) { // 业务逻辑 return ResponseEntity.ok("Success"); }
关键注意事项
- 响应式请求体的一次性消费特性是核心问题,任何过滤器只要读取了请求体(哪怕只是解析),后续环节就无法再获取,必须确保CSRF过滤器不消费form-urlencoded请求的body
- Angular默认会从响应头的
X-XSRF-TOKEN获取令牌,后续POST请求会自动携带X-XSRF-TOKEN请求头,你的自定义过滤器逻辑是正确的,只需确保不干扰请求体传递
内容的提问来源于stack exchange,提问作者manjosh
相关产品推荐
相关产品推荐

