Search code examples
springspring-securityoauth-2.0spring-webfluxspring-cloud-gateway

How to access all sessions on a Spring webflux server instance / hook into sessions lifecycle?


I'm trying to implement a Back-Channel Logout endpoint on a Spring webflux application (spring-cloud-gateway).

For that, I need to retrieve user session "statically" to:

  • remove the authorized client matching an issuer and subject, but without the context of user session
  • if the removed authorized client was the last one (with authorization-code) for this user, invalidate the session. But only if there is no more authorized client: if you are logged onto Google and Facebook with the same client, the session on this client should remain active when you logout from only one of the two.

The current ServerWebExchange is initiated by the OIDC provider itself and, as user browser is not involved, there is no session cookie.

Instead, a "logout" JWT is provided as request body (this is where I find the issuer and the subject which should be enough to identify the session to invalidate and the authorized client to remove).

With Servlets, this problem can be addressed by implementing HttpSessionListener, HttpSessionIdListener (and using a ServletListenerRegistrationBean) to build and maintain indexes of sessions by issuer and user subject each time an authorized client is added or removed.

Unfortunately, spring-cloud-gateway is a reactive application and I couldn't find equivalent session listeners for WebSession.

Any clue on how I could proceed?


Solution

  • The answer from @elyorbek-ibrokhimov does not answer my need which is to hook into the sessions lifecycle but it definitely put me on the right track. Many thanks to him. Here is how I implemented it (with inspiration from the MaxIdleTimeInMemoryWebSessionStore):

    @Bean
    WebSessionManager webSessionManager(WebSessionStore webSessionStore) {
        DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager();
        webSessionManager.setSessionStore(webSessionStore);
        return webSessionManager;
    }
    
    @Bean
    WebSessionStore webSessionStore(ServerProperties serverProperties) {
        return new SpringAddonsWebSessionStore(serverProperties.getReactive().getSession().getTimeout());
    }
    
    public static interface WebSessionListener {
    
        default void sessionCreated(WebSession session) {
        }
    
        default void sessionRemoved(String sessionId) {
        }
    }
    
    static class SpringAddonsWebSessionStore implements WebSessionStore {
        private final InMemoryWebSessionStore delegate = new InMemoryWebSessionStore();
        private final ConcurrentLinkedQueue<WebSessionListener> webSessionListeners = new ConcurrentLinkedQueue<WebSessionListener>();
    
        private final Duration timeout;
    
        public SpringAddonsWebSessionStore(Duration timeout) {
            this.timeout = timeout;
        }
        
        public void addWebSessionListener(WebSessionListener listener) {
            webSessionListeners.add(listener);
        }
    
        @Override
        public Mono<WebSession> createWebSession() {
            return delegate.createWebSession().doOnSuccess(this::setMaxIdleTime)
                    .doOnSuccess(session -> webSessionListeners.forEach(l -> l.sessionCreated(session)));
        }
    
        @Override
        public Mono<WebSession> retrieveSession(String sessionId) {
            return delegate.retrieveSession(sessionId);
        }
    
        @Override
        public Mono<Void> removeSession(String sessionId) {
            webSessionListeners.forEach(l -> l.sessionRemoved(sessionId));
            return delegate.removeSession(sessionId);
        }
    
        @Override
        public Mono<WebSession> updateLastAccessTime(WebSession webSession) {
            return delegate.updateLastAccessTime(webSession);
        }
    
        private void setMaxIdleTime(WebSession session) {
            session.setMaxIdleTime(this.timeout);
        }
    }
    

    Now, I can register a custom authorized client repository against this WebSessionStore and have it be notified with sessionCreated and sessionRemoved events to update its own indexes.