SEC-664: Removed validateUserDetails method from AbstractRememberMeServices, wrapped the UserDetailsService in a status-checking one and added a catch block for AccountStatusExceptions. Also some minor tidying up of other remember-me classes.

This commit is contained in:
Luke Taylor 2008-02-04 21:26:07 +00:00
parent d3f26f09b6
commit 84c7ac5e57
8 changed files with 25 additions and 45 deletions

View File

@ -7,6 +7,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.MessageSourceAccessor; import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.Authentication; import org.springframework.security.Authentication;
import org.springframework.security.SpringSecurityMessageSource; import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.AccountStatusException;
import org.springframework.security.providers.rememberme.RememberMeAuthenticationToken; import org.springframework.security.providers.rememberme.RememberMeAuthenticationToken;
import org.springframework.security.ui.AuthenticationDetailsSource; import org.springframework.security.ui.AuthenticationDetailsSource;
import org.springframework.security.ui.AuthenticationDetailsSourceImpl; import org.springframework.security.ui.AuthenticationDetailsSourceImpl;
@ -14,6 +15,7 @@ import org.springframework.security.ui.logout.LogoutHandler;
import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService; import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException; import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.security.userdetails.decorator.StatusCheckingUserDetailsService;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.ServletRequestUtils; import org.springframework.web.bind.ServletRequestUtils;
@ -27,6 +29,7 @@ import javax.servlet.http.HttpServletResponse;
* *
* @author Luke Taylor * @author Luke Taylor
* @version $Id$ * @version $Id$
* @since 2.0
*/ */
public abstract class AbstractRememberMeServices implements RememberMeServices, InitializingBean, LogoutHandler { public abstract class AbstractRememberMeServices implements RememberMeServices, InitializingBean, LogoutHandler {
//~ Static fields/initializers ===================================================================================== //~ Static fields/initializers =====================================================================================
@ -91,6 +94,10 @@ public abstract class AbstractRememberMeServices implements RememberMeServices,
cancelCookie(request, response); cancelCookie(request, response);
logger.debug("Invalid remember-me cookie: " + invalidCookie.getMessage()); logger.debug("Invalid remember-me cookie: " + invalidCookie.getMessage());
return null; return null;
} catch (AccountStatusException statusInvalid) {
cancelCookie(request, response);
logger.debug("Invalid UserDetails: " + statusInvalid.getMessage());
return null;
} catch (RememberMeAuthenticationException e) { } catch (RememberMeAuthenticationException e) {
cancelCookie(request, response); cancelCookie(request, response);
logger.debug(e.getMessage()); logger.debug(e.getMessage());
@ -175,21 +182,6 @@ public abstract class AbstractRememberMeServices implements RememberMeServices,
return sb.toString(); return sb.toString();
} }
/**
* Provided for subclass convenience to check the account status of a loaded user.
*
* @throws UsernameNotFoundException if the username could not be located by the configured UserDetailsService.
* @throws RememberMeAuthenticationException if the account is locked or disabled.
*/
protected void validateUserDetails(UserDetails user) throws UsernameNotFoundException,
RememberMeAuthenticationException {
if (!user.isAccountNonExpired() || !user.isCredentialsNonExpired() || !user.isEnabled()) {
throw new RememberMeAuthenticationException("Remember-me login was valid for user " +
user.getUsername() + ", but account is expired, has expired credentials or is disabled");
}
}
public final void loginFail(HttpServletRequest request, HttpServletResponse response) { public final void loginFail(HttpServletRequest request, HttpServletResponse response) {
logger.debug("Interactive login attempt was unsuccessful."); logger.debug("Interactive login attempt was unsuccessful.");
cancelCookie(request, response); cancelCookie(request, response);
@ -327,7 +319,7 @@ public abstract class AbstractRememberMeServices implements RememberMeServices,
} }
public void setUserDetailsService(UserDetailsService userDetailsService) { public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService; this.userDetailsService = new StatusCheckingUserDetailsService(userDetailsService);
} }
public void setKey(String key) { public void setKey(String key) {

View File

@ -18,6 +18,7 @@ import java.util.Date;
* *
* @author Luke Taylor * @author Luke Taylor
* @version $Id$ * @version $Id$
* @since 2.0
*/ */
public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements PersistentTokenRepository { public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements PersistentTokenRepository {
//~ Static fields/initializers ===================================================================================== //~ Static fields/initializers =====================================================================================

View File

@ -34,6 +34,7 @@ import java.util.Date;
* *
* @author Luke Taylor * @author Luke Taylor
* @version $Id$ * @version $Id$
* @since 2.0
*/ */
public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices { public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices {
@ -112,8 +113,6 @@ public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeSe
UserDetails user = getUserDetailsService().loadUserByUsername(token.getUsername()); UserDetails user = getUserDetailsService().loadUserByUsername(token.getUsername());
validateUserDetails(user);
return user; return user;
} }

View File

@ -11,6 +11,7 @@ import java.util.Date;
* *
* @author Luke Taylor * @author Luke Taylor
* @version $Id$ * @version $Id$
* @since 2.0
*/ */
public interface PersistentTokenRepository { public interface PersistentTokenRepository {

View File

@ -39,16 +39,12 @@ import java.util.Date;
* credentials - not the time period they last logged in via remember-me. The * credentials - not the time period they last logged in via remember-me. The
* implementation will only send a remember-me token if the parameter defined by * implementation will only send a remember-me token if the parameter defined by
* {@link #setParameter(String)} is present. * {@link #setParameter(String)} is present.
* </p>
* *
* <p> * <p>
* An {@link org.springframework.security.userdetails.UserDetailsService} is required by * An {@link org.springframework.security.userdetails.UserDetailsService} is required by
* this implementation, so that it can construct a valid * this implementation, so that it can construct a valid
* <code>Authentication</code> from the returned {@link * <code>Authentication</code> from the returned {@link org.springframework.security.userdetails.UserDetails}.
* org.springframework.security.userdetails.UserDetails}. This is also necessary so that * This is also necessary so that the user's password is available and can be checked as part of the encoded cookie.
* the user's password is available and can be checked as part of the encoded
* cookie.
* </p>
* *
* <p> * <p>
* The cookie encoded by this implementation adopts the following form: * The cookie encoded by this implementation adopts the following form:
@ -57,7 +53,6 @@ import java.util.Date;
* username + &quot;:&quot; + expiryTime + &quot;:&quot; + Md5Hex(username + &quot;:&quot; + expiryTime + &quot;:&quot; + password + &quot;:&quot; + key) * username + &quot;:&quot; + expiryTime + &quot;:&quot; + Md5Hex(username + &quot;:&quot; + expiryTime + &quot;:&quot; + password + &quot;:&quot; + key)
* </pre> * </pre>
* *
* </p>
* <p> * <p>
* As such, if the user changes their password, any remember-me token will be * As such, if the user changes their password, any remember-me token will be
* invalidated. Equally, the system administrator may invalidate every * invalidated. Equally, the system administrator may invalidate every
@ -69,17 +64,17 @@ import java.util.Date;
* implementation (as we do not want to rely on a database for remember-me * implementation (as we do not want to rely on a database for remember-me
* services) and as such high security applications should be aware of this * services) and as such high security applications should be aware of this
* occasionally undesired disclosure of a valid username. * occasionally undesired disclosure of a valid username.
* </p> *
* <p> * <p>
* This is a basic remember-me implementation which is suitable for many * This is a basic remember-me implementation which is suitable for many
* applications. However, we recommend a database-based implementation if you * applications. However, we recommend a database-based implementation if you
* require a more secure remember-me approach (see {@link PersistentTokenBasedRememberMeServices}). * require a more secure remember-me approach (see {@link PersistentTokenBasedRememberMeServices}).
* </p> *
* <p> * <p>
* By default the tokens will be valid for 14 days from the last successful * By default the tokens will be valid for 14 days from the last successful
* authentication attempt. This can be changed using * authentication attempt. This can be changed using
* {@link #setTokenValiditySeconds(int)}. * {@link #setTokenValiditySeconds(int)}.
* </p> *
* *
* @author Ben Alex * @author Ben Alex
* @version $Id$ * @version $Id$
@ -116,8 +111,6 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]); UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]);
validateUserDetails(userDetails);
// Check signature of token matches remaining details. // Check signature of token matches remaining details.
// Must do this after user lookup, as we need the DAO-derived password. // Must do this after user lookup, as we need the DAO-derived password.
// If efficiency was a major issue, just add in a UserCache implementation, // If efficiency was a major issue, just add in a UserCache implementation,

View File

@ -259,7 +259,6 @@ public class AbstractRememberMeServicesTests {
} }
UserDetails user = getUserDetailsService().loadUserByUsername("joe"); UserDetails user = getUserDetailsService().loadUserByUsername("joe");
validateUserDetails(user);
return user; return user;
} }

View File

@ -84,8 +84,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase {
return tokenValueBase64; return tokenValueBase64;
} }
public void testAutoLoginIfDoesNotPresentAnyCookies() public void testAutoLoginIfDoesNotPresentAnyCookies() throws Exception {
throws Exception {
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
services.setKey("key"); services.setKey("key");
services.setUserDetailsService(new MockAuthenticationDao(null, true)); services.setUserDetailsService(new MockAuthenticationDao(null, true));
@ -104,8 +103,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase {
assertNull(returnedCookie); // shouldn't try to invalidate our cookie assertNull(returnedCookie); // shouldn't try to invalidate our cookie
} }
public void testAutoLoginIfDoesNotPresentRequiredCookie() public void testAutoLoginIfDoesNotPresentRequiredCookie() throws Exception {
throws Exception {
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
services.setKey("key"); services.setKey("key");
services.setUserDetailsService(new MockAuthenticationDao(null, true)); services.setUserDetailsService(new MockAuthenticationDao(null, true));
@ -150,8 +148,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase {
assertEquals(0, returnedCookie.getMaxAge()); assertEquals(0, returnedCookie.getMaxAge());
} }
public void testAutoLoginIfMissingThreeTokensInCookieValue() public void testAutoLoginIfMissingThreeTokensInCookieValue() throws Exception {
throws Exception {
UserDetails user = new User("someone", "password", true, true, true, true, UserDetails user = new User("someone", "password", true, true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}); new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
@ -201,8 +198,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase {
assertEquals(0, returnedCookie.getMaxAge()); assertEquals(0, returnedCookie.getMaxAge());
} }
public void testAutoLoginIfSignatureBlocksDoesNotMatchExpectedValue() public void testAutoLoginIfSignatureBlocksDoesNotMatchExpectedValue() throws Exception {
throws Exception {
UserDetails user = new User("someone", "password", true, true, true, true, UserDetails user = new User("someone", "password", true, true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}); new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
@ -228,8 +224,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase {
assertEquals(0, returnedCookie.getMaxAge()); assertEquals(0, returnedCookie.getMaxAge());
} }
public void testAutoLoginIfTokenDoesNotContainANumberInCookieValue() public void testAutoLoginIfTokenDoesNotContainANumberInCookieValue() throws Exception {
throws Exception {
UserDetails user = new User("someone", "password", true, true, true, true, UserDetails user = new User("someone", "password", true, true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}); new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});

View File

@ -23,9 +23,9 @@
--> -->
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" /> <intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<!-- <!--
Uncomment to enable X509 client authentication support Uncomment to enable X509 client authentication support -->
<x509 /> <x509 />
-->
<!-- All of this is unnecessary if auto-config="true" --> <!-- All of this is unnecessary if auto-config="true" -->
<form-login /> <form-login />
<anonymous /> <anonymous />
@ -68,7 +68,7 @@ Uncomment to authenticate against an embedded LDAP server.
<authentication-provider> <authentication-provider>
<password-encoder hash="md5"/> <password-encoder hash="md5"/>
<user-service> <user-service>
<user name="rod" password="a564de63c2d0da68cf47586ee05984d7" authorities="ROLE_SUPERVISOR,ROLE_USER,ROLE_TELLER" /> <user name="rod" password="a564de63c2d0da68cf47586ee05984d7" locked="true" authorities="ROLE_SUPERVISOR, ROLE_USER, ROLE_TELLER" />
<user name="dianne" password="65d15fe9156f9c4bbffd98085992a44e" authorities="ROLE_USER,ROLE_TELLER" /> <user name="dianne" password="65d15fe9156f9c4bbffd98085992a44e" authorities="ROLE_USER,ROLE_TELLER" />
<user name="scott" password="2b58af6dddbd072ed27ffc86725d7d3a" authorities="ROLE_USER" /> <user name="scott" password="2b58af6dddbd072ed27ffc86725d7d3a" authorities="ROLE_USER" />
</user-service> </user-service>