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:
Rob Winch
2016-11-22 20:46:06 -06:00
parent 3b8258f233
commit 49e3a1c7cd
12 changed files with 393 additions and 104 deletions

View File

@@ -33,7 +33,7 @@ import org.springframework.session.ExpiringSession;
import org.springframework.session.SessionRepository;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.session.security.SpringSessionRememberMeServices;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
import org.springframework.session.web.http.CookieHttpSessionStrategy;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
@@ -42,6 +42,7 @@ import org.springframework.session.web.http.MultiHttpSessionStrategy;
import org.springframework.session.web.http.SessionEventHttpSessionListenerAdapter;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Configures the basics for setting up Spring Session in a web environment. In order to
@@ -138,10 +139,12 @@ public class SpringHttpSessionConfiguration implements ApplicationContextAware {
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
if (ClassUtils.isPresent("org.springframework.security.web.authentication." +
"RememberMeServices", null)) {
this.usesSpringSessionRememberMeServices = !applicationContext
.getBeansOfType(SpringSessionRememberMeServices.class).isEmpty();
if (ClassUtils.isPresent(
"org.springframework.security.web.authentication.RememberMeServices",
null)) {
this.usesSpringSessionRememberMeServices = !ObjectUtils
.isEmpty(applicationContext
.getBeanNamesForType(SpringSessionRememberMeServices.class));
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.session.security;
package org.springframework.session.security.web.authentication;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -25,6 +25,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.util.Assert;
/**
@@ -34,7 +35,8 @@ import org.springframework.util.Assert;
* @author Vedran Pavic
* @since 1.3.0
*/
public class SpringSessionRememberMeServices implements RememberMeServices {
public class SpringSessionRememberMeServices
implements RememberMeServices, LogoutHandler {
/**
* Remember-me login request attribute name.
@@ -42,13 +44,14 @@ public class SpringSessionRememberMeServices implements RememberMeServices {
public static final String REMEMBER_ME_LOGIN_ATTR = SpringSessionRememberMeServices.class
.getName() + "REMEMBER_ME_LOGIN_ATTR";
private static final String DEFAULT_PARAMETER = "remember-me";
private static final String DEFAULT_REMEMBERME_PARAMETER = "remember-me";
private static final int THIRTY_DAYS_SECONDS = 2592000;
private static final Log logger = LogFactory.getLog(SpringSessionRememberMeServices.class);
private static final Log logger = LogFactory
.getLog(SpringSessionRememberMeServices.class);
private String parameter = DEFAULT_PARAMETER;
private String rememberMeParameterName = DEFAULT_REMEMBERME_PARAMETER;
private boolean alwaysRemember;
@@ -59,17 +62,15 @@ public class SpringSessionRememberMeServices implements RememberMeServices {
return null;
}
public final void loginFail(HttpServletRequest request, HttpServletResponse response) {
logger.debug("Interactive login attempt was unsuccessful.");
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
public final void loginFail(HttpServletRequest request,
HttpServletResponse response) {
logout(request);
}
public final void loginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication) {
if (!this.alwaysRemember && !rememberMeRequested(request, this.parameter)) {
public final void loginSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication successfulAuthentication) {
if (!this.alwaysRemember
&& !rememberMeRequested(request, this.rememberMeParameterName)) {
logger.debug("Remember-me login not requested.");
return;
}
@@ -96,8 +97,8 @@ public class SpringSessionRememberMeServices implements RememberMeServices {
}
}
if (logger.isDebugEnabled()) {
logger.debug("Did not send remember-me cookie (principal did not set " +
"parameter '" + parameter + "')");
logger.debug("Did not send remember-me cookie (principal did not set "
+ "parameter '" + parameter + "')");
}
return false;
}
@@ -106,11 +107,12 @@ public class SpringSessionRememberMeServices implements RememberMeServices {
* Set the name of the parameter which should be checked for to see if a remember-me
* has been requested during a login request. This should be the same name you assign
* to the checkbox in your login form.
* @param parameter the request parameter
* @param rememberMeParameterName the request parameter
*/
public void setParameter(String parameter) {
Assert.hasText(parameter, "Parameter name cannot be empty or null");
this.parameter = parameter;
public void setRememberMeParameterName(String rememberMeParameterName) {
Assert.hasText(rememberMeParameterName,
"rememberMeParameterName cannot be empty or null");
this.rememberMeParameterName = rememberMeParameterName;
}
public void setAlwaysRemember(boolean alwaysRemember) {
@@ -121,4 +123,16 @@ public class SpringSessionRememberMeServices implements RememberMeServices {
this.validitySeconds = validitySeconds;
}
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
logout(request);
}
private void logout(HttpServletRequest request) {
logger.debug("Interactive login attempt was unsuccessful.");
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
}
}

View File

@@ -55,8 +55,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
private String rememberMeRequestAttribute;
private int rememberMeCookieMaxAge = Integer.MAX_VALUE;
/*
* (non-Javadoc)
*
@@ -115,9 +113,11 @@ public class DefaultCookieSerializer implements CookieSerializer {
if ("".equals(requestedCookieValue)) {
sessionCookie.setMaxAge(0);
}
else if (this.rememberMeRequestAttribute != null &&
request.getAttribute(this.rememberMeRequestAttribute) != null) {
sessionCookie.setMaxAge(this.rememberMeCookieMaxAge);
else if (this.rememberMeRequestAttribute != null
&& request.getAttribute(this.rememberMeRequestAttribute) != null) {
// the cookie is only written at time of session creation, so we rely on
// session expiration rather than cookie expiration if remember me is enabled
sessionCookie.setMaxAge(Integer.MAX_VALUE);
}
else {
sessionCookie.setMaxAge(this.cookieMaxAge);
@@ -209,10 +209,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
* @param cookieMaxAge the maxAge property of the Cookie
*/
public void setCookieMaxAge(int cookieMaxAge) {
if (cookieMaxAge > this.rememberMeCookieMaxAge) {
throw new IllegalArgumentException("cookieMaxAge cannot be greater than " +
"rememberMeCookieMaxAge");
}
this.cookieMaxAge = cookieMaxAge;
}
@@ -304,12 +300,10 @@ public class DefaultCookieSerializer implements CookieSerializer {
}
/**
* Set the request attribute name that indicates remember-me login. Used to write
* {@link Cookie} with {@code maxAge} property indicated by
* {@link #rememberMeCookieMaxAge}.
* Set the request attribute name that indicates remember-me login. If specified, the
* cookie will be written as Integer.MAX_VALUE.
* @param rememberMeRequestAttribute the remember-me request attribute name
* @since 1.3.0
* @see #setRememberMeCookieMaxAge(int)
*/
public void setRememberMeRequestAttribute(String rememberMeRequestAttribute) {
if (rememberMeRequestAttribute == null) {
@@ -319,20 +313,6 @@ public class DefaultCookieSerializer implements CookieSerializer {
this.rememberMeRequestAttribute = rememberMeRequestAttribute;
}
/**
* Set the {@code maxAge} property of the {@link Cookie} to be used when remember-me
* is requested. The default is {@link Integer#MAX_VALUE}.
* @param rememberMeCookieMaxAge the {@code maxAge} property of the {@code Cookie}
* @since 1.3.0
*/
public void setRememberMeCookieMaxAge(int rememberMeCookieMaxAge) {
if (rememberMeCookieMaxAge < 1 || rememberMeCookieMaxAge < this.cookieMaxAge) {
throw new IllegalArgumentException("rememberMeCookieMaxAge must be greater " +
"than zero and greater than cookieMaxAge");
}
this.rememberMeCookieMaxAge = rememberMeCookieMaxAge;
}
private String getDomainName(HttpServletRequest request) {
if (this.domainName != null) {
return this.domainName;

View File

@@ -29,7 +29,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.SessionRepository;
import org.springframework.session.security.SpringSessionRememberMeServices;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
import org.springframework.session.web.http.CookieHttpSessionStrategy;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.session.web.http.SessionEventHttpSessionListenerAdapter;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.session.security;
package org.springframework.session.security.web.authentication;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -50,44 +50,48 @@ public class SpringSessionRememberMeServicesTests {
@Test
public void create() {
this.rememberMeServices = new SpringSessionRememberMeServices();
assertThat(ReflectionTestUtils.getField(this.rememberMeServices, "parameter"))
.isEqualTo("remember-me");
assertThat(ReflectionTestUtils.getField(this.rememberMeServices, "alwaysRemember"))
.isEqualTo(false);
assertThat(ReflectionTestUtils.getField(this.rememberMeServices, "validitySeconds"))
.isEqualTo(2592000);
assertThat(ReflectionTestUtils.getField(this.rememberMeServices,
"rememberMeParameterName")).isEqualTo("remember-me");
assertThat(
ReflectionTestUtils.getField(this.rememberMeServices, "alwaysRemember"))
.isEqualTo(false);
assertThat(
ReflectionTestUtils.getField(this.rememberMeServices, "validitySeconds"))
.isEqualTo(2592000);
}
@Test
public void createWithCustomParameter() {
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.setParameter("test-param");
assertThat(ReflectionTestUtils.getField(this.rememberMeServices, "parameter"))
.isEqualTo("test-param");
this.rememberMeServices.setRememberMeParameterName("test-param");
assertThat(ReflectionTestUtils.getField(this.rememberMeServices,
"rememberMeParameterName")).isEqualTo("test-param");
}
@Test
public void createWithNullParameter() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Parameter name cannot be empty or null");
this.thrown.expectMessage("rememberMeParameterName cannot be empty or null");
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.setParameter(null);
this.rememberMeServices.setRememberMeParameterName(null);
}
@Test
public void createWithAlwaysRemember() {
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.setAlwaysRemember(true);
assertThat(ReflectionTestUtils.getField(this.rememberMeServices, "alwaysRemember"))
.isEqualTo(true);
assertThat(
ReflectionTestUtils.getField(this.rememberMeServices, "alwaysRemember"))
.isEqualTo(true);
}
@Test
public void createWithCustomValidity() {
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.setValiditySeconds(100000);
assertThat(ReflectionTestUtils.getField(this.rememberMeServices, "validitySeconds"))
.isEqualTo(100000);
assertThat(
ReflectionTestUtils.getField(this.rememberMeServices, "validitySeconds"))
.isEqualTo(100000);
}
@Test
@@ -139,7 +143,7 @@ public class SpringSessionRememberMeServicesTests {
given(request.getParameter(eq("test-param"))).willReturn("true");
given(request.getSession()).willReturn(session);
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.setParameter("test-param");
this.rememberMeServices.setRememberMeParameterName("test-param");
this.rememberMeServices.loginSuccess(request, response, authentication);
verify(request, times(1)).getParameter(eq("test-param"));
verify(request, times(1)).getSession();
@@ -172,8 +176,7 @@ public class SpringSessionRememberMeServicesTests {
HttpServletResponse response = mock(HttpServletResponse.class);
Authentication authentication = mock(Authentication.class);
HttpSession session = mock(HttpSession.class);
given(request.getParameter(eq("remember-me")))
.willReturn("true");
given(request.getParameter(eq("remember-me"))).willReturn("true");
given(request.getSession()).willReturn(session);
this.rememberMeServices = new SpringSessionRememberMeServices();
this.rememberMeServices.setValiditySeconds(100000);

View File

@@ -431,23 +431,6 @@ public class DefaultCookieSerializerTests {
// --- rememberMe ---
@Test
public void setRememberMeCookieMaxAgeNotGreaterThanZero() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("rememberMeCookieMaxAge must be greater than zero and " +
"greater than cookieMaxAge");
this.serializer.setRememberMeCookieMaxAge(0);
}
@Test
public void setRememberMeCookieMaxAgeNotGreaterMaxCookieAge() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("rememberMeCookieMaxAge must be greater than zero and " +
"greater than cookieMaxAge");
this.serializer.setCookieMaxAge(20);
this.serializer.setRememberMeCookieMaxAge(10);
}
@Test
public void writeCookieRememberMeCookieMaxAgeDefault() {
this.request.setAttribute("rememberMe", true);
@@ -457,17 +440,6 @@ public class DefaultCookieSerializerTests {
assertThat(getCookie().getMaxAge()).isEqualTo(Integer.MAX_VALUE);
}
@Test
public void writeCookieRememberMeCookieMaxAgeExplicit() {
this.request.setAttribute("rememberMe", true);
this.serializer.setRememberMeRequestAttribute("rememberMe");
this.serializer.setRememberMeCookieMaxAge(100);
this.serializer.writeCookieValue(cookieValue(this.sessionId));
assertThat(getCookie().getMaxAge()).isEqualTo(100);
}
public void setCookieName(String cookieName) {
this.cookieName = cookieName;
this.serializer.setCookieName(cookieName);