spring-security Spring云网关上的CSRF正在从POST请求中删除formData 400错误请求错误

2ekbmq32  于 2022-11-11  发布在  Spring
关注(0)|答案(1)|浏览(138)

我已经在我的Spring Cloud API网关服务器上启用了CSRF。我有angular作为我的GUI框架,它通过API网关调用其余的服务。
我已经使用了一个自定义过滤器将CSRF标记添加到响应头中。
当进行POST调用时,我看到formData丢失了,所以我总是得到400个Bad请求错误。
我禁用了CSRF,请求顺利通过,没有任何问题。
有什么不对吗?
下面是我的spring云网关配置。网关只用于将请求路由到其他微服务,它没有任何控制器或其他端点。

@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休止符端点定义为
@请求参数
下面的代码来自于一个服务端点。2它是一个使用传统的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 Cloud Gateway中的筛选器似乎正在阻止formData
以下是我Netty配置:

@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;
                    }
            )
    );
}
}

下面是我的应用程序。ymlx 1c 0d1x
示例日志:

2022-07-28 09:18:20.743调试26532 --- [ctor-http-nio-5] r.n.http.客户端. HttpClient操作:[id:199 cd 714 -5,L:/127.0.0.1:50342- R:本地主机/127.0.0.1:18080]收到的响应(自动读取:错误):[X-内容-类型-选项=无嗅探,X-XSS-保护=1;模式=块,缓存控制=无缓存,无存储,最大使用期限=0,必须重新验证,编译指示=无缓存,过期=0,严格传输安全性=最大使用期限=31536000 ;包括子域,X-帧-选项=拒绝,X-应用程序-上下文=应用程序:18080,日期=星期四,2022年7月28日03:48:20 GMT,连接=关闭,内容长度=0] 2022-07-28 09:18:20.744调试26532 --- [ctor-http-nio-5] r.n.r.默认池连接提供程序:[ID:199 cd 714 -5,L:/127.0.0.1:50342- R:本地主机/127.0.0.1:18080]打开状态更改(POST{uri=/cms-service/webapi/terminate/描述符,连接=池连接{通道=[ID:如果您有任何问题,请与我们联系。如果您有任何问题,请与我们联系。[ID:199 cd 714 -5,L:/127.0.0.1:50342- R:本地主机/127.0.0.1:18080]通量接收{挂起=0,取消=假,入站完成=假,入站错误=空}:2008年12月26日,在北京,一个叫“中国”的网站在中国大陆的一个网站上发布了一个名为“中国”的网站。[ID:199 cd 714 -5,L:/127.0.0.1:50342- R:本地主机/127.0.0.1:18080]已接收到最后一个HTTP数据包。[正在减少挂起的响应,现在为0。2022-07-28 09:18:20.745调试26532 --- [ctor-http-nio-5] r.n.http.服务器。HttpServer操作:[ID:b 0 f975 eb-11,L:/0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337]最后一个HTTP数据包已发送,正在终止通道。请注意,如果您有一个错误请求,那么请在下面的代码中输入一个错误。如果您有一个错误请求,请在下面的代码中输入一个错误。[ID:b 0 f975 eb-11,L:/0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:0:1:50337]最后一个HTTP响应帧2022-07-28 09:18:20.745调试26532 --- [ctor-http-nio-5] c.m. Web网关.处理程序.请求记录器:处理/cms-service/webapi/terminate/描述符请求所需的总时间。默认池连接提供程序:[ID:199 cd 714,L:/127.0.0.1:50342- R:本地主机/127.0.0.1:18080]打开状态更改(POST{uri=/cms-service/webapi/terminate/描述符,连接=池连接{通道=[ID:如果您有任何问题,请与我们联系。如果您有任何问题,请与我们联系。[ID:199 cd 714,L:/127.0.0.1:50342- R:本地主机/127.0.0.1:18080]打开状态更改(POST{uri=/cms-service/webapi/terminate/描述符,连接=池连接{通道=[ID:[正在断开连接])2022-07-28 09:18:20.752调试26532 --- [ctor-http-nio-5] r.n.资源。池连接提供程序:[ID:199 cd 714,L:/127.0.0.1:50342!R:本地主机/127.0.0.1:18080]通道已关闭,现在状态为:0个活动连接,4个非活动连接和0个待处理的获取请求。2022-07-28 09:18:20.752调试26532 --- [ctor-http-nio-5] r.n.r.默认池连接提供程序:[ID:199 cd 714,L:/127.0.0.1:50342!R:本地主机/127.0.0.1:18080]上的状态更改(池连接{通道=[ID:如果您有任何问题,请与我们联系。如果您有任何问题,请与我们联系。[ID:100000]正在增加等待响应,现在是1个。2022-07-28 09:18:23.805调试26532 --- [ctor-http-nio-5] reactor.netty.http.server.HttpServer:[ID:b 0 f975 eb-12,L:/0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:0:1:50337]正在应用处理程序:这是一个很好的解决方案,它可以让你的工作更轻松、更高效,并且可以让你的工作更轻松。“服务描述符”是一个很好的描述符,它可以帮助您在Web服务器上创建一个服务描述符。
下面是指向示例项目的链接。https://github.com/manjosh1990/webgateway-issues
我尝试忽略FORM URL ENCODED请求和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();
}
kdfy810k

kdfy810k1#

感谢您提供最少的样本来重现此问题!
经过一些测试后,我无法为您的配置提供允许表单发布的解决方法或修复程序(URL编码)通过启用了CSRF保护的网关。(应缓存以供后续筛选器使用)与SpringCloudGateway如何使用请求主体以代理到下游服务。
我通过禁用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();
    }

}

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

相关问题