mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-25 05:22:16 +00:00
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:
parent
d3f26f09b6
commit
84c7ac5e57
@ -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) {
|
||||||
|
@ -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 =====================================================================================
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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 + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key)
|
* username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + 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,
|
||||||
|
@ -259,7 +259,6 @@ public class AbstractRememberMeServicesTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UserDetails user = getUserDetailsService().loadUserByUsername("joe");
|
UserDetails user = getUserDetailsService().loadUserByUsername("joe");
|
||||||
validateUserDetails(user);
|
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
@ -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")});
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user