如何访问Springwebflux服务器示例上的所有会话/挂钩会话生命周期?

zfycwa2u  于 2023-03-16  发布在  Spring
关注(0)|答案(2)|浏览(169)

我正在尝试在Spring webflux应用程序(spring-cloud-gateway)上实现一个Back-Channel Logout端点。
为此,我需要“静态”检索用户会话,以便:

  • 删除匹配issuersubject但没有用户会话上下文的授权客户端
  • 如果移除的授权客户端是该用户的最后一个(具有授权码),则使会话无效。但仅当不再有授权客户端时:如果您使用同一客户端登录Google和Facebook,则当您仅从其中一个客户端注销时,该客户端上的会话应保持活动状态。

当前的ServerWebExchange由OIDC提供者自己发起,由于不涉及用户浏览器,因此没有会话cookie。
相反,提供了一个“logout”JWT作为请求主体(我在这里找到了issuersubject,它们应该足以标识要失效的会话和要删除的授权客户机)。
对于Servlet,这个问题可以通过实现HttpSessionListener, HttpSessionIdListener(并使用ServletListenerRegistrationBean)来解决,每次添加或删除授权客户机时,都可以通过issuer和用户subject来构建和维护会话索引。
不幸的是,spring-cloud-gateway是一个被动的应用程序,我找不到与WebSession等效的会话侦听器。
有线索告诉我该怎么做吗?

vql8enpb

vql8enpb1#

一种可能的方法是覆盖默认的WebSessionManager配置,并使用手动创建的InMemoryWebSessionStore bean设置会话存储。

@Configuration
public class SessionConfig {

    @Bean
    public WebSessionManager webSessionManager (WebSessionStore webSessionStore) {
        DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager();
        webSessionManager.setSessionStore(webSessionStore);
        return webSessionManager;
    }

    @Bean
    public InMemoryWebSessionStore inMemoryWebSessionStore() {
        return new InMemoryWebSessionStore();
    }

}

然后在注销逻辑中,我们可以注入InMemoryWebSessionStore bean,检索所有会话并执行注销:

@Service
public class SessionService {

    private InMemoryWebSessionStore webSessionStore;

    public SessionService(InMemoryWebSessionStore webSessionStore) {
        this.webSessionStore = webSessionStore;
    }

    private Mono<Void> performLogout(String issuer, String subject) {
       String sessionId = webSessionStore.getSessions()
                .entrySet().stream()
                .filter(e -> shouldBeRemoved(issuer, subject, e.getValue()))
                .findFirst() // assuming issuer and subject combination is always unique
                .map(Map.Entry::getKey)
                .orElse("dummy-session-id");
       return webSessionStore.removeSession(sessionId);
    }

    private boolean shouldBeRemoved(String issuer, String subject, WebSession session) {
        OidcUser oidcUser = oidcUserFromSession(session);
        return issuer.equals(oidcUser.getIssuer().toString()) && subject.equals(oidcUser.getSubject());
    }

    private OidcUser oidcUserFromSession(WebSession session) {
        SecurityContext securityContext = session.getAttribute(WebSessionServerSecurityContextRepository
                .DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME);
        return (OidcUser) securityContext.getAuthentication().getPrincipal();
    }

}
soat7uwm

soat7uwm2#

来自@elyorbek-ibrokhimov的答案并没有满足我的需求,即挂钩到会话生命周期,但它确实让我走上了正确的轨道。非常感谢他。以下是我如何实现它(灵感来自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);
    }
}

现在,我可以针对这个WebSessionStore注册一个定制的授权客户端存储库,并让它收到sessionCreatedsessionRemoved事件的通知,以更新它自己的索引。

相关问题