跳到主要内容

并发会话控制

QWen Max 中英对照 Concurrent Sessions Control

类似于 Servlet 的并发会话控制,Spring Security 也提供了支持来限制用户在 Reactive 应用程序中可以拥有的并发会话数量。

当你在Spring Security中设置并发会话控制时,它通过挂接到表单登录、OAuth 2.0 登录和HTTP Basic认证这些认证机制处理认证成功的方式来监控认证。更具体地说,会话管理DSL会将ConcurrentSessionControlServerAuthenticationSuccessHandlerRegisterSessionServerAuthenticationSuccessHandler添加到认证过滤器使用的ServerAuthenticationSuccessHandler列表中。

以下部分包含如何配置并发会话控制的示例。

限制并发会话

默认情况下,Spring Security 将允许用户拥有任意数量的并发会话。要限制并发会话的数量,可以使用 maximumSessions DSL 方法:

@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
http
// ...
.sessionManagement((sessions) -> sessions
.concurrentSessions((concurrency) -> concurrency
.maximumSessions(SessionLimit.of(1))
)
);
return http.build();
}

@Bean
ReactiveSessionRegistry reactiveSessionRegistry() {
return new InMemoryReactiveSessionRegistry();
}
java

上述配置允许任何用户一个会话。类似地,您也可以通过使用 SessionLimit#UNLIMITED 常量来允许无限制的会话:

@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
http
// ...
.sessionManagement((sessions) -> sessions
.concurrentSessions((concurrency) -> concurrency
.maximumSessions(SessionLimit.UNLIMITED))
);
return http.build();
}

@Bean
ReactiveSessionRegistry reactiveSessionRegistry() {
return new InMemoryReactiveSessionRegistry();
}
java

由于 maximumSessions 方法接受一个 SessionLimit 接口,而该接口又扩展了 Function<Authentication, Mono<Integer>>,因此你可以有一个更复杂的逻辑来根据用户的认证确定最大会话数:

@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
http
// ...
.sessionManagement((sessions) -> sessions
.concurrentSessions((concurrency) -> concurrency
.maximumSessions(maxSessions()))
);
return http.build();
}

private SessionLimit maxSessions() {
return (authentication) -> {
if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_UNLIMITED_SESSIONS"))) {
return Mono.empty(); // allow unlimited sessions for users with ROLE_UNLIMITED_SESSIONS
}
if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) {
return Mono.just(2); // allow two sessions for admins
}
return Mono.just(1); // allow one session for every other user
};
}

@Bean
ReactiveSessionRegistry reactiveSessionRegistry() {
return new InMemoryReactiveSessionRegistry();
}
java

当会话数量超过最大值时,默认情况下,最近最少使用的会话将被失效。如果您想更改此行为,可以自定义超过最大会话数量时使用的策略

important

并发会话管理并不知道在某些身份提供商(例如通过 OAuth 2 登录 使用的身份提供商)中是否存在另一个会话。如果你还需要使身份提供商的会话失效,必须包含你自己的 ServerMaximumSessionsExceededHandler 实现

处理超过最大会话数

默认情况下,当会话数量超过最大值时,将使用 InvalidateLeastUsedServerMaximumSessionsExceededHandler 使最近最少使用的会话过期。Spring Security 还提供了另一种实现,通过使用 PreventLoginServerMaximumSessionsExceededHandler 阻止用户创建新的会话。如果你想使用自己的策略,可以提供一个不同的 ServerMaximumSessionsExceededHandler 实现。

@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
http
// ...
.sessionManagement((sessions) -> sessions
.concurrentSessions((concurrency) -> concurrency
.maximumSessions(SessionLimit.of(1))
.maximumSessionsExceededHandler(new PreventLoginMaximumSessionsExceededHandler())
)
);
return http.build();
}

@Bean
ReactiveSessionRegistry reactiveSessionRegistry() {
return new InMemoryReactiveSessionRegistry();
}
java

指定 ReactiveSessionRegistry

为了跟踪用户的会话,Spring Security 使用了一个 ReactiveSessionRegistry,并且每次用户登录时,他们的会话信息都会被保存。

Spring Security 提供了 InMemoryReactiveSessionRegistry 实现的 ReactiveSessionRegistry

要指定一个 ReactiveSessionRegistry 实现,你可以将其声明为一个 bean:

@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
http
// ...
.sessionManagement((sessions) -> sessions
.concurrentSessions((concurrency) -> concurrency
.maximumSessions(SessionLimit.of(1))
)
);
return http.build();
}

@Bean
ReactiveSessionRegistry reactiveSessionRegistry() {
return new MyReactiveSessionRegistry();
}
java

或者你可以使用 sessionRegistry DSL 方法:

@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
http
// ...
.sessionManagement((sessions) -> sessions
.concurrentSessions((concurrency) -> concurrency
.maximumSessions(SessionLimit.of(1))
.sessionRegistry(new MyReactiveSessionRegistry())
)
);
return http.build();
}
java

使注册用户的会话失效

有时,能够使用户的全部或部分会话失效是非常有用的。例如,当用户更改密码时,您可能希望使他们的所有会话失效,从而迫使他们再次登录。为此,您可以使用 ReactiveSessionRegistry bean 检索用户的全部会话,使它们失效,然后将它们从 WebSessionStore 中移除:

public class SessionControl {
private final ReactiveSessionRegistry reactiveSessionRegistry;

private final WebSessionStore webSessionStore;

public Mono<Void> invalidateSessions(String username) {
return this.reactiveSessionRegistry.getAllSessions(username)
.flatMap((session) -> session.invalidate().thenReturn(session))
.flatMap((session) -> this.webSessionStore.removeSession(session.getSessionId()))
.then();
}
}
java

禁用某些身份验证过滤器

默认情况下,只要表单登录、OAuth 2.0 登录和 HTTP Basic 身份验证没有自己指定 ServerAuthenticationSuccessHandler,就会自动配置并发会话控制。例如,以下配置将禁用表单登录的并发会话控制:

@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
http
// ...
.formLogin((login) -> login
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/"))
)
.sessionManagement((sessions) -> sessions
.concurrentSessions((concurrency) -> concurrency
.maximumSessions(SessionLimit.of(1))
)
);
return http.build();
}
java

添加额外的成功处理程序而不禁用并发会话控制

您还可以在不禁用并发会话控制的情况下,将其他 ServerAuthenticationSuccessHandler 实例添加到身份验证过滤器使用的处理程序列表中。为此,您可以使用 authenticationSuccessHandler(Consumer<List<ServerAuthenticationSuccessHandler>>) 方法:

@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
http
// ...
.formLogin((login) -> login
.authenticationSuccessHandler((handlers) -> handlers.add(new MyAuthenticationSuccessHandler()))
)
.sessionManagement((sessions) -> sessions
.concurrentSessions((concurrency) -> concurrency
.maximumSessions(SessionLimit.of(1))
)
);
return http.build();
}
java

检查示例应用程序

你可以在这里检查示例应用程序