並行工作階段控制

類似於 Servlet 的並行工作階段控制,Spring Security 也提供了限制使用者在反應式應用程式中可擁有的並行工作階段數量的支援。

當您在 Spring Security 中設定並行工作階段控制時,它會監控透過表單登入、OAuth 2.0 登入和 HTTP Basic 驗證執行的身份驗證,方法是掛鉤到這些驗證機制處理驗證成功的方式。更具體地說,工作階段管理 DSL 會將 ConcurrentSessionControlServerAuthenticationSuccessHandlerRegisterSessionServerAuthenticationSuccessHandler 新增到驗證過濾器使用的 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 介面,而 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 登入 使用某些身份提供者,則並行工作階段管理不會意識到其中是否有另一個工作階段。如果您還需要使針對身份提供者的工作階段失效,則必須包含您自己的 ServerMaximumSessionsExceededHandler 實作

處理超過最大工作階段數量

預設情況下,當超過最大工作階段數量時,將使用 InvalidateLeastUsedMaximumSessionsExceededHandler 使最近最少使用的工作階段過期。Spring Security 也提供了另一個實作,透過使用 PreventLoginMaximumSessionsExceededHandler 來防止使用者建立新的工作階段。如果您想要使用自己的策略,則可以提供 ServerMaximumSessionsExceededHandler 的不同實作。

組態 maximumSessionsExceededHandler
  • 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 隨附了 ReactiveSessionRegistryInMemoryReactiveSessionRegistry 實作。

若要指定 ReactiveSessionRegistry 實作,您可以將其宣告為 bean

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 方法

使用 sessionRegistry DSL 方法的 ReactiveSessionRegistry
  • 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 中移除

使用 ReactiveSessionRegistry 手動使工作階段失效
  • 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();
}

檢查範例應用程式