当前位置: 首页 > 知识库问答 >
问题:

Spring云网关上的 CSRF 从 POST 请求中删除表单数据 400 错误请求错误

濮书
2023-03-14

我已经在我的spring云api网关服务器上启用了CSRF。我使用angular作为GUI框架,它通过api网关调用其余服务。

我使用了自定义过滤器将CSRF令牌添加到响应标头。

当进行开机自检调用时,我看到表单数据丢失。所以我总是收到 400 个错误请求错误。

我禁用了CSRF,请求正常,没有任何问题。

有什么问题吗?

下面是我的spring云网关配置。网关仅用于将请求路由到其他微服务,它没有任何控制器或Restendpoint。

@SpringBootApplication
public class GatewayApplication {

@Autowired
ProfileManager profileManager;

@PostConstruct
public void onInit() {
    profileManager.printActiveProfiles();
}

public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); }
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    http.authorizeExchange().anyExchange().permitAll();
    http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse());
    return http.build();
   }
}

下面是过滤器代码

@Component
public class CsrfHeaderFilter implements WebFilter {

@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    Mono<CsrfToken> token = (Mono<CsrfToken>) exchange.getAttributes().get(CsrfToken.class.getName());
    if (token != null) {
        return token.flatMap(t -> chain.filter(exchange));
    }
    return chain.filter(exchange);
}

}

我的 POST Restendpoint定义为

@RequestParam

下面是来自rest服务endpoint之一的代码。它是使用传统servlet springboot框架实现的上游服务。

@RequestMapping(value = "terminate/{listName}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED)
@CrossOrigin
@Loggable (activityname = ActivityLogConstants.DESCRIPTOR_TERMINATE)
public Response terminate(@Context HttpServletRequest reqContext, @PathVariable String listName, @RequestParam(value = "rowData") String rowData)
        throws ServiceException {....}

当请求到达上游服务时,formData 将丢失。

看起来Spring云网关中的过滤器正在阻止formData

以下是我的网络配置:

@Configuration
public class NettyConfiguration implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {

@Value("${server.max-initial-line-length:65536}")
private int maxInitialLingLength;
@Value("${server.max-http-header-size:65536}")
private int maxHttpHeaderSize;

public void customize(NettyReactiveWebServerFactory container) {
    container.addServerCustomizers(
            httpServer -> httpServer.httpRequestDecoder(
                    httpRequestDecoderSpec -> {
                        httpRequestDecoderSpec.maxHeaderSize(maxHttpHeaderSize);
                        httpRequestDecoderSpec.maxInitialLineLength(maxInitialLingLength);
                        return httpRequestDecoderSpec;
                    }
            )
    );
}
}

示例日志:

2022-07-28 09:18:20.743 调试 26532 --- [ctor-http-nio-5] r.n.http.client.HttpClientOperations : [id:199cd714-5, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] 收到的响应(自动读取:false):[X-Content-Type-Options=nosniff, X-XSS-Protection=1; mode=block, Cache-Control=no-cache, no-store, max-age=0, must-revalidate, Pragma=no-cache, Expires=0, Strict-Transport-Security=max-age=31536000 ; includeSubDomain, X-Frame-Options=DENY, X-Application-Context=application:18080, 日期=星期四, 28 七月 2022 03:48:20 GMT, 连接=关闭, 内容长度=0] 2022-07-28 09:18:20.744 调试 26532 --- [ctor-http-nio-5] r.n.r.DefaultPooledConnectionProvider : [id:199cd714-5, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] onStateChange(POST{uri=/cms-service/webapi/terminate/descriptor, connection=PooledConnection{channel=[id: 0x199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080]}}, [response_received]) 2022-07-28 09:18:20.744 调试 26532 --- [ctor-http-nio-5] reactor.netty.channel.FluxReceive : [id:199cd714-5, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] FluxReceive{pending=0, cancel=false, inboundDone=false, inboundError=null}: 订阅入站接收器 2022-07-28 09:18:20.744 调试 26532 --- [ctor-http-nio-5] r.n.http.client.HttpClientOperations : [id:199cd714-5, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] 收到最后一个 HTTP 数据包 2022-07-28 09:18:20.744 调试 26532 --- [ctor-http-nio-5]r.n.http.server.HttpServerOperations : [id:b0f975eb-11, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:1:50337] 减少挂起的响应,现在为 0 2022-07-28 09:18:20.745 调试 26532 --- [ctor-http-nio-5] r.n.http.server.HttpServerOperations : [id:b0f975eb-11, L:/0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] 上次发送 HTTP 数据包,终止通道 2022-07-28 09:18:20.745 调试 26532 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter : [b0f975eb-11, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] 已完成 400 BAD_REQUEST 2022-07-28 09:18:20.745 调试 26532 --- [ctor-http-nio-5] r.n.http.server.HttpServerOperations : [id:b0f975eb-11, L:/0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] 最后一个 HTTP 响应帧 2022-07-28 09:18:20.745 调试 26532 --- [ctor-http-nio-5] c.m.webgateway.handler.RequestLogger : 处理 /cms-service/webapi/terminate/descriptor 请求所需的总时间 60055 2022-07-28 09:18:20.745 调试 26532 --- [ctor-http-nio-5] r.n.r.DefaultPooledConnectionProvider : [id:199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] onStateChange(POST{uri=/cms-service/webapi/terminate/descriptor, connection=PooledConnection{channel=[id: 0x199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080]}}, [response_completed]) 2022-07-28 09:18:20.745 调试 26532 --- [ctor-http-nio-5] r.n.r.DefaultPooledConnectionProvider : [id:199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] onStateChange(POST{uri=/cms-service/webapi/terminate/descriptor, connection=PooledConnection{channel=[id: 0x199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080]}}, [断开连接]) 2022-07-28 09:18:20.752 调试 26532 --- [ctor-http-nio-5] r.n.resources.PooledConnectionProvider : [id:199cd714, L:/127.0.0.1:50342 !R:localhost/127.0.0.1:18080] 通道已关闭,现在:0 个活动连接、4 个非活动连接和 0 个挂起的获取请求。2022-07-28 09:18:20.752 调试 26532 --- [ctor-http-nio-5] r.n.r.DefaultPooledConnectionProvider : [id:199cd714, L:/127.0.0.1:50342 !R:localhost/127.0.0.1:18080] onStateChange(PooledConnection{channel=[id: 0x199cd714, L:/127.0.0.1:50342 !R:localhost/127.0.0.1:18080]}, [断开连接]) 2022-07-28 09:18:23.805 调试 26532 --- [ctor-http-nio-5] r.n.http.server.HttpServerOperations : [id:b0f975eb, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] 增加待处理响应,现在 1 2022-07-28 09:18:23.805 调试 26532 --- [ctor-http-nio-5] reactor.netty.http.server.HttpServer : [id:b0f975eb-12, L:/0:0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] 正在应用处理程序:org.springframework.http.server.reactive。ReactorHttpHandlerAdapter@7c82616c 2022-07-28 09:18:23.805 调试 26532 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter : [b0f975eb-12, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] HTTP GET “/cms-service/webapi/data/descriptor”

下面是示例项目的链接。https://github.com/manjosh1990/webgateway-issues

我试图忽略表单URL编码的请求和GET请求,但它仍然不起作用

private static final Set<HttpMethod> ALLOWED_METHODS = new HashSet<>(
        Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE, HttpMethod.OPTIONS));
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
    http.authorizeExchange().anyExchange().permitAll().and()
            .csrf(csrf -> csrf
                    .requireCsrfProtectionMatcher(ignoringFormUrlEncodedContentType())
                    .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()));
    return http.build();
}
private ServerWebExchangeMatcher ignoringFormUrlEncodedContentType() {
    return (exchange) -> !MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(
            exchange.getRequest().getHeaders().getContentType()) || !ALLOWED_METHODS.contains(exchange.getRequest().getMethod())
            ? ServerWebExchangeMatcher.MatchResult.match()
            : ServerWebExchangeMatcher.MatchResult.notMatch();
}

共有1个答案

东方弘壮
2023-03-14

感谢您提供最小的样本来重现问题!

经过一些测试,我无法想出一个解决方法或修复您的配置,允许表单发布(URL编码)在启用CSRF保护的情况下通过网关。我的最佳猜测是,它与Spring Security如何使用请求正文(应该缓存以供后续过滤器使用)以及Spring Cloud Gateway如何使用请求正文以代理到下游服务有关。

我通过禁用CSRF保护并添加以下过滤器对此进行了测试:

@Component
public class TestWebFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return Mono.defer(() -> exchange.getFormData()
                .doOnSuccess(System.out::println))
                .then(chain.filter(exchange));
    }
}

在我的测试中,这会导致通过网关的请求在收到之前长时间阻塞:

{
    "timestamp": "2022-08-10T19:13:54.265+00:00",
    "status": 400,
    "error": "Bad Request",
    "path": "/cms-service/webapi/service/post/test"
}

由于这似乎是Spring Security中的一个bug,我建议提交一个Spring Security中的bug,我们可以从那里着手解决它。

如果您想同时解决此问题,您可以为这些类型的请求禁用CSRF保护,如下所示:

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
            .authorizeExchange((authorize) -> authorize
                .anyExchange().authenticated()
            )
            .csrf((csrf) -> csrf
                .requireCsrfProtectionMatcher(ignoringFormUrlEncodedContentType())
                .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
            )
            .oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt);
        return http.build();
    }

    private ServerWebExchangeMatcher ignoringFormUrlEncodedContentType() {
        return (exchange) -> !MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(
            exchange.getRequest().getHeaders().getContentType())
                ? ServerWebExchangeMatcher.MatchResult.match()
                : ServerWebExchangeMatcher.MatchResult.notMatch();
    }

}

重要提示:这并不理想,因为这些请求不会受到保护。但是,如果这些请求从未在浏览器中执行过,这可能是有意义的。在这种情况下,有一个单独的身份验证机制是有意义的,比如要求一个不记名令牌而不是表单登录,等等。(如上例)。

 类似资料:
  • 我正在为一个项目使用Hackerrank API。查看官方文档,点击这里! 在他们的网站上有一个使用UNIREST的例子, 由于我使用的是axios,所以我将其转换为类似的axios代码,如下所示: 我希望这只适用于示例中所示的示例,但它给我带来了以下错误: 请求失败,状态代码为400 错误:请求失败,状态代码为400 在createError(createError.js:16) 在sett(s

  • 我尝试发送一个删除请求到我的服务器,但我得到一个400坏的请求错误代码。我找了很多,但我找不到任何有帮助解决方案。当我尝试与邮差,请求工作良好。 这是来自Postman的curl命令的外观: 这是我的Java代码: 我还尝试在头中不设置Content-Type,但当我得到一个415错误时,我还删除了getBodyContentType()方法,但这也没有改变。 还有什么能帮上忙的点子吗?

  • 我收到 400 个错误的 AJAX 发布方法请求。我正在后端使用Spring数据Rest服务。以下是我在JS前端的代码 尽管我正在序列化 JSON 数据。我仍然收到400错误请求错误。如果后端的某些代码中断或发送到服务器的请求出现问题,则会出现此错误吗? JAVA实现

  • 但如果我将请求更改为“multipart/form-data”-春回错误400“bad request”: 为什么?也许我应该创造一些额外的豆子? PS:我需要“multipart/form-data”来与json对象一起发送文件。

  • 我有一个基于Spring Web model view controller(MVC)框架的项目。Spring Web模型-视图-控制器(MVC)框架的版本是3.2.8 我有这个控制器 这个URL一切正常:

  • 目前从Angular JS controller中,我试图将JSON数据发送到后端服务。但是我有400个错误的请求错误。 在Controller中,我试图通过http服务发送数据,如下所示: