Update Max Sessions on WebFlux
Delete WebSessionStoreReactiveSessionRegistry.java and gives the responsibility to remove the sessions from the WebSessionStore to the handler Issue gh-6192
This commit is contained in:
+240
-247
@@ -28,246 +28,6 @@ Java::
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.sessionManagement((sessions) -> sessions
|
||||
.concurrentSessions((concurrency) -> concurrency
|
||||
.maximumSessions(SessionLimit.of(1))
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveSessionRegistry reactiveSessionRegistry(WebSessionManager webSessionManager) {
|
||||
return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.of(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@Bean
|
||||
open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
|
||||
return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The above configuration allows one session for any user.
|
||||
Similarly, you can also allow unlimited sessions by using the `SessionLimit#UNLIMITED` constant:
|
||||
|
||||
.Configuring unlimited sessions
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.sessionManagement((sessions) -> sessions
|
||||
.concurrentSessions((concurrency) -> concurrency
|
||||
.maximumSessions(SessionLimit.UNLIMITED))
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveSessionRegistry reactiveSessionRegistry(WebSessionManager webSessionManager) {
|
||||
return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.UNLIMITED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@Bean
|
||||
open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
|
||||
return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Since the `maximumSessions` method accepts a `SessionLimit` interface, which in turn extends `Function<Authentication, Mono<Integer>>`, you can have a more complex logic to determine the maximum number of sessions based on the user's authentication:
|
||||
|
||||
.Configuring maximumSessions based on `Authentication`
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@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(WebSessionManager webSessionManager) {
|
||||
return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@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(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
|
||||
return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
When the maximum number of sessions is exceeded, by default, the least recently used session(s) will be expired.
|
||||
If you want to change that behavior, you can <<concurrent-sessions-control-custom-strategy,customize the strategy used when the maximum number of sessions is exceeded>>.
|
||||
|
||||
[[concurrent-sessions-control-custom-strategy]]
|
||||
== Handling Maximum Number of Sessions Exceeded
|
||||
|
||||
By default, when the maximum number of sessions is exceeded, the least recently used session(s) will be expired by using the {security-api-url}org/springframework/security/web/server/authentication/session/InvalidateLeastUsedMaximumSessionsExceededHandler.html[InvalidateLeastUsedMaximumSessionsExceededHandler].
|
||||
Spring Security also provides another implementation that prevents the user from creating new sessions by using the {security-api-url}org/springframework/security/web/server/authentication/session/PreventLoginMaximumSessionsExceededHandler.html[PreventLoginMaximumSessionsExceededHandler].
|
||||
If you want to use your own strategy, you can provide a different implementation of {security-api-url}org/springframework/security/web/server/authentication/session/ServerMaximumSessionsExceededHandler.html[ServerMaximumSessionsExceededHandler].
|
||||
|
||||
.Configuring maximumSessionsExceededHandler
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@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(WebSessionManager webSessionManager) {
|
||||
return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.of(1)
|
||||
maximumSessionsExceededHandler = PreventLoginMaximumSessionsExceededHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
|
||||
return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[reactive-concurrent-sessions-control-specify-session-registry]]
|
||||
== Specifying a `ReactiveSessionRegistry`
|
||||
|
||||
In order to keep track of the user's sessions, Spring Security uses a {security-api-url}org/springframework/security/core/session/ReactiveSessionRegistry.html[ReactiveSessionRegistry], and, every time a user logs in, their session information is saved.
|
||||
Typically, in a Spring WebFlux application, you will use the {security-api-url}/org/springframework/security/web/session/WebSessionStoreReactiveSessionRegistry.html[WebSessionStoreReactiveSessionRegistry] which makes sure that the `WebSession` is invalidated whenever the `ReactiveSessionInformation` is invalidated.
|
||||
|
||||
Spring Security ships with {security-api-url}/org/springframework/security/web/session/WebSessionStoreReactiveSessionRegistry.html[WebSessionStoreReactiveSessionRegistry] and {security-api-url}org/springframework/security/core/session/InMemoryReactiveSessionRegistry.html[InMemoryReactiveSessionRegistry] implementations of `ReactiveSessionRegistry`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
When creating the `WebSessionStoreReactiveSessionRegistry`, you need to provide the `WebSessionStore` that is being used by your application.
|
||||
If you are using Spring WebFlux, you can use the `WebSessionManager` bean (which is usually an instance of `DefaultWebSessionManager`) to get the `WebSessionStore`.
|
||||
====
|
||||
|
||||
To specify a `ReactiveSessionRegistry` implementation you can either declare it as a bean:
|
||||
|
||||
.ReactiveSessionRegistry as a Bean
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
@@ -300,6 +60,123 @@ open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
}
|
||||
}
|
||||
}
|
||||
@Bean
|
||||
open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
|
||||
return InMemoryReactiveSessionRegistry()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The above configuration allows one session for any user.
|
||||
Similarly, you can also allow unlimited sessions by using the `SessionLimit#UNLIMITED` constant:
|
||||
|
||||
.Configuring unlimited sessions
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.sessionManagement((sessions) -> sessions
|
||||
.concurrentSessions((concurrency) -> concurrency
|
||||
.maximumSessions(SessionLimit.UNLIMITED))
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveSessionRegistry reactiveSessionRegistry() {
|
||||
return new InMemoryReactiveSessionRegistry();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.UNLIMITED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@Bean
|
||||
open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
|
||||
return InMemoryReactiveSessionRegistry()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Since the `maximumSessions` method accepts a `SessionLimit` interface, which in turn extends `Function<Authentication, Mono<Integer>>`, you can have a more complex logic to determine the maximum number of sessions based on the user's authentication:
|
||||
|
||||
.Configuring maximumSessions based on `Authentication`
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@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();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@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 {
|
||||
@@ -308,6 +185,123 @@ open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
|
||||
----
|
||||
======
|
||||
|
||||
When the maximum number of sessions is exceeded, by default, the least recently used session(s) will be expired.
|
||||
If you want to change that behavior, you can <<concurrent-sessions-control-custom-strategy,customize the strategy used when the maximum number of sessions is exceeded>>.
|
||||
|
||||
[[concurrent-sessions-control-custom-strategy]]
|
||||
== Handling Maximum Number of Sessions Exceeded
|
||||
|
||||
By default, when the maximum number of sessions is exceeded, the least recently used session(s) will be expired by using the {security-api-url}org/springframework/security/web/server/authentication/session/InvalidateLeastUsedMaximumSessionsExceededHandler.html[InvalidateLeastUsedMaximumSessionsExceededHandler].
|
||||
Spring Security also provides another implementation that prevents the user from creating new sessions by using the {security-api-url}org/springframework/security/web/server/authentication/session/PreventLoginMaximumSessionsExceededHandler.html[PreventLoginMaximumSessionsExceededHandler].
|
||||
If you want to use your own strategy, you can provide a different implementation of {security-api-url}org/springframework/security/web/server/authentication/session/ServerMaximumSessionsExceededHandler.html[ServerMaximumSessionsExceededHandler].
|
||||
|
||||
.Configuring maximumSessionsExceededHandler
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@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();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.of(1)
|
||||
maximumSessionsExceededHandler = PreventLoginMaximumSessionsExceededHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
|
||||
return InMemoryReactiveSessionRegistry()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[reactive-concurrent-sessions-control-specify-session-registry]]
|
||||
== Specifying a `ReactiveSessionRegistry`
|
||||
|
||||
In order to keep track of the user's sessions, Spring Security uses a {security-api-url}org/springframework/security/core/session/ReactiveSessionRegistry.html[ReactiveSessionRegistry], and, every time a user logs in, their session information is saved.
|
||||
|
||||
Spring Security ships with {security-api-url}org/springframework/security/core/session/InMemoryReactiveSessionRegistry.html[InMemoryReactiveSessionRegistry] implementation of `ReactiveSessionRegistry`.
|
||||
|
||||
To specify a `ReactiveSessionRegistry` implementation you can either declare it as a bean:
|
||||
|
||||
.ReactiveSessionRegistry as a Bean
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@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();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.of(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
|
||||
return MyReactiveSessionRegistry()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
or you can use the `sessionRegistry` DSL method:
|
||||
|
||||
.ReactiveSessionRegistry using sessionRegistry DSL method
|
||||
@@ -324,7 +318,7 @@ SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
|
||||
.sessionManagement((sessions) -> sessions
|
||||
.concurrentSessions((concurrency) -> concurrency
|
||||
.maximumSessions(SessionLimit.of(1))
|
||||
.sessionRegistry(new InMemoryReactiveSessionRegistry())
|
||||
.sessionRegistry(new MyReactiveSessionRegistry())
|
||||
)
|
||||
);
|
||||
return http.build();
|
||||
@@ -342,7 +336,7 @@ open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions = SessionLimit.of(1)
|
||||
sessionRegistry = InMemoryReactiveSessionRegistry()
|
||||
sessionRegistry = MyReactiveSessionRegistry()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,7 +349,7 @@ open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
|
||||
At times, it is handy to be able to invalidate all or some of a user's sessions.
|
||||
For example, when a user changes their password, you may want to invalidate all of their sessions so that they are forced to log in again.
|
||||
To do that, you can use the `ReactiveSessionRegistry` bean to retrieve all the user's sessions and then invalidate them:
|
||||
To do that, you can use the `ReactiveSessionRegistry` bean to retrieve all the user's sessions, invalidate them, and them remove them from the `WebSessionStore`:
|
||||
|
||||
.Using ReactiveSessionRegistry to invalidate sessions manually
|
||||
[tabs]
|
||||
@@ -367,13 +361,12 @@ Java::
|
||||
public class SessionControl {
|
||||
private final ReactiveSessionRegistry reactiveSessionRegistry;
|
||||
|
||||
public SessionControl(ReactiveSessionRegistry reactiveSessionRegistry) {
|
||||
this.reactiveSessionRegistry = reactiveSessionRegistry;
|
||||
}
|
||||
private final WebSessionStore webSessionStore;
|
||||
|
||||
public Mono<Void> invalidateSessions(String username) {
|
||||
return this.reactiveSessionRegistry.getAllSessions(username)
|
||||
.flatMap(ReactiveSessionInformation::invalidate)
|
||||
.flatMap((session) -> session.invalidate().thenReturn(session))
|
||||
.flatMap((session) -> this.webSessionStore.removeSession(session.getSessionId()))
|
||||
.then();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user