Duplicate Cache-Control header in Spring Boot application - spring-boot

We have a Spring Boot application that runs in Openshift where we configure the Cache-Control header like this:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().cacheControl().disable().addHeaderWriter((httpServletRequest, httpServletResponse) -> {
httpServletResponse.setHeader(HttpHeaders.CACHE_CONTROL, "public, max-age=86400");
});
}
}
In the HTTP responses there are two Cache-Control headers:
$ curl --header https://<our-url> --head
HTTP/1.1 200 Connection established
HTTP/1.1 200
...
Cache-Control: public, max-age=86400
...
Cache-control: private
We expect the first header, and we have no idea where the second header comes from. (Note the lowercase c in the name of the second header.)
Any ideas where the second header comes from and how we can get rid of it?

I found the answer: The Cache-control header is added by HAProxy. HAProxy uses this header and a cookie to create sticky sessions (i.e. to make sure that requests from the same client are handled by the same pod).
See this question for details.
In short, you can disable this behaviour by
oc annotate route <myroute> haproxy.router.openshift.io/disable_cookies='true'

Related

SecurityFilterChain .anyRequest().permitAll() doesn't allow POST requests

Using Spring Boot 2.7 (Spring Security 5.7.1) and trying to configure an API as a resource server and OAuth2 client I find a behavior I don't get to understand:
#EnableWebSecurity
public class SecurityConfig {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.mvcMatchers("/swagger-ui/**", "/api-docs/**").permitAll()
.anyRequest().permitAll())
// register OAuth2 resource server
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
// register OAuth2 client
.oauth2Client(withDefaults());
return http.build();
}
}
Checking the logs, all this filters apply
o.s.s.web.DefaultSecurityFilterChain : Will secure any request with
org.springframework.security.web.session.DisableEncodeUrlFilter#320ca97c,
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#3c592c0c,
org.springframework.security.web.context.SecurityContextPersistenceFilter#2b33e616,
org.springframework.security.web.header.HeaderWriterFilter#2e9bff08,
org.springframework.security.web.csrf.CsrfFilter#7926d092,
org.springframework.security.web.authentication.logout.LogoutFilter#37227aa7,
org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter#6f18445b,
org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter#42af2977,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter#79e3f444, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#1252d480,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter#3979c6e8,
org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter#19faa9dc,
org.springframework.security.web.session.SessionManagementFilter#7d3b4646,
org.springframework.security.web.access.ExceptionTranslationFilter#6cb2d5ea,
So far, this configuration works as expected in the other APIs I'm protecting. However, in this particular one, AND not having protected any endpoint I see that:
I can access any GET endpoint but any POST endpoint returns a 403 FORBIDDEN. However, I can access them if I add a token to the request EVEN if its an expired token
This alone I can't understand as .anyRequest().permitAll() should unprotect anything, if I'm not wrong.
If I comment out the line configuring the oauth2 ResourceServer
`// .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)`
this filter dissapears
org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter
And can't use POST endpoints anymore, even with the expired token
Logically, I want the API to be oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) so
How it is I can't access POST endpoints when using .anyRequest().permitAll())?
DISCLAIMER: I know it makes no sense to declare the API as resource server if all endpoints must be public. Endpoints will be accessed by Discord callbacks and I have to figure out if I can protect them with OAuth
EDIT:
server.servlet.context-path = /api
Controller
#RestController
#RequestMapping("/slack")
public class SlackBotController {
#PostMapping("/test")
public String test(#RequestBody String a) {
return a;
}
#GetMapping("/test")
public String testGet() {
return "OK";
}
}
Request
GET/POST http://localhost:8081/api/slack/test
Request headers
User-Agent: PostmanRuntime/7.29.0
Accept: */*
Postman-Token: f20ba7a6-26e5-47c4-a827-0596afec27b8
Host: localhost:8081
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 3
Cookie: JSESSIONID=D1C2B2668DC130C63DDE03F30574ED5F; JSESSIONID=823D79956CFBF14F3C77C96E29F4131C
Response 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-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 03 Jun 2022 12:03:00 GMT
Keep-Alive: timeout=60
Connection: keep-alive
The reason why the POST endpoint or the POST returns a 403 FORBIDDEN because the CSRF protection is enabled by default in spring.
That means that every modifying request (POST, PUT, DELETE, PATCH) requires a CSRF token. Otherwise the request gets denied to prevent CSRF attacks.
To disable the CSRF protection:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable();
}
The problem here is the CSRF protection. When you use http.oauth2ResourceServer() Spring Security configures CSRF to ignore requests that contains the header Authorization: Bearer whatever, note that it has to contain the Bearer prefix.
In the request sample that you shared, you are not including the Authorization header.
Take a look at the code in Spring Security.

Spring WS not accepting basic auth credentials

I've been struggling with this one for a while and can't figure out what I'm doing wrong.
I'm supposed to add basic authentication to my SOAP web service in Spring. I made the security config pretty simple (maybe too simple) so it concentrates on basic auth only (see below).
When I'm accessing the base URL from the browser, the authentication seems to be working, it asks for the credentials and if I provide them correctly, it accepts them.
However, when I want to send the SOAP request that contains the basic auth header to my web service endpoint, Spring Security sends back 401 to me. I tried sending the request with SOAPUI, Postman and from Windows Powershell via Invoke-WebRequest, and the result is the same whereas if I catch the request with Wireshark, the right header is there.
I'm using Spring Boot 2.1.8 for this project (same version with Spring Web Services and Security).
The security config class:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("foo")
.password(passwordEncoder().encode("bar"))
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and().httpBasic();
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
To my understanding, there is nothing specific I have to add to the web service config itself so all related basic auth settings can be done in the security config class. Or am I wrong?
Appreciate your help.
Update
Here is the request/response pair:
REQUEST:
POST /foo/endpoint/ HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: "http://foo.bar"
Authorization: Basic Zm9vOmJhcg==
Content-Length: 9688
Host: localhost:1502
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:foo="http://foobar.com/">
<soapenv:Header/>
<soapenv:Body>
// body omitted
</soapenv:Body>
</soapenv:Envelope>
RESPONSE
HTTP/1.1 401
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
WWW-Authenticate: Basic realm="Realm"
Content-Length: 0
Date: Thu, 13 Aug 2020 14:22:29 GMT
configure(HttpSecurity http) needs some modifications to enable the http basic authentication in your code.
authorizeRequests() is used for authorization purposes. Once after a user successfully logged in, what all the resources(endpoints) should be accessible to logged-in user, is defined by authorizeRequests(). I also recommend, while authorizing the endpoints, you better use the antMatchers. For Example: I have modified the above code as
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("foo")
.password(passwordEncoder().encode("bar"))
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and()
.authorizeRequests()
.antMatchers("/simple/**").hasRole("USER");
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
I hope this would work for you. Try & let me know.
NOTE: In Spring boot, just by adding the spring-boot-starter-security dependency, security gets enabled, without any configurations. So now, you trying to re-configure the basic auto-configuration. By extending, WebSecurityConfigurerAdapter class. That's correct.
POSTMAN: use authorization header

Spring Boot Custom Authorization Header request not going through at all

So I have a Spring Boot application and I am sending a request to it using PostMan. It is using Spring Security along with JWT for authentication. I'm trying to get authorization to work but am running into issues. Spring is able to login the user and return a token fine. But when I put the token in the header it's not working at all. I get no response from the server. When the token is removed, it works fine. Right now all requests should be able to go through regardless of being logged in or not.
My Spring Web Configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
}
The REST path I'm trying to access:
#RestController("threadService")
#RequestMapping("/api/thread")
public class ThreadService {
#RequestMapping(value="/list", method=RequestMethod.GET)
public List<ThreadDetails> getThreadList() {
logger.info("getThreadList");
return threadDao.getThreadList();
}
}
The failed GET request I'm issuing after I have logged in and gotten a token:
GET /api/thread/list HTTP/1.1
Host: localhost:8080
Authorization : Bearer (JWT token here)
Cache-Control: no-cache
Postman-Token: 69565839-4806-b4f6-9a03-11382a80c7da
The above request works fine when there is no Authorization in the header.
Not sure it is exactly the problem I was facing.
When I want to communicate with the restservice exposed by spring boot application, the "Authorization" is not set. I followed the steps which are required to communicate but the value wont be passes through header.
The solution I found, the "common-codec" library was missing. Once I add the dependency in my web application, it start sending the "Authorization" in header to my spring boot application.
Hope this helps to someone.

Use Active Directory Authentication in Spring Boot OAuth2 Authorization Server

I'm trying it set up a proof of concept using Spring Boot and OAuth2. I've set up some project very similar to the ones outlined here:
https://spring.io/guides/tutorials/spring-boot-oauth2/
and here: https://spring.io/guides/tutorials/spring-security-and-angular-js/
The main difference with mine is I've left out all the AngularJS stuff.
I have the following services:
Authorization server
Resource server (as protected OAuth2 client)
UI server (as protected OAuth2 client)
All that I want to happen is this:
Hit the UI server
Get redirected to auth server and get prompted for credentials
UI server will then fetch some text from resource server and display them
I can get this all to work fine with Basic authentication on the auth server. However I want to be able to replace the basic authentication with that from Active Directory.
I have a couple of other Spring Boot projects that can do the AD authentication and it works, however whenever I try and drop it into this things go wrong. I think it's to do with the security around the auth server endpoints, but I'm not sure what.
Also, it's not clear to me which endpoints should be secured by what protocol (OAuth2 v. Basic) in a production environment? The docs recommend some endpoints should be secured with Basic. Should all 'OAuth2 clients' somehow include these credentials in their requests?
Here's my auth server application (with bits for Active Directory added):
#EnableResourceServer
#EnableAuthorizationServer
#SpringBootApplication
#RestController
public class AuthServerLdapApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServerLdapApplication.class, args);
}
#RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
#Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER)
#Configuration
protected static class ActiveDirectoryConfig extends WebSecurityConfigurerAdapter {
#Value("${activedirectory.url}")
private String activeDirectoryUrl;
#Value("${activedirectory.domain}")
private String getActiveDirectoryDomain;
#Autowired private AuthenticationManager authenticationManager;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(getActiveDirectoryDomain,
activeDirectoryUrl);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
auth.authenticationProvider(provider);
auth.parentAuthenticationManager(authenticationManager);
}
}
}
Basically what then happens is that I get this when I hit the UI server:
<oauth>
<error_description>
Full authentication is required to access this resource
</error_description>
<error>unauthorized</error>
</oauth>
If I do this against the authorization server:
curl -v http://localhost:9004/uaa/login
* Trying ::1...
* Connected to localhost (::1) port 9004 (#0)
> GET /uaa/login HTTP/1.1
> Host: localhost:9004
> User-Agent: curl/7.44.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< 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
< X-Frame-Options: DENY
< Cache-Control: no-store
< Pragma: no-cache
< WWW-Authenticate: Bearer realm="null", error="unauthorized", error_description="Full authentication is required to access this resource"
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 01 Dec 2015 12:38:53 GMT
<
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}* Connection #0 to host localhost left intact
It looks like the login endpoint now expects a bearer token? I'm not sure how to proceed now...
Any help/advice would be appreciated...
You #EnableResourceServer but you haven't provided any rules for the resources you want to protect so it's trying to protect everything. You need to add a #Bean of type ResourceServerConfigurerAdapter and set the request matchers (as well as whatever other rules you want).

Spring MVC Trace Http Request

I want to disable the Trace verb on the serve so that endpoints are untraceable.
My endpoints are created using Spring MVC and there is a option endpoints.trace.enabled=false.
Pain is how to test this, if trace is disabled or not. I tried chrome plug-ins live HTTP Headers but it for generic site. I want to check my endpoint.
For example:
curl --insecure -v -X TRACE -H http://localhost:8080/toy/49f6a7d3-eb20-3ab2-be3b-8399e7f28abf
HTTP/1.1 200 OK
< Date: Sat, 21 Mar 2015 04:34:03 GMT
< Content-Type: message/http; charset=UTF-8
< Content-Length: 270
<
TRACE /toy/49f6a7d3-eb20-3ab2-be3b-8399e7f28abf HTTP/1.1
User-Agent: curl/7.37.1
Host: localhost:8080
Accept: */*
* Connection #0 to host localhost left intact
I tried below but now it denies all the request.
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.TRACE, "/**").denyAll();
}
}
AFAIK TRACE is disabled by default on Tomcat, as well as Jetty 6,
You can use curl to make sure:
curl -v -X TRACE http://www.example.com
You want to see 405 method not allowed in the response.

Resources