Polish SessionLimit
- Move to the web.authentication.session package since it is only needed by web.authentication.session elements and does not access any other web element itself. - Add Kotlin support - Add documentation Issue gh-16206
This commit is contained in:
parent
1864577e98
commit
1104b45832
|
@ -47,6 +47,7 @@ import org.springframework.security.web.authentication.session.NullAuthenticated
|
|||
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
|
||||
import org.springframework.security.web.authentication.session.SessionLimit;
|
||||
import org.springframework.security.web.context.DelegatingSecurityContextRepository;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
import org.springframework.security.web.context.NullSecurityContextRepository;
|
||||
|
@ -59,7 +60,6 @@ import org.springframework.security.web.session.DisableEncodeUrlFilter;
|
|||
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
|
||||
import org.springframework.security.web.session.InvalidSessionStrategy;
|
||||
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
|
||||
import org.springframework.security.web.session.SessionLimit;
|
||||
import org.springframework.security.web.session.SessionManagementFilter;
|
||||
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
|
||||
import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy;
|
||||
|
|
|
@ -19,7 +19,9 @@ package org.springframework.security.config.annotation.web.session
|
|||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
|
||||
import org.springframework.security.core.session.SessionRegistry
|
||||
import org.springframework.security.web.authentication.session.SessionLimit
|
||||
import org.springframework.security.web.session.SessionInformationExpiredStrategy
|
||||
import org.springframework.util.Assert
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the behaviour of multiple sessions using idiomatic
|
||||
|
@ -44,12 +46,21 @@ class SessionConcurrencyDsl {
|
|||
var expiredSessionStrategy: SessionInformationExpiredStrategy? = null
|
||||
var maxSessionsPreventsLogin: Boolean? = null
|
||||
var sessionRegistry: SessionRegistry? = null
|
||||
private var sessionLimit: SessionLimit? = null
|
||||
|
||||
fun maximumSessions(max: SessionLimit) {
|
||||
this.sessionLimit = max
|
||||
}
|
||||
|
||||
internal fun get(): (SessionManagementConfigurer<HttpSecurity>.ConcurrencyControlConfigurer) -> Unit {
|
||||
Assert.isTrue(maximumSessions == null || sessionLimit == null, "You cannot specify maximumSessions as both an Int and a SessionLimit. Please use only one.")
|
||||
return { sessionConcurrencyControl ->
|
||||
maximumSessions?.also {
|
||||
sessionConcurrencyControl.maximumSessions(maximumSessions!!)
|
||||
}
|
||||
sessionLimit?.also {
|
||||
sessionConcurrencyControl.maximumSessions(sessionLimit!!)
|
||||
}
|
||||
expiredUrl?.also {
|
||||
sessionConcurrencyControl.expiredUrl(expiredUrl)
|
||||
}
|
||||
|
|
|
@ -59,12 +59,12 @@ import org.springframework.security.web.authentication.session.ChangeSessionIdAu
|
|||
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
|
||||
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
|
||||
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
|
||||
import org.springframework.security.web.authentication.session.SessionLimit;
|
||||
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
|
||||
import org.springframework.security.web.context.SecurityContextRepository;
|
||||
import org.springframework.security.web.savedrequest.RequestCache;
|
||||
import org.springframework.security.web.session.ConcurrentSessionFilter;
|
||||
import org.springframework.security.web.session.HttpSessionDestroyedEvent;
|
||||
import org.springframework.security.web.session.SessionLimit;
|
||||
import org.springframework.security.web.session.SessionManagementFilter;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
|
|
@ -35,7 +35,7 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException;
|
|||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.session.SessionLimit;
|
||||
import org.springframework.security.web.authentication.session.SessionLimit;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
|
|
|
@ -18,18 +18,19 @@ package org.springframework.security.config.annotation.web.session
|
|||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkObject
|
||||
import java.util.Date
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.mock.web.MockHttpSession
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager
|
||||
import org.springframework.security.authorization.AuthorizationManager
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.config.test.SpringTestContext
|
||||
import org.springframework.security.config.test.SpringTestContextExtension
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.core.session.SessionInformation
|
||||
import org.springframework.security.core.session.SessionRegistry
|
||||
import org.springframework.security.core.session.SessionRegistryImpl
|
||||
|
@ -44,6 +45,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
|||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Tests for [SessionConcurrencyDsl]
|
||||
|
@ -173,16 +175,75 @@ class SessionConcurrencyDslTests {
|
|||
open fun sessionRegistry(): SessionRegistry = SESSION_REGISTRY
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `session concurrency when session limit then no more sessions allowed`() {
|
||||
this.spring.register(MaximumSessionsFunctionConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
|
||||
this.mockMvc.perform(post("/login")
|
||||
.with(csrf())
|
||||
.param("username", "user")
|
||||
.param("password", "password"))
|
||||
|
||||
this.mockMvc.perform(post("/login")
|
||||
.with(csrf())
|
||||
.param("username", "user")
|
||||
.param("password", "password"))
|
||||
.andExpect(status().isFound)
|
||||
.andExpect(redirectedUrl("/login?error"))
|
||||
|
||||
this.mockMvc.perform(post("/login")
|
||||
.with(csrf())
|
||||
.param("username", "admin")
|
||||
.param("password", "password"))
|
||||
.andExpect(status().isFound)
|
||||
.andExpect(redirectedUrl("/"))
|
||||
|
||||
this.mockMvc.perform(post("/login")
|
||||
.with(csrf())
|
||||
.param("username", "admin")
|
||||
.param("password", "password"))
|
||||
.andExpect(status().isFound)
|
||||
.andExpect(redirectedUrl("/"))
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class MaximumSessionsFunctionConfig {
|
||||
|
||||
@Bean
|
||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
val isAdmin: AuthorizationManager<Any> = AuthorityAuthorizationManager.hasRole("ADMIN")
|
||||
http {
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions {
|
||||
authentication -> if (isAdmin.authorize({ authentication }, null)!!.isGranted) -1 else 1
|
||||
}
|
||||
maxSessionsPreventsLogin = true
|
||||
}
|
||||
}
|
||||
formLogin { }
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
open class UserDetailsConfig {
|
||||
@Bean
|
||||
open fun userDetailsService(): UserDetailsService {
|
||||
val userDetails = User.withDefaultPasswordEncoder()
|
||||
val user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
return InMemoryUserDetailsManager(userDetails)
|
||||
val admin = User.withDefaultPasswordEncoder()
|
||||
.username("admin")
|
||||
.password("password")
|
||||
.roles("ADMIN")
|
||||
.build()
|
||||
return InMemoryUserDetailsManager(user, admin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -399,7 +399,62 @@ XML::
|
|||
|
||||
This will prevent a user from logging in multiple times - a second login will cause the first to be invalidated.
|
||||
|
||||
Using Spring Boot, you can test the above configuration scenario the following way:
|
||||
You can also adjust this based on who the user is.
|
||||
For example, administrators may be able to have more than one session:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
AuthorizationManager<?> isAdmin = AuthorityAuthorizationManager.hasRole("ADMIN");
|
||||
http
|
||||
.sessionManagement(session -> session
|
||||
.maximumSessions((authentication) -> isAdmin.authorize(() -> authentication, null).isGranted() ? -1 : 1)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
val isAdmin: AuthorizationManager<*> = AuthorityAuthorizationManager.hasRole("ADMIN")
|
||||
http {
|
||||
sessionManagement {
|
||||
sessionConcurrency {
|
||||
maximumSessions {
|
||||
authentication -> if (isAdmin.authorize({ authentication }, null)!!.isGranted) -1 else 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
----
|
||||
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
...
|
||||
<session-management>
|
||||
<concurrency-control max-sessions-ref="sessionLimit" />
|
||||
</session-management>
|
||||
</http>
|
||||
|
||||
<b:bean id="sessionLimit" class="my.SessionLimitImplementation"/>
|
||||
----
|
||||
======
|
||||
|
||||
Using Spring Boot, you can test the above configurations in the following way:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
|
|
@ -33,7 +33,6 @@ import org.springframework.security.core.session.SessionRegistry;
|
|||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.session.ConcurrentSessionFilter;
|
||||
import org.springframework.security.web.session.SessionLimit;
|
||||
import org.springframework.security.web.session.SessionManagementFilter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.web.session;
|
||||
package org.springframework.security.web.authentication.session;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
|
@ -34,7 +34,6 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
|
|||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.security.core.session.SessionRegistry;
|
||||
import org.springframework.security.web.session.SessionLimit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.web.session;
|
||||
package org.springframework.security.web.authentication.session;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
Loading…
Reference in New Issue