SEC-1493: Added CredentialsContainer interface and implemented it in User, AbstractAuthenticationToken and UsernamePasswordAuthenticationToken. ProviderManager makes use of this to erase the credentials of the returned Authentication object (and its contents) if configured to do so by setting the 'eraseCredentialsAfterAuthentication' property.

This commit is contained in:
Luke Taylor 2010-02-07 00:04:57 +00:00
parent ea8d37892c
commit db913f6857
7 changed files with 113 additions and 18 deletions

View File

@ -22,6 +22,7 @@ import java.util.Collections;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
@ -34,7 +35,7 @@ import org.springframework.security.core.userdetails.UserDetails;
* @author Ben Alex * @author Ben Alex
* @author Luke Taylor * @author Luke Taylor
*/ */
public abstract class AbstractAuthenticationToken implements Authentication { public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
//~ Instance fields ================================================================================================ //~ Instance fields ================================================================================================
private Object details; private Object details;
@ -99,6 +100,22 @@ public abstract class AbstractAuthenticationToken implements Authentication {
this.details = details; this.details = details;
} }
/**
* Checks the {@code credentials}, {@code principal} and {@code details} objects, invoking the
* {@code eraseCredentials} method on any which implement {@link CredentialsContainer}.
*/
public void eraseCredentials() {
eraseSecret(getCredentials());
eraseSecret(getPrincipal());
eraseSecret(details);
}
private void eraseSecret(Object secret) {
if (secret instanceof CredentialsContainer) {
((CredentialsContainer)secret).eraseCredentials();
}
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (!(obj instanceof AbstractAuthenticationToken)) { if (!(obj instanceof AbstractAuthenticationToken)) {
@ -174,7 +191,7 @@ public abstract class AbstractAuthenticationToken implements Authentication {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(super.toString()).append(": "); sb.append(super.toString()).append(": ");
sb.append("Principal: ").append(this.getPrincipal()).append("; "); sb.append("Principal: ").append(this.getPrincipal()).append("; ");
sb.append("Password: [PROTECTED]; "); sb.append("Credentials: [PROTECTED]; ");
sb.append("Authenticated: ").append(this.isAuthenticated()).append("; "); sb.append("Authenticated: ").append(this.isAuthenticated()).append("; ");
sb.append("Details: ").append(this.getDetails()).append("; "); sb.append("Details: ").append(this.getDetails()).append("; ");

View File

@ -26,6 +26,7 @@ import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor; import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -42,10 +43,17 @@ import org.springframework.util.Assert;
* <code>AuthenticationException</code>, the last <code>AuthenticationException</code> received will be used. * <code>AuthenticationException</code>, the last <code>AuthenticationException</code> received will be used.
* If no provider returns a non-null response, or indicates it can even process an <code>Authentication</code>, * If no provider returns a non-null response, or indicates it can even process an <code>Authentication</code>,
* the <code>ProviderManager</code> will throw a <code>ProviderNotFoundException</code>. * the <code>ProviderManager</code> will throw a <code>ProviderNotFoundException</code>.
* A parent {@code AuthenticationManager} can also be set, and this will also be tried if none of the configured
* providers can perform the authentication. This is intended to support namespace configuration options though and
* is not a feature that should normally be required.
* <p> * <p>
* The exception to this process is when a provider throws an {@link AccountStatusException}, in which case no * The exception to this process is when a provider throws an {@link AccountStatusException}, in which case no
* further providers in the list will be queried. * further providers in the list will be queried.
* *
* Post-authentication, the credentials will be cleared from the returned {@code Authentication} object, if it
* implements the {@link CredentialsContainer} interface. This behaviour can be controlled by modifying the
* {@link #setEraseCredentialsAfterAuthentication(boolean) eraseCredentialsAfterAuthentication} property.
*
* <h2>Event Publishing</h2> * <h2>Event Publishing</h2>
* <p> * <p>
* Authentication event publishing is delegated to the configured {@link AuthenticationEventPublisher} which defaults * Authentication event publishing is delegated to the configured {@link AuthenticationEventPublisher} which defaults
@ -57,9 +65,10 @@ import org.springframework.util.Assert;
* the <tt>&lt;http&gt;</tt> configuration, so you will receive events from the web part of your application automatically. * the <tt>&lt;http&gt;</tt> configuration, so you will receive events from the web part of your application automatically.
* <p> * <p>
* Note that the implementation also publishes authentication failure events when it obtains an authentication result * Note that the implementation also publishes authentication failure events when it obtains an authentication result
* (or an exception) from the "parent" <tt>AuthenticationManager</tt> if one has been set. So in this situation, the * (or an exception) from the "parent" {@code AuthenticationManager} if one has been set. So in this situation, the
* parent should not generally be configured to publish events or there will be duplicates. * parent should not generally be configured to publish events or there will be duplicates.
* *
*
* @author Ben Alex * @author Ben Alex
* @author Luke Taylor * @author Luke Taylor
* *
@ -76,7 +85,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
private List<AuthenticationProvider> providers = Collections.emptyList(); private List<AuthenticationProvider> providers = Collections.emptyList();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private AuthenticationManager parent; private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication = true;
private boolean clearExtraInformation = false; private boolean clearExtraInformation = false;
//~ Methods ======================================================================================================== //~ Methods ========================================================================================================
@ -150,6 +159,11 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
} }
if (result != null) { if (result != null) {
if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data from authentication
((CredentialsContainer)result).eraseCredentials();
}
eventPublisher.publishAuthenticationSuccess(result); eventPublisher.publishAuthenticationSuccess(result);
return result; return result;
} }
@ -207,6 +221,22 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
this.eventPublisher = eventPublisher; this.eventPublisher = eventPublisher;
} }
/**
* If set to, a resulting {@code Authentication} which implements the {@code CredentialsContainer} interface
* will have its {@link CredentialsContainer#eraseCredentials() eraseCredentials} method called before it is returned
* from the {@code authenticate()} method.
*
* @param eraseSecretData set to {@literal false} to retain the credentials data in memory.
* Defaults to {@literal true}.
*/
public void setEraseCredentialsAfterAuthentication(boolean eraseSecretData) {
this.eraseCredentialsAfterAuthentication = eraseSecretData;
}
public boolean isEraseCredentialsAfterAuthentication() {
return eraseCredentialsAfterAuthentication;
}
/** /**
* Sets the {@link AuthenticationProvider} objects to be used for authentication. * Sets the {@link AuthenticationProvider} objects to be used for authentication.
* *

View File

@ -15,7 +15,6 @@
package org.springframework.security.authentication; package org.springframework.security.authentication;
import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
@ -28,8 +27,9 @@ import org.springframework.security.core.GrantedAuthority;
* <code>GrantedAuthority</code>s that apply. * <code>GrantedAuthority</code>s that apply.
* *
* @author Ben Alex * @author Ben Alex
* @author Luke Taylor
*/ */
public class RememberMeAuthenticationToken extends AbstractAuthenticationToken implements Serializable { public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
//~ Instance fields ================================================================================================ //~ Instance fields ================================================================================================
private final Object principal; private final Object principal;

View File

@ -22,8 +22,8 @@ import org.springframework.security.core.GrantedAuthority;
/** /**
* An {@link org.springframework.security.core.Authentication} implementation that is designed for simple presentation of a * An {@link org.springframework.security.core.Authentication} implementation that is designed for simple presentation
* username and password. * of a username and password.
* <p> * <p>
* The <code>principal</code> and <code>credentials</code> should be set with an <code>Object</code> that provides * The <code>principal</code> and <code>credentials</code> should be set with an <code>Object</code> that provides
* the respective property via its <code>Object.toString()</code> method. The simplest such <code>Object</code> to use * the respective property via its <code>Object.toString()</code> method. The simplest such <code>Object</code> to use
@ -34,8 +34,8 @@ import org.springframework.security.core.GrantedAuthority;
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken { public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
//~ Instance fields ================================================================================================ //~ Instance fields ================================================================================================
private final Object credentials;
private final Object principal; private final Object principal;
private Object credentials;
//~ Constructors =================================================================================================== //~ Constructors ===================================================================================================
@ -94,4 +94,10 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
super.setAuthenticated(false); super.setAuthenticated(false);
} }
@Override
public void eraseCredentials() {
super.eraseCredentials();
credentials = null;
}
} }

View File

@ -0,0 +1,17 @@
package org.springframework.security.core;
/**
* Indicates that the implementing object contains sensitive data, which can be erased using the
* {@code eraseCredentials} method. Implementations are expected to invoke the method on any internal objects
* which may also implement this interface.
* <p>
* For internal framework use only. Users who are writing their own {@code AuthenticationProvider} implementations
* should create and return an appropriate {@code Authentication} object there, minus any sensitive data,
* rather than using this interface.
*
* @author Luke Taylor
* @since 3.0.3
*/
public interface CredentialsContainer {
void eraseCredentials();
}

View File

@ -24,6 +24,7 @@ import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -41,9 +42,9 @@ import org.springframework.util.Assert;
* @author Ben Alex * @author Ben Alex
* @author Luke Taylor * @author Luke Taylor
*/ */
public class User implements UserDetails { public class User implements UserDetails, CredentialsContainer {
//~ Instance fields ================================================================================================ //~ Instance fields ================================================================================================
private final String password; private String password;
private final String username; private final String username;
private final Set<GrantedAuthority> authorities; private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired; private final boolean accountNonExpired;
@ -113,20 +114,24 @@ public class User implements UserDetails {
return username; return username;
} }
public boolean isEnabled() {
return enabled;
}
public boolean isAccountNonExpired() { public boolean isAccountNonExpired() {
return accountNonExpired; return accountNonExpired;
} }
public boolean isAccountNonLocked() { public boolean isAccountNonLocked() {
return this.accountNonLocked; return accountNonLocked;
} }
public boolean isCredentialsNonExpired() { public boolean isCredentialsNonExpired() {
return credentialsNonExpired; return credentialsNonExpired;
} }
public boolean isEnabled() { public void eraseCredentials() {
return enabled; password = null;
} }
private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) { private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {

View File

@ -28,7 +28,6 @@ import org.springframework.context.MessageSource;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
/** /**
* Tests {@link ProviderManager}. * Tests {@link ProviderManager}.
@ -40,14 +39,33 @@ public class ProviderManagerTests {
@Test(expected=ProviderNotFoundException.class) @Test(expected=ProviderNotFoundException.class)
public void authenticationFailsWithUnsupportedToken() throws Exception { public void authenticationFailsWithUnsupportedToken() throws Exception {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password", Authentication token = new AbstractAuthenticationToken (null) {
AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO")); public Object getCredentials() {
return "";
}
public Object getPrincipal() {
return "";
}
};
ProviderManager mgr = makeProviderManager(); ProviderManager mgr = makeProviderManager();
mgr.setMessageSource(mock(MessageSource.class)); mgr.setMessageSource(mock(MessageSource.class));
mgr.authenticate(token); mgr.authenticate(token);
} }
@Test
public void credentialsAreClearedByDefault() throws Exception {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password");
ProviderManager mgr = makeProviderManager();
Authentication result = mgr.authenticate(token);
assertNull(result.getCredentials());
mgr.setEraseCredentialsAfterAuthentication(false);
token = new UsernamePasswordAuthenticationToken("Test", "Password");
result = mgr.authenticate(token);
assertNotNull(result.getCredentials());
}
@Test @Test
public void authenticationSucceedsWithSupportedTokenAndReturnsExpectedObject() throws Exception { public void authenticationSucceedsWithSupportedTokenAndReturnsExpectedObject() throws Exception {
final Authentication a = mock(Authentication.class); final Authentication a = mock(Authentication.class);
@ -126,6 +144,7 @@ public class ProviderManagerTests {
request.setDetails(details); request.setDetails(details);
Authentication result = authMgr.authenticate(request); Authentication result = authMgr.authenticate(request);
assertNotNull(result.getCredentials());
assertSame(details, result.getDetails()); assertSame(details, result.getDetails());
} }
@ -278,7 +297,8 @@ public class ProviderManagerTests {
} }
public boolean supports(Class<? extends Object> authentication) { public boolean supports(Class<? extends Object> authentication) {
if (TestingAuthenticationToken.class.isAssignableFrom(authentication)) { if (TestingAuthenticationToken.class.isAssignableFrom(authentication) ||
UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)) {
return true; return true;
} else { } else {
return false; return false;