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.security.Authentication;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.AccountStatusException;
import org.springframework.security.providers.rememberme.RememberMeAuthenticationToken;
import org.springframework.security.ui.AuthenticationDetailsSource;
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.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.security.userdetails.decorator.StatusCheckingUserDetailsService;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
@ -27,6 +29,7 @@ import javax.servlet.http.HttpServletResponse;
*
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
public abstract class AbstractRememberMeServices implements RememberMeServices, InitializingBean, LogoutHandler {
//~ Static fields/initializers =====================================================================================
@ -91,6 +94,10 @@ public abstract class AbstractRememberMeServices implements RememberMeServices,
cancelCookie(request, response);
logger.debug("Invalid remember-me cookie: " + invalidCookie.getMessage());
return null;
} catch (AccountStatusException statusInvalid) {
cancelCookie(request, response);
logger.debug("Invalid UserDetails: " + statusInvalid.getMessage());
return null;
} catch (RememberMeAuthenticationException e) {
cancelCookie(request, response);
logger.debug(e.getMessage());
@ -175,21 +182,6 @@ public abstract class AbstractRememberMeServices implements RememberMeServices,
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) {
logger.debug("Interactive login attempt was unsuccessful.");
cancelCookie(request, response);
@ -327,7 +319,7 @@ public abstract class AbstractRememberMeServices implements RememberMeServices,
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
this.userDetailsService = new StatusCheckingUserDetailsService(userDetailsService);
}
public void setKey(String key) {

View File

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

View File

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

View File

@ -7,10 +7,11 @@ import java.util.Date;
* login tokens for a user.
*
* @see JdbcTokenRepositoryImpl
* @see InMemoryTokenRepositoryImpl
* @see InMemoryTokenRepositoryImpl
*
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
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
* implementation will only send a remember-me token if the parameter defined by
* {@link #setParameter(String)} is present.
* </p>
*
* <p>
* An {@link org.springframework.security.userdetails.UserDetailsService} is required by
* this implementation, so that it can construct a valid
* <code>Authentication</code> from the returned {@link
* org.springframework.security.userdetails.UserDetails}. This is also necessary so that
* the user's password is available and can be checked as part of the encoded
* cookie.
* </p>
* <code>Authentication</code> from the returned {@link org.springframework.security.userdetails.UserDetails}.
* This is also necessary so that the user's password is available and can be checked as part of the encoded cookie.
*
* <p>
* 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)
* </pre>
*
* </p>
* <p>
* As such, if the user changes their password, any remember-me token will be
* 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
* services) and as such high security applications should be aware of this
* occasionally undesired disclosure of a valid username.
* </p>
*
* <p>
* This is a basic remember-me implementation which is suitable for many
* applications. However, we recommend a database-based implementation if you
* require a more secure remember-me approach (see {@link PersistentTokenBasedRememberMeServices}).
* </p>
*
* <p>
* By default the tokens will be valid for 14 days from the last successful
* authentication attempt. This can be changed using
* {@link #setTokenValiditySeconds(int)}.
* </p>
*
*
* @author Ben Alex
* @version $Id$
@ -116,8 +111,6 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices {
UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]);
validateUserDetails(userDetails);
// Check signature of token matches remaining details.
// 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,

View File

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

View File

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

View File

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