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:
issuer
and subject
, but without the context of user sessionThe 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?
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.