如何访问 Spring webflux 服务器实例上的所有会话/挂接到会话生命周期?

问题描述 投票:0回答:2

我正在尝试在 Spring webflux 应用程序 (spring-cloud-gateway) 上实现一个

Back-Channel Logout
端点。

为此,我需要:

  • 删除匹配
    issuer
    subject
    的授权客户端,但没有用户会话的上下文
  • 如果删除的授权客户端是该用户的最后一个(带有授权码),则“静态”检索用户会话以使其无效

目前的

ServerWebExchange
是由OIDC提供商自己发起的,由于不涉及用户浏览器,因此没有会话cookie。

相反,提供了一个“注销”JWT 作为请求正文(这是我找到

issuer
subject
的地方,它们应该足以识别要失效的会话和要删除的授权客户端)。

使用 Servlet,这个问题可以通过实施

HttpSessionListener, HttpSessionIdListener
(并使用
ServletListenerRegistrationBean
)来解决,每次添加或删除授权客户端时,
issuer
和用户
subject
构建和维护会话索引。

不幸的是,

spring-cloud-gateway
是一个反应式应用程序,我找不到
WebSession
的等效会话侦听器。

关于我如何继续的任何线索?

spring spring-security spring-webflux spring-security-oauth2 spring-cloud-gateway
2个回答
2
投票

一种可能的方法是覆盖默认

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();
    }

}

0
投票

@elyorbek-ibrokhimov 的回答没有回答我的需要,即挂钩到会话生命周期,但它确实让我走上了正确的轨道。非常感谢他。这是我如何实现它的(灵感来自

MaxIdleTimeInMemoryWebSessionStore
):

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

@Bean
WebSessionStore webSessionStore(SpringAddonsOAuth2ClientProperties clientProperties) {
    return new SpringAddonsWebSessionStore(Duration.ofHours(clientProperties.getSessionsDurationInHours()));
}

static class SpringAddonsWebSessionStore implements WebSessionStore {
    private final InMemoryWebSessionStore delegate = new InMemoryWebSessionStore();

    private final Duration timeout;

    public SpringAddonsWebSessionStore(Duration timeout) {
        this.timeout = timeout;
    }

    @Override
    public Mono<WebSession> createWebSession() {
        // TODO do my own stuff
        return delegate.createWebSession().doOnSuccess(this::setMaxIdleTime);
    }

    @Override
    public Mono<Void> removeSession(String sessionId) {
        // TODO do my own stuff
        return delegate.removeSession(sessionId);
    }

    @Override
    public Mono<WebSession> retrieveSession(String sessionId) {
        return delegate.retrieveSession(sessionId);
    }

    @Override
    public Mono<WebSession> updateLastAccessTime(WebSession webSession) {
        return delegate.updateLastAccessTime(webSession);
    }

    private void setMaxIdleTime(WebSession session) {
        session.setMaxIdleTime(this.timeout);
    }
}

此外,事实证明,只有当删除的授权客户端是用户的最后一个授权客户端时,后台通道注销才需要删除授权客户端并使会话无效:如果您使用同一客户端登录 Google 和 Facebook,则当您仅从两者之一注销时,此客户端上的会话应保持活动状态。

© www.soinside.com 2019 - 2024. All rights reserved.