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()
Related
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();
}
}
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
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 .
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
I have integrated Spring Session with Redis into my SpringBoot app. It seems that all works well except the cookie domain attribute. I just found how to set the cookie session domain attribute for in tomcat i.e. method "setSessionCookieDomain", but that does not work. for example.
I had configured domain attribute in the context of the tomcat for example the domain attribute of the cookie.
#Bean
public TomcatContextCustomizer tomcatContextCustomizer() {
System.out.println("TOMCATCONTEXTCUSTOMIZER INITILIZED");
return new TomcatContextCustomizer() {
#Override
public void customize(Context context) {
context.addServletContainerInitializer(new WsSci(), null);
context.setUseHttpOnly(true);
context.setPath("/");
context.setSessionCookiePath("/");
context.setSessionCookieDomain(".127.0.0.5");
// context.setSessionCookieDomain(".localhost");
// context.setSessionCookieDomain(".test.blabla.com");
}
};
}
When i open the https trace in wireshark and click follow ssl stream, here is what i get. All other attributes are listed except the domain. So my question is how do I set the domain attribute correctly in Spring Session 1.0.0.M1, Does spring session somehow override the tomcat context?
GET / HTTP/1.1
Host: 127.0.0.5:8888
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:34.0) Gecko/20100101 Firefox/34.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Lang: keep-alive
: keep-alive
: keep-alive
: keep-alive
: keep-alive
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
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 ; includeSubDomains
X-Frame-Options: DENY
X-Content-Security-Policy: script-src 'self'; object-src 'self'
Content-Security-Policy: script-src 'self'; object-src 'self'
X-WebKit-CSP: default-src 'self'
X-Application-Context: application:Production
Set-Cookie: SESSION=5d0a738f-f011-4e43-a1ee-d691b8eba94c; Path=/; Secure; HttpOnly
Content-Type: text/html;charset=UTF-8
Conten10:01:27 GMT
10:01:27 GMT
10:01:27 GMT
10:01:27 GMT
10:01:27 GMT
<!DOCTYPE html>
Thanks for bringing this up. Spring Session should allow configuring the domain via the SessionCookieConfig but doesn't. I have created gh-87 to address this.