Polish SpringSessionRememberMeServices
* Move to ~.security.web.authentication package * Add documentation * Use getBeanNamesForType to avoid eager bean initialization * Remove rememberMeCookieMaxAge because it must be Integer.MAX_VALUE since cookie is only written at the creation of the session * Change from parameter to rememberMeParameterName Issue gh-189
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
|
||||
= Spring Session
|
||||
Rob Winch, Vedran Pavić, Jakub Kubrynski
|
||||
:doctype: book
|
||||
@@ -518,17 +519,49 @@ Before using WebSocket integration, you should be sure that you have <<httpsessi
|
||||
|
||||
include::guides/websocket.adoc[tags=config,leveloffset=+2]
|
||||
|
||||
[[spring-security-concurrent-sessions]]
|
||||
[[spring-security]]
|
||||
== Spring Security Integration
|
||||
|
||||
Spring Session provides integration with Spring Security.
|
||||
|
||||
[[spring-security-rememberme]]
|
||||
=== Spring Security Remember-Me Support
|
||||
|
||||
Spring Session provides integration with http://docs.spring.io/spring-security/site/docs/4.2.x/reference/htmlsingle/#remember-me[Spring Security's Remember-Me Authentication].
|
||||
The support will:
|
||||
|
||||
* Change the session expiration length
|
||||
* Ensure the session cookie expires at `Integer.MAX_VALUE`.
|
||||
The cookie expiration is set to the largest possible value because the cookie is only set when the session is created.
|
||||
If it were set to the same value as the session expiration, then the session would get renewed when the user used it but the cookie expiration would not be updated causing the expiration to be fixed.
|
||||
|
||||
To configure Spring Session with Spring Security in Java Configuration use the following as a guide:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{docs-test-dir}docs/security/RememberMeSecurityConfiguration.java[tags=http-rememberme]
|
||||
}
|
||||
|
||||
include::{docs-test-dir}docs/security/RememberMeSecurityConfiguration.java[tags=rememberme-bean]
|
||||
----
|
||||
|
||||
An XML based configuration would look something like this:
|
||||
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{docs-test-resources-dir}docs/security/RememberMeSecurityConfigurationXmlTests-context.xml[tags=config]
|
||||
----
|
||||
|
||||
|
||||
[[spring-security-concurrent-sessions]]
|
||||
=== Spring Security Concurrent Session Control
|
||||
|
||||
|
||||
Spring Session provides integration with Spring Security to support its concurrent session control.
|
||||
This allows limiting the number of active sessions that a single user can have concurrently, but unlike the default
|
||||
Spring Security support this will also work in a clustered environment. This is done by providing a custom
|
||||
implementation of Spring Security's `SessionRegistry` interface.
|
||||
|
||||
[[spring-security-concurrent-sessions-how]]
|
||||
=== Configuring Spring Security's concurrent session management
|
||||
|
||||
When using Spring Security's Java config DSL, you can configure the custom `SessionRegistry` through the
|
||||
`SessionManagementConfigurer` like this:
|
||||
[source,java,indent=0]
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package docs.security;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.authentication.RememberMeServices;
|
||||
import org.springframework.session.MapSessionRepository;
|
||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
|
||||
|
||||
/**
|
||||
* @author rwinch
|
||||
*/
|
||||
@EnableWebSecurity
|
||||
@EnableSpringHttpSession
|
||||
public class RememberMeSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
// @formatter:off
|
||||
// tag::http-rememberme[]
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// ... additional configuration ...
|
||||
.rememberMe()
|
||||
.rememberMeServices(rememberMeServices());
|
||||
// end::http-rememberme[]
|
||||
|
||||
http
|
||||
.formLogin().and()
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated();
|
||||
}
|
||||
|
||||
// tag::rememberme-bean[]
|
||||
@Bean
|
||||
RememberMeServices rememberMeServices() {
|
||||
SpringSessionRememberMeServices rememberMeServices =
|
||||
new SpringSessionRememberMeServices();
|
||||
// optionally customize
|
||||
rememberMeServices.setAlwaysRemember(true);
|
||||
return rememberMeServices;
|
||||
}
|
||||
// end::rememberme-bean[]
|
||||
// @formatter:on
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public InMemoryUserDetailsManager userDetailsService() {
|
||||
InMemoryUserDetailsManager uds = new InMemoryUserDetailsManager();
|
||||
uds.createUser(
|
||||
User.withUsername("user").password("password").roles("USER").build());
|
||||
return uds;
|
||||
}
|
||||
|
||||
@Bean
|
||||
MapSessionRepository sessionRepository() {
|
||||
return new MapSessionRepository();
|
||||
}
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package docs.security;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
|
||||
/**
|
||||
* @author rwinch
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = RememberMeSecurityConfiguration.class)
|
||||
@WebAppConfiguration
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class RememberMeSecurityConfigurationTests<T extends ExpiringSession> {
|
||||
@Autowired
|
||||
WebApplicationContext context;
|
||||
@Autowired
|
||||
SessionRepositoryFilter springSessionRepositoryFilter;
|
||||
@Autowired
|
||||
SessionRepository<T> sessions;
|
||||
|
||||
MockMvc mockMvc;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
// @formatter:off
|
||||
this.mockMvc = MockMvcBuilders
|
||||
.webAppContextSetup(this.context)
|
||||
.addFilters(this.springSessionRepositoryFilter)
|
||||
.apply(springSecurity())
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenSpringSessionRememberMeEnabledThenCookieMaxAgeAndSessionExpirationSet()
|
||||
throws Exception {
|
||||
// @formatter:off
|
||||
MvcResult result = this.mockMvc
|
||||
.perform(formLogin())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
|
||||
Cookie cookie = result.getResponse().getCookie("SESSION");
|
||||
assertThat(cookie.getMaxAge()).isEqualTo(Integer.MAX_VALUE);
|
||||
T session = this.sessions.getSession(cookie.getValue());
|
||||
assertThat(session.getMaxInactiveIntervalInSeconds())
|
||||
.isEqualTo((int) TimeUnit.DAYS.toSeconds(30));
|
||||
|
||||
}
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package docs.security;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
|
||||
/**
|
||||
* @author rwinch
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class RememberMeSecurityConfigurationXmlTests<T extends ExpiringSession> {
|
||||
@Autowired
|
||||
WebApplicationContext context;
|
||||
@Autowired
|
||||
SessionRepositoryFilter springSessionRepositoryFilter;
|
||||
@Autowired
|
||||
SessionRepository<T> sessions;
|
||||
|
||||
MockMvc mockMvc;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
// @formatter:off
|
||||
this.mockMvc = MockMvcBuilders
|
||||
.webAppContextSetup(this.context)
|
||||
.addFilters(this.springSessionRepositoryFilter)
|
||||
.apply(springSecurity())
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenSpringSessionRememberMeEnabledThenCookieMaxAgeAndSessionExpirationSet()
|
||||
throws Exception {
|
||||
// @formatter:off
|
||||
MvcResult result = this.mockMvc
|
||||
.perform(formLogin())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
|
||||
Cookie cookie = result.getResponse().getCookie("SESSION");
|
||||
assertThat(cookie.getMaxAge()).isEqualTo(Integer.MAX_VALUE);
|
||||
T session = this.sessions.getSession(cookie.getValue());
|
||||
assertThat(session.getMaxInactiveIntervalInSeconds())
|
||||
.isEqualTo((int) TimeUnit.DAYS.toSeconds(30));
|
||||
|
||||
}
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:security="http://www.springframework.org/schema/security"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!-- tag::config[] -->
|
||||
<security:http>
|
||||
<!-- ... -->
|
||||
<security:form-login />
|
||||
<security:remember-me services-ref="rememberMeServices"/>
|
||||
</security:http>
|
||||
|
||||
<bean id="rememberMeServices"
|
||||
class="org.springframework.session.security.web.authentication.SpringSessionRememberMeServices"
|
||||
p:alwaysRemember="true"/>
|
||||
<!-- end::config[] -->
|
||||
|
||||
|
||||
<security:user-service>
|
||||
<security:user name="user" password="password" authorities="ROLE_USER"/>
|
||||
</security:user-service>
|
||||
|
||||
<bean class="org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration"/>
|
||||
<bean id="springSessionRepository" class="org.springframework.session.MapSessionRepository"/>
|
||||
</beans>
|
||||
Reference in New Issue
Block a user