spring-projects / spring-projects/spring-security
Change of default securityContextRepository in filters causes SessionRegistryImpl to be empty
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 9.6k
- Forks
- 6.3k
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 52
Description
Introduction
In my project we use websockets to send UI notifications. For this reason we use the SessionRegistry in order to retrieve the logged in users to send notifications to.
Everything was working well with Spring Boot 2.7.x and Spring Security 5.7.x but after the migration to Spring Boot 3 and Spring Security 6.2 I noticed that the SessionRegistry was always empty even though there were logged in users.
After struggling a lot to investigate and hard debugging, I came across the reason why this is happening.
I'm not sure if it's a bug or a wanted behavior, nevertheless I couldn't find any reference in the documentation so I'm raising here the problem.
Situation
Our application is beyond CAS authentication server. So I configured a J2eePreAuthenticatedProcessingFilter in order to catch the logged in user and manage it
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(requests -> requests
.requestMatchers("/public/**").permitAll()
.requestMatchers("/secure/**").authenticated())
.cors(withDefaults())
.addFilterAfter(j2eePreAuthenticatedProcessingFilter(), J2eePreAuthenticatedProcessingFilter.class)
.headers(headers -> headers
.httpStrictTransportSecurity(security -> security.includeSubDomains(true).maxAgeInSeconds(31536000).requestMatcher(AnyRequestMatcher.INSTANCE))
.frameOptions(options -> options.sameOrigin()))
.sessionManagement(management -> management
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.sessionFixation().changeSessionId()
.maximumSessions(3)
.sessionRegistry(sessionRegistry()));
return http.build();
}
@Bean
J2eePreAuthenticatedProcessingFilter j2eePreAuthenticatedProcessingFilter() {
J2eePreAuthenticatedProcessingFilter filter = new J2eePreAuthenticatedProcessingFilter();
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationDetailsSource(yestWebAuthenticationDetailsSource());
filter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
return filter;
}
I skipped the other parts of the configuration as not useful for our scenario.
When I logged in the application, the filter chain I could see in the stack was:
....->J2eePreAuthenticatedProcessingFilter.....->SessionManagementFilter...->
In the SessionManagementFilter there's the part which afterwards leads to fill the SessionRegistry by the sessionAuthenticationStrategy
this.sessionAuthenticationStrategy.onAuthentication(authentication, request, response);
as the event is caught by the SessionRegistryImpl instance. However this block of code is executed only if the session wasn't already stored in the securityContextRepository
Here is it the snippet of SessionManagementFilter where this happens (please focus on ----> comments)
----> if the securityContext is still empty, then it goes inside the if
if (!this.securityContextRepository.containsContext(request)) {
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
if (this.trustResolver.isAuthenticated(authentication)) {
// The user has been authenticated during the current request, so call the
// session strategy
try {
----> The event is raised and the SessionRegistry is filled
this.sessionAuthenticationStrategy.onAuthentication(authentication, request, response);
}
catch (SessionAuthenticationException ex) {
// The session strategy can reject the authentication
this.logger.debug("SessionAuthenticationStrategy rejected the authentication object", ex);
this.securityContextHolderStrategy.clearContext();
this.failureHandler.onAuthenticationFailure(request, response, ex);
return;
}
// Eagerly save the security context to make it available for any possible
// re-entrant requests which may occur before the current request
// completes. SEC-1396.
----> Then the context is saved
this.securityContextRepository.saveContext(this.securityContextHolderStrategy.getContext(), request,
response);
}
In Spring Security 5.7, by default the securityContextRepository for the J2eePreAuthenticatedProcessingFilter is a NullSecurityContextRepository; instead, in the SessionManagementFilter by default it's an instance of HttpSessionSecurityContextRepository.
So, in the block mentioned above, the execution goes inside the if and everything works well.
In Spring Security 6.2 we have the HttpSessionSecurityContextRepository for both the filters and because of the order in the chain, when it comes to the SessionManagementFilter, the context is already present in the securityContextRepository and it never goes inside the if, with the consequence that the event is never raised.
To Fix
In order to make it work I've just added the NullSecurityContextRepository to the J2eePreAuthenticatedProcessingFilter configuration.
@Bean
J2eePreAuthenticatedProcessingFilter j2eePreAuthenticatedProcessingFilter() {
J2eePreAuthenticatedProcessingFilter filter = new J2eePreAuthenticatedProcessingFilter();
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationDetailsSource(yestWebAuthenticationDetailsSource());
filter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
filter.setSecurityContextRepository(new NullSecurityContextRepository()); ---> THE SOLUTION
return filter;
}
However I don't know if this is the correct way or just a workaround for a possible bug.
Any suggestion is very welcome
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading SessionManagementFilter and J2eePreAuthenticatedProcessingFilter, focusing on their securityContextRepository defaults and the call to sessionAuthenticationStrategy.onAuthentication. Compare the Spring Security 5.7 and 6.2 behavior described in the report, including the NullSecurityContextRepository workaround; done should establish whether the changed defaults are intentional and ensure the expected SessionRegistry behavior is documented or corrected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring
- Domain
- authentication, backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100