此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.1! |
并发会话控制
与 Servlet 的并发会话控制类似,Spring Security 还支持限制用户在 Reactive 应用程序中可以拥有的并发会话数。
当您在 Spring Security 中设置并发会话控制时,它会通过与这些身份验证机制处理身份验证成功的方式挂钩来监控通过表单登录、OAuth 2.0 登录和 HTTP 基本身份验证执行的身份验证。
更具体地说,会话管理 DSL 将添加ConcurrentSessionControlServerAuthenticationSuccessHandler
和RegisterSessionServerAuthenticationSuccessHandler
到ServerAuthenticationSuccessHandler
由 Authentication 过滤器使用。
以下部分包含如何配置 Concurrent Sessions Control 的示例。
限制并发会话
默认情况下,Spring Security 将允许用户进行任意数量的并发会话。
要限制并发会话的数量,您可以使用maximumSessions
DSL 方法:
-
Java
-
Kotlin
@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();
}
@Bean
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
sessionManagement {
sessionConcurrency {
maximumSessions = SessionLimit.of(1)
}
}
}
}
@Bean
open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
return InMemoryReactiveSessionRegistry()
}
上述配置允许任何用户使用一个会话。
同样,您也可以通过使用SessionLimit#UNLIMITED
不断:
-
Java
-
Kotlin
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
http
// ...
.sessionManagement((sessions) -> sessions
.concurrentSessions((concurrency) -> concurrency
.maximumSessions(SessionLimit.UNLIMITED))
);
return http.build();
}
@Bean
ReactiveSessionRegistry reactiveSessionRegistry() {
return new InMemoryReactiveSessionRegistry();
}
@Bean
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
sessionManagement {
sessionConcurrency {
maximumSessions = SessionLimit.UNLIMITED
}
}
}
}
@Bean
open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
return InMemoryReactiveSessionRegistry()
}
由于maximumSessions
方法接受SessionLimit
接口,进而扩展了Function<Authentication, Mono<Integer>>
,您可以使用更复杂的逻辑来确定基于用户身份验证的最大会话数:
Authentication
-
Java
-
Kotlin
@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();
}
@Bean
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
sessionManagement {
sessionConcurrency {
maximumSessions = maxSessions()
}
}
}
}
fun maxSessions(): SessionLimit {
return { authentication ->
if (authentication.authorities.contains(SimpleGrantedAuthority("ROLE_UNLIMITED_SESSIONS"))) Mono.empty
if (authentication.authorities.contains(SimpleGrantedAuthority("ROLE_ADMIN"))) Mono.just(2)
Mono.just(1)
}
}
@Bean
open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
return InMemoryReactiveSessionRegistry()
}
当超过最大会话数时,默认情况下,最近使用最少的会话将过期。 如果要更改该行为,可以自定义在超过最大会话数时使用的策略。
例如,并发会话管理不知道某个身份提供者中是否有您可能通过 OAuth 2 登录使用的其他会话。
如果您还需要使针对身份提供商的会话失效,则必须包含你自己的 |
处理超出的最大会话数
默认情况下,当超过最大会话数时,最近使用最少的会话将使用InvalidateLeastUsedServerMaximumSessionsExceededHandler
.
Spring Security 还提供了另一种实现,该实现可以防止用户使用PreventLoginServerMaximumSessionsExceededHandler
.
如果您想使用自己的策略,则可以提供ServerMaximumSessionsExceededHandler
.
-
Java
-
Kotlin
@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();
}
@Bean
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
sessionManagement {
sessionConcurrency {
maximumSessions = SessionLimit.of(1)
maximumSessionsExceededHandler = PreventLoginMaximumSessionsExceededHandler()
}
}
}
}
@Bean
open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
return InMemoryReactiveSessionRegistry()
}
指定ReactiveSessionRegistry
为了跟踪用户的会话,Spring Security 使用ReactiveSessionRegistry
,并且每次用户登录时,都会保存其会话信息。
Spring Security 随附InMemoryReactiveSessionRegistry
实现ReactiveSessionRegistry
.
要指定ReactiveSessionRegistry
实现中,你可以将其声明为 Bean:
-
Java
-
Kotlin
@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();
}
@Bean
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
sessionManagement {
sessionConcurrency {
maximumSessions = SessionLimit.of(1)
}
}
}
}
@Bean
open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
return MyReactiveSessionRegistry()
}
或者,您可以使用sessionRegistry
DSL 方法:
-
Java
-
Kotlin
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
http
// ...
.sessionManagement((sessions) -> sessions
.concurrentSessions((concurrency) -> concurrency
.maximumSessions(SessionLimit.of(1))
.sessionRegistry(new MyReactiveSessionRegistry())
)
);
return http.build();
}
@Bean
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
sessionManagement {
sessionConcurrency {
maximumSessions = SessionLimit.of(1)
sessionRegistry = MyReactiveSessionRegistry()
}
}
}
}
使注册用户的会话失效
有时,能够使用户的全部或部分会话无效是很方便的。
例如,当用户更改其密码时,您可能希望使他们的所有会话失效,以便强制他们再次登录。
为此,您可以使用ReactiveSessionRegistry
bean 来检索所有用户的会话,使它们失效,然后它们将它们从WebSessionStore
:
-
Java
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();
}
}
为某些身份验证过滤器禁用它
默认情况下,并发会话控制将自动配置为表单登录、OAuth 2.0 登录和 HTTP 基本身份验证,只要它们未指定ServerAuthenticationSuccessHandler
他们自己。
例如,以下配置将禁用表单登录的并发会话控制:
-
Java
-
Kotlin
@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();
}
@Bean
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
formLogin {
authenticationSuccessHandler = RedirectServerAuthenticationSuccessHandler("/")
}
sessionManagement {
sessionConcurrency {
maximumSessions = SessionLimit.of(1)
}
}
}
}
在不禁用并发会话控制的情况下添加其他成功处理程序
您还可以添加其他ServerAuthenticationSuccessHandler
实例添加到身份验证筛选器使用的处理程序列表中,而无需禁用 Concurrent Sessions Control。
为此,您可以使用authenticationSuccessHandler(Consumer<List<ServerAuthenticationSuccessHandler>>)
方法:
-
Java
@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();
}
检查示例应用程序
您可以在此处查看示例应用程序。