I'm having some trouble with my application. We're using Spring Boot 2.4.10 and Spring Security 5.4.8. We use cookies to interact with the app.
We have a frontend application stored in src/main/resources
that, among other things, connects to a websocket endpoint exposed in /api/v1/notification
.
application.properties
file:
# cookie-related settings
server.servlet.session.cookie.name=mySessionCookie
server.servlet.session.cookie.path=/
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final String[] authorizedWithoutAuth = {
"/",
"/index.html",
"/static/**"
};
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers(authorizedWithoutAuth).permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.and()
.logout().logoutUrl("/api/v1/logout").invalidateHttpSession(true)
.deleteCookies("mySessionCookie");
}
}
While no user is logged in, the frontend tries to reach periodically the websocket endpoint to open a connection.
The first api/v1/notification
ends redirected to the /error
endpoint, which returns an HttpServletResponse
with a 'Set-Cookie' header (I think this may be an anonymous cookie set in the first request?) and a 401 status. I cannot change this behaviour.
The following requests to api/v1/notification
use this cookie in the header (while user is not logged in). These requests are also redirected to the /error
endpoint, which returns each time an HttpServletResponse
with 401 status but here, no 'Set-Cookie' header is included.
Once the user logs in with Authorization headers, a correct cookie is set by the response and used in the following requests.
The thing is, sometimes the set cookie suddenly changes again to an invalid one, and the following requests, done with this new invalid cookie, turn into a redirection to the login page.
After checking the code, it seems there is an old api/v1/notification
request (previous to the login request) taking place, with an invalid cookie (the anonymous one, present before login).
This request is redirected to the /error
endpoint: here, once again the HttpServletResponse
has 401 status and is containing a Set-Cookie header that is modifying the browser cookie (replacing the good one).
Following is a scheme of the problem, to hopefully make it easier to understand.
I would like to prevent an unauthorized request from setting the session cookie.
It's ok if a previous request responds with a 401 code, but I don't want it to change the current set cookie.
I tried extending the ErrorController
by returning a ResponseEntity
with all the headers present in the input HttpServletResponse
except for the 'Set-Cookie' header. This doesn't work.
I also tried modifying my configuration to disable anonymous requests:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final String[] authorizedWithoutAuth = {
"/",
"/index.html",
"/static/**"
};
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.anonymous().disable()
.authorizeRequests()
// .antMatchers(authorizedWithoutAuth).permitAll() I had to remove these from here, and include them in the method below
.anyRequest().authenticated()
.and()
.csrf().disable()
.and()
.logout().logoutUrl("/api/v1/logout").invalidateHttpSession(true)
.deleteCookies("mySessionCookie");
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(authorizedWithoutAuth);
}
}
but the session cookie is still set this way too, with 401 requests.
I also tried using @ControllerAdvice
to handle the exceptions, but these are thrown by Spring Security in the AbstractSecurityInterceptor
, as learnt in this response.
Thak you all for your time. Sorry for the post length :)
I started digging in Spring Security libraries, and noticed the session cookie was being set in HttpSessionRequestCache.saveRequest(...)
method:
public void saveRequest(HttpServletRequest request, HttpServletResponse response) {
if (!this.requestMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Did not save request since it did not match [%s]", this.requestMatcher));
}
} else {
DefaultSavedRequest savedRequest = new DefaultSavedRequest(request, this.portResolver);
if (!this.createSessionAllowed && request.getSession(false) == null) {
this.logger.trace("Did not save request since there's no session and createSessionAllowed is false");
} else {
request.getSession().setAttribute(this.sessionAttrName, savedRequest);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Saved request %s to session", savedRequest.getRedirectUrl()));
}
}
}
}
The 'Set-Cookie' header appears when creating the DefaultSavedRequest
object.
I changed my WebSecurityConfig
to the following:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.requestCache().requestCache(getHttpSessionRequestCache()) // This is new
.and()
.authorizeRequests().antMatchers(authorizedWithoutAuth).permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.and()
.logout().logoutUrl("/api/v1/logout").invalidateHttpSession(true)
.deleteCookies("mySessionCookie");
}
public HttpSessionRequestCache getHttpSessionRequestCache()
{
HttpSessionRequestCache httpSessionRequestCache = new HttpSessionRequestCache();
httpSessionRequestCache.setCreateSessionAllowed(false); // I modified this parameter
return httpSessionRequestCache;
}
}
and now it works. When logging in with Authorization headers, the cookie is being set correctly, but all the requests with an invalid session cookie or an expired one return a 401
response without setting a new one.
Also, after reading this answer, I understood better what this createSessionAllowed
was doing