Describe the bug I have a spring security application that configures a ConcurrencyControlConfigurer with maximum sessions set to 1 and maxSessionsPreventsLogin set to true. The problem is when I logout and log back the user in I get an error that says the max sessions limit has been reached.

After doing some debugging I've noticed that the spring changes the sessionId multiple times in between requests (to prevent session fixation I guess). The ChangeSessionIdAuthenticationStrategy changes the sessionId but never updates the existing sessionId for the user in SessionRegistry to the new sessionId.

So essentially what ends up happing is when the logout is triggered the HttpEventPublisher publishes a new event with the new sessionId to be destroyed by the SessionRegistry. But of course the sessionRegistry never finds it because the sessionRegistry was never notified of the session id change!

When the sessionId change happens a SessionFixationProtectionEvent is triggered but I couldn't find any known listeners for this event. Shouldn't SessionRegistry be listening to this event because the ConcurrentSessionControlAuthenticationStrategy seems to be using the sessionRegistry to make decisions on the max allowed sessions per user?

To Reproduce

  1. Configure spring security with maximumSessions set to 1 and maxSessionsPrevents set to true.
sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
invalidSessionUrl(PATH_LOGIN_PAGE)
enableSessionUrlRewriting(false)
maximumSessions(1)
maxSessionsPreventsLogin(true);
  1. Login as user 1 and logout.
  2. Login back as user 1 and spring security throws an error : "Maximum sessions of 1 for this principal exceeded"

Expected behavior When a sessionId change happens the sessionRegistry needs to be notified of this event to keep the sesionIds up to date so that the logout can successfully clear the sessionId from session registry.

Comment From: jzheaux

I think what's needed here is to publish the HttpSessionEventPublisher class as well:

@Bean 
HttpSessionEventPublisher sessionEventPublisher() {
    return new HttpSessionEventPublisher();
}

This is detailed in the Spring Security Reference, but it only contains the XML configuration, so it's easy to overlook.

Would you be interested in submitting a PR to add Java config to that section of the documentation? It would also be helpful to update the relevant question in the FAQ.

Comment From: jcd006

@jzheaux I did declare the bean in the configuration file I just omitted in the code snippet. Also it looks like HttpSessionEventPublisher only publishes the SessionCreated and the SessionDestroyed events not the SessionFixationProtectionEvent that's fired by the ChangeSessionIdAuthenticationStrategy.

I noticed spring-security change the jsessionId a few times but none of those changes made it into the sessionRegistry. Is there a way spring syncs the sessionId changes to the sessionRegistry?

I've tried to fix it by implementing a listener to update the sessionRegistry. While I've seen some ids get syc'd it looks like the listener is not able to keep pace with all the sessionId changes. So by the time the logout happens the session id supplied in the logout request doesn't exist in the SessionRegistry.

.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .invalidSessionUrl(PATH_LOGIN_PAGE)
                .enableSessionUrlRewriting(false)
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true)
                .sessionRegistry(sessionRegistry());

@Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

@Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    @Bean
    public SessionFixationProtectionEventListener sessionFixationProtectionEventListener(SessionRegistry sessionRegistry) {
        return new SessionFixationProtectionEventListener(sessionRegistry);
    }

public class SessionFixationProtectionEventListener implements ApplicationListener<SessionFixationProtectionEvent> {

    private SessionRegistry sessionRegistry;

    public SessionFixationProtectionEventListener(SessionRegistry sessionRegistry) {
        this.sessionRegistry = sessionRegistry;
    }

    @Override
    public void onApplicationEvent(SessionFixationProtectionEvent event) {
        sessionRegistry.removeSessionInformation(event.getOldSessionId());
        sessionRegistry.registerNewSession(event.getNewSessionId(), event.getAuthentication().getPrincipal());
    }
}

Comment From: jzheaux

@jcd006 Okay, thanks for the extra information.

When I change Spring Security's hello world sample to work according to your original spec + publish an HttpSessionEventPublisher, things appear to work as expected.

Would you please post a sample via GitHub? That will ensure that there's no further miscommunication.

Comment From: spring-projects-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: jcd006

I was able to resolve this issue by overriding the default session mitigation strategy to migrateSession() instead of the default ChangeSessionIdAuthenticationStrategy.

It looks like there's a similar issue that was resolved earlier here that had the same suggested answer :

https://github.com/spring-projects/spring-security/issues/7166

Comment From: jzheaux

Thanks for the extra information, @jcd006. I'll close this as a duplicate of https://github.com/spring-projects/spring-security/issues/7166, then.