併發會話控制
與 Servlet 的併發會話控制 類似,Spring Security 也提供了在響應式應用中限制使用者併發會話數量的支援。
在 Spring Security 中設定併發會話控制時,它會透過介入表單登入 (OAuth 2.0 登入) 和 HTTP Basic 認證處理認證成功的方式來監控這些認證。更具體地說,會話管理 DSL 會將 ConcurrentSessionControlServerAuthenticationSuccessHandler
和 RegisterSessionServerAuthenticationSuccessHandler
新增到認證過濾器使用的 ServerAuthenticationSuccessHandler
列表。
以下各節包含如何配置併發會話控制的示例。
限制併發會話
預設情況下,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
配置 maximumSessions-
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 提供了 ReactiveSessionRegistry
的實現 InMemoryReactiveSessionRegistry
。
要指定 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 Basic 認證本身沒有指定 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
例項新增到認證過濾器使用的處理器列表中。為此,您可以使用 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();
}
檢視示例應用
您可以在此處檢視示例應用。