Spring Boot Controller sends "Connection: close" on big body - spring

I have a Spring Boot Application (2.7.3) with a POST-Endpoint that is secured via Basic Auth. The client has reported problems connecting to the endpoint because he does use non-preemptive-authentication and receives a "Connection: close" on the first request without credentials. After receiving the Header Field, the client does not send a second request including the credentials and therefore cannot connect to the Application.
After investigation I found out that the "Connection: close" header is only send on a request with a rather large body. Is this expected behaviour?
Curl Requests:
#>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
#>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
Controller:
#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 Configuration:
#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();
}
}

Related

ResponseStatusException - response body missing

I am using
id 'org.springframework.boot' version '2.6.1'
Following is the code snippet I am using for throwing the exception
catch(Exception e){
System.out.println(e.getLocalizedMessage());
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "something went wrong",e);
}
In response, we are getting 400 without a response body
* upload completely sent off: 100 out of 100 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 400
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< 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: DENY
< Content-Length: 0
< Date: Thu, 27 Oct 2022 14:43:38 GMT
< Connection: close
<
* Closing connection 0
UPDATE: application.properties has the following config
server.error.include-message=always
What am I missing?
Starting from 2.3, Spring Boot doesn't include an error message on the default error page. The reason is to reduce the risk of leaking information to a client.
To change the default behavior, you can set the server.error.include-message property:
server.error.include-message=always
Source:
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#changes-to-the-default-error-pages-content

Why Spring Boot application does not work with relative quality factor of HTTP?

Lets suppose I have a Spring Boot application:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-webflux:2.4.0")
}
with a simple RestController:
#RestController
class TestController {
#PostMapping("/test")
suspend fun test(#RequestBody request: Map<String, String>) {
throw RuntimeException("test")
}
}
When I use httpie client to make requests, then the result looks like:
➜ ~ http post :8080/test param=value --verbose
POST /test HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 18
Content-Type: application/json
Host: localhost:8080
User-Agent: HTTPie/2.3.0
{
"param": "value"
}
HTTP/1.1 500 Internal Server Error
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 309
Content-Type: text/html;charset=UTF-8
Expires: 0
Pragma: no-cache
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
<html><body><h1>Whitelabel Error Page</h1><p>This application has no configured error view, so you are seeing this as a fallback.</p><div id='created'>Wed Dec 09 22:24:52 MSK 2020</div><div>[906bb33e-5] There was an unexpected error (type=Internal Server Error, status=500).</div><div>test</div></body></html>
When I use cURL:
➜ ~ curl -XPOST localhost:8080/test -d '{"param": "value"}' -H "Content-type: application/json" -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /test HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-type: application/json
> Content-Length: 18
>
* upload completely sent off: 18 out of 18 bytes
< HTTP/1.1 500 Internal Server Error
< Content-Type: application/json
< Content-Length: 170
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
< Referrer-Policy: no-referrer
<
* Connection #0 to host localhost left intact
{"timestamp":1607541989698,"path":"/test","status":500,"error":"Internal Server Error","message":"test","requestId":"7f249f68-9","exception":"java.lang.RuntimeException"}* Closing connection 0
The difference in header Accept. Httpie uses Accept: application/json, */*;q=0.5 header with relative quality factor 0.5 and, despite the fact that I requested JSON if possible (and it's possible), the application returns HTML representation.
Is it how Spring Boot should work and I do something wrong?
UPD: Everything works fine with Tomcat (spring-boot-starter-web). After some debugging I found that, when Tomcat used, errors are handled by org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController and for Netty it is org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler. It looks like the source of misbehavior is here: when MediaType.ALL is removed from acceptedMediaTypes .

What might be wrong with Spring Security Config? Get vs POST

I have a Rest Controller and two endpoints there. While I can do basic authentication to GET end endpoint, I can not perform the same for the POST endpoint.
Please see the curl executions for GET and POST below, for some reason POST endpoint returns 401.
What might be the reason? Any ideas? As you can check in Security Config at bottom, I do not have special cases for GET/POST.
GET TRY:
curl -v -u user:password -X GET http://localhost:8080/api/clusters/
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
* Server auth using Basic with user 'user'
> GET /api/clusters/ HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dXNlcjpwYXNzd29yZA==
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200
< 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: DENY
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Fri, 20 Dec 2019 12:50:44 GMT
<
* Connection #0 to host localhost left intact
[{"id":1,"name":"Alfa","securityProtocol":"PLAINTEXT","sslTruststorePassword":null,"sslKeystorePassword":null,"sslKeyPassword":null,"trustStore":null,"keyStore":null,"saslMechanism":"","saslJaasConfig":"","clientId":"Ali","requestTimeoutMs":30000,"version":0,"properties":[],"bootstrapServers":"kafka1:9093"}]* Closing connection 0
POST TRY:
kaan$ curl -v -u user:password -X POST http://localhost:8080/api/clusters/ -d {"name":"Beta"}
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
* Server auth using Basic with user 'user'
> POST /api/clusters/ HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dXNlcjpwYXNzd29yZA==
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 11
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 11 out of 11 bytes
< HTTP/1.1 401
< Set-Cookie: JSESSIONID=022342C09075AC29AC703547FD5A0E45; Path=/; HttpOnly
< 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: DENY
* Authentication problem. Ignoring this.
< WWW-Authenticate: Basic realm="Realm"
< Content-Length: 0
< Date: Fri, 20 Dec 2019 12:51:38 GMT
<
* Connection #0 to host localhost left intact
* Closing connection 0
And my Security Config is as:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER).and()
.authorizeRequests()
.antMatchers("/css/**", "/js/**", "/img/**", "/vendor/**", "/login").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.formLogin()
.loginPage("/login.html")
.permitAll()
.and()
.logout()
.permitAll();
}
This solved the issue:
.csrf().disable()

"Missing grant type" despite "Content-Type: application/x-www-form-urlencoded"

Similar to this question I am sending the following POST to the server:
content-type: application/x-www-form-urlencoded
authorization: Basic dGVzdGp3dGNsaWVudGlkOlhZN2ttem9OemwxMDA=
accept: */*
With the payload:
{"username"="test", "password": "pw", "email": "test#example.com"}
However, I'm still getting
{
"error": "invalid_request",
"error_description": "Missing grant type"
}
as response from the server. Any idea why this is not working?
Note that the request is working if I use curl:
$ curl testjwtclientid:XY7kmzoNzl100#localhost:8080/oauth/token -d grant_type=password -d username=john.doe -d password=pw -v
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
* Server auth using Basic with user 'testjwtclientid'
> POST /oauth/token HTTP/1.1
> Host: localhost:8080
> Authorization: Basic dGVzdGp3dGNsaWVudGlkOlhZN2ttem9OemwxMDA=
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 49
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 49 out of 49 bytes
< HTTP/1.1 200
< 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: DENY
< Cache-Control: no-store
< Pragma: no-cache
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Thu, 02 Nov 2017 19:21:29 GMT
<
* Connection #0 to host localhost left intact
{"access_token":"<an-ugly-long-token>","token_type":"bearer","expires_in":43199,"scope":"read write","jti":"80e2b6af-d999-4fb6-a4cd-5e6ab9c3fcaa"}
Your payload should be something like grant_type=password&username=john.doe&password=pw, whereas you are passing it as JSON. You can check Spring security OAuth2 accept JSON for JSON payload

JSON POST Spring MVC Curl - 400 bad request

This is my curl method request maps to POJO that fails with 400:
CURL REQUEST:
curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"key":"XYZ","uniqueId":"ABCD"}' http://localhost:8080/api/rest/connect/tick/view.json
* Adding handle: conn: 0x1cb54c0
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x1cb54c0) send_pipe: 1, recv_pipe: 0
* About to connect() to localhost port 8080 (#0)
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /api/rest/connect/tick/view.json HTTP/1.1
> User-Agent: curl/7.32.0
> Host: localhost:8080
> Accept: application/json
> Content-type: application/json
> Content-Length: 53
>
* upload completely sent off: 53 out of 53 bytes
< HTTP/1.1 400 Bad Request
* Server Apache-Coyote/1.1 is not blacklisted
< Server: Apache-Coyote/1.1
< Content-Type: text/html;charset=utf-8
< Content-Length: 971
< Date: Thu, 19 Sep 2013 13:16:29 GMT
< Connection: close
APPLICATION-CONFIG.XML:
<context:annotation-config/>
<mvc:annotation-driven/>
followed by, Bean Definitions
CONTROLLER CLASS:
#RequestMapping(value="/tick/view.*", method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE, produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public IConnectResponse tickConnectPost(#RequestBody final ConnectServiceBean connectServiceBean) {
CONNECTSERVICEBEAN:
import java.io.Serializable;
public class ConnectServiceBean implements Serializable {
private static final long serialVersionUID = 1L;
private String key;
private String uniqueId;
//Getters and setters }
But is working for another method request maps to String:
curl -i -H "Content-Type: application/json" -X POST -d '{"JKL#user.com"}' http://localhost:8080/api/rest/connect/tick/XYZ/view.json
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Pragma: no-cache
Cache-Control: no-cache, no-store, max-age=0
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Type: application/json;charset=UTF-8
Content-Language: en-GB
Transfer-Encoding: chunked
Date: Thu, 19 Sep 2013 13:11:40 GMT
CONTROLLER:
#RequestMapping(value="/tick/{key}/view.*", method = RequestMethod.POST, consumes="application/json", produces = "application/json; charset=utf-8")
public IConnectResponse tickConnectPostString (
#PathVariable("key") String key, #RequestBody String uniqueId
) {}
I felt that there is something wrong with Jackson Mapping. But I have all the necessary POM dependencies also I read in some other stackoverflow post where I need only for Jackson configurations for bean mappings(not sure though)
org.codehaus.jackson
jackson-core-asl
1.9.7
org.codehaus.jackson
jackson-mapper-asl
1.9.7

Resources