Lachezar Balev
Lachezar Balev

Reputation: 12041

Invalidate the session of a user (not the current) in Spring Security

Use case (Srping boot 3/Spring security 6):

I tried to implement that by registering a SessionRegistry. This is how it looks like:

  @Bean
  public SessionRegistry sessionRegistry() {
    return new SessionRegistryImpl();
  }
  @Bean
  public HttpSessionEventPublisher httpSessionEventPublisher() {
    // enables the events for session destruction so that these can be removed
    // from the session registry
    return new HttpSessionEventPublisher();
  }

Then in order to register new sessions in the session registry:

  @Bean
  public SecurityFilterChain filterChain(
      HttpSecurity http,
      SessionRegistry sessionRegistry) throws Exception {
    http.
          ...
          and().
            sessionManagement().
            sessionAuthenticationStrategy(new RegisterSessionAuthenticationStrategy(sessionRegistry)).

          ...        
    return http.build();
  }

So far so good. Then I can query the session registry for all session information objects.

My expectation was that after calling SessionInformation.expireNow() the session will be invalidated somehow (e.g. by a Filter). This does not happen and the problematic user is still logged in.

Any ideas how can I forcibly invalidate the session for which is the SessionInformation?

Here is the documentation of SessionInformation:

Sessions have three states: active, expired, and destroyed. A session can that is invalidated by session.invalidate() or via Servlet Container management is considered "destroyed". An "expired" session, on the other hand, is a session that Spring Security wants to end because it was selected for removal for some reason (generally as it was the least recently used session and the maximum sessions for the user were reached). An "expired" session is removed as soon as possible by a Filter.

Upvotes: 4

Views: 2494

Answers (1)

Lachezar Balev
Lachezar Balev

Reputation: 12041

After some pain, sweat and irritating hints in the documentation it seems that I've made it.

First, it seems that we indeed need a SessionRegistry. Because it is not wired by default we need to wire it manually, e.g. if we rely on the provided implementation:

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

Second, as mentioned in the docs of SessionRegistryImpl:

For this class to function correctly in a web application, it is important that you register an HttpSessionEventPublisher. So we need it too:

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

If we peek further into the documentation of SessionRegistryImpl we would see this curious remark: HttpSessionEventPublisher... file so that this class is notified of sessions that expire. So basically our repo listens only for expiring sessions, not new ones. We need something more.

Third we would need to register our repo so that it can listen for newly created sessions. This can be done by setting up the http security, namely:

and().
  sessionManagement().
  sessionAuthenticationStrategy(new RegisterSessionAuthenticationStrategy(sessionRegistry))

Fine, now you have a functioning SessionRegistry with all the power to retrieve the correct SessionInformation objects. The session information has a nice method expireNow(). If you use it you will find out that nothing happens. Time for the new interesting remark in the javadoc: An "expired" session is removed as soon as possible by a Filter. We have to find out which is this filter. Another hint helps: This is primarily used for concurrent session support.

Fourth. Seems that we need to setup concurrent session management support so that the correct filter that looks for expired sessions is installed. We can use the http security. Example:

         and().
            sessionManagement().
            sessionConcurrency(session -> session.maximumSessions(5)). <-- !

...

Upvotes: 3

Related Questions