Spring Boot大请求体下Basic Auth返回Connection: close是否为预期行为?
Spring Boot 2.7.3大请求体触发Basic Auth返回
Connection: close的问题解答 这是预期行为,根源在于Servlet容器(如Tomcat)与Spring Security的交互逻辑:
原因分析
- 大请求体场景:当请求体体积较大时,客户端会发送
Expect: 100-continue请求头,服务器返回100 Continue后客户端开始传输请求体。此时Spring Security的Basic Auth认证逻辑触发,发现请求无凭证后返回401 Unauthorized。由于服务器已经接收了部分请求体,为防止资源泄漏,Servlet容器会主动关闭连接并返回Connection: close头——因为该连接已被部分传输的请求体污染,无法复用处理后续的认证重试请求。 - 小请求体场景:请求体较小时,客户端直接发送完整请求,Spring Security在服务器接收完请求体前就完成了认证检查并返回
401,此时连接状态未被污染,因此可以复用,不会返回Connection: close。
解决方案
方案1:客户端启用预认证
让客户端在第一次请求时就携带Authorization: Basic <base64编码的用户名密码>头,跳过401重试流程。例如使用curl时添加-u username:password参数:
curl -u username:password -H "Accept: application/json" -v -d "@<path-to-file>\big_body.json" http://127.0.0.1:8080/demo/updateSapStatus
方案2:调整服务端配置(不推荐)
可以修改Tomcat的maxSwallowSize参数,允许服务器吞下已接收的请求体后复用连接,但这可能导致服务器资源被恶意请求消耗,存在安全风险。添加配置如下:
server.tomcat.max-swallow-size=-1
(注:设为-1表示无限制,生产环境需谨慎使用)
原问题中的请求示例
大请求体请求(返回Connection: close)
#>curl -H "Accept: application/json" -v -d "@<path-to-file>\big_body.json" http://127.0.0.1:8080/demo/updateSapStatus * Trying 127.0.0.1:8080... * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) > POST /demo/updateSapStatus HTTP/1.1 > Host: 127.0.0.1:8080 > User-Agent: curl/7.83.1 > Accept: application/json > Content-Length: 1585745 > Content-Type: application/x-www-form-urlencoded > Expect: 100-continue > * Mark bundle as not supporting multiuse < HTTP/1.1 100 * Mark bundle as not supporting multiuse < HTTP/1.1 401 < WWW-Authenticate: Basic realm="Realm" < 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 < X-Frame-Options: SAMEORIGIN < Content-Length: 0 < Date: Thu, 17 Nov 2022 08:01:18 GMT < Connection: close < * we are done reading and this is set to close, stop send * Closing connection 0
小请求体请求(未返回Connection: close)
#>curl -H "Accept: application/json" -v -d "[]" http://127.0.0.1:8080/demo/updateSapStatus * Trying 127.0.0.1:8080... * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) > POST /demo/updateSapStatus HTTP/1.1 > Host: 127.0.0.1:8080 > User-Agent: curl/7.83.1 > Accept: application/json > Content-Length: 2 > Content-Type: application/x-www-form-urlencoded > * Mark bundle as not supporting multiuse < HTTP/1.1 401 < WWW-Authenticate: Basic realm="Realm" < 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 < X-Frame-Options: SAMEORIGIN < Content-Length: 0 < Date: Thu, 17 Nov 2022 08:01:22 GMT < * Connection #0 to host 127.0.0.1 left intact
控制器代码
@RestController @RequestMapping("/demo") public class DemoController { @PostMapping(value = "/updateSapStatus") public ResponseEntity<String> updateSapStatus(@RequestBody List<ChangeSapStatusRequestBody> requestBody) { return ResponseEntity.of(Optional.of("Hello")); } }
Basic Auth配置
@EnableWebSecurity public class BasicAuthSecurityConfiguration { private static final String USER = "USER"; @Bean public AuthenticationProvider basicAuthenticationProvider() { PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); UserDetails user = User.builder() .username("username") .password(encoder.encode("password")) .roles(USER) .build(); InMemoryUserDetailsManager userManager = new InMemoryUserDetailsManager(user); DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(userManager); return provider; } @Bean @Order(1) public SecurityFilterChain basicAuthFilterChain(HttpSecurity http) throws Exception { return http .requestMatchers(requestMatchers -> requestMatchers.anyRequest()) .authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().hasRole(USER)) .csrf(CsrfConfigurer::disable) .headers(headers -> headers.frameOptions(FrameOptionsConfig::sameOrigin)) .httpBasic(withDefaults()) .build(); } }
内容的提问来源于stack exchange,提问作者flxkrmr
相关产品推荐
相关产品推荐

