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.GrantedAuthority;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
@ -34,7 +35,7 @@ import org.springframework.security.core.userdetails.UserDetails;
* @author Ben Alex
* @author Luke Taylor
*/
public abstract class AbstractAuthenticationToken implements Authentication {
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
//~ Instance fields ================================================================================================
private Object details;
@ -99,6 +100,22 @@ public abstract class AbstractAuthenticationToken implements Authentication {
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
public boolean equals(Object obj) {
if (!(obj instanceof AbstractAuthenticationToken)) {
@ -174,7 +191,7 @@ public abstract class AbstractAuthenticationToken implements Authentication {
StringBuilder sb = new StringBuilder();
sb.append(super.toString()).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("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.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.SpringSecurityMessageSource;
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.
* 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>.
* 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>
* 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.
*
* 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>
* <p>
* 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.
* <p>
* 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.
*
*
* @author Ben Alex
* @author Luke Taylor
*
@ -76,7 +85,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
private List<AuthenticationProvider> providers = Collections.emptyList();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication = true;
private boolean clearExtraInformation = false;
//~ Methods ========================================================================================================
@ -150,6 +159,11 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
}
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);
return result;
}
@ -207,6 +221,22 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
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.
*

View File

@ -15,7 +15,6 @@
package org.springframework.security.authentication;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
@ -28,8 +27,9 @@ import org.springframework.security.core.GrantedAuthority;
* <code>GrantedAuthority</code>s that apply.
*
* @author Ben Alex
* @author Luke Taylor
*/
public class RememberMeAuthenticationToken extends AbstractAuthenticationToken implements Serializable {
public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
//~ Instance fields ================================================================================================
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
* username and password.
* An {@link org.springframework.security.core.Authentication} implementation that is designed for simple presentation
* of a username and password.
* <p>
* 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
@ -34,8 +34,8 @@ import org.springframework.security.core.GrantedAuthority;
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
//~ Instance fields ================================================================================================
private final Object credentials;
private final Object principal;
private Object credentials;
//~ Constructors ===================================================================================================
@ -94,4 +94,10 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
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 org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.util.Assert;
/**
@ -41,9 +42,9 @@ import org.springframework.util.Assert;
* @author Ben Alex
* @author Luke Taylor
*/
public class User implements UserDetails {
public class User implements UserDetails, CredentialsContainer {
//~ Instance fields ================================================================================================
private final String password;
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
@ -113,20 +114,24 @@ public class User implements UserDetails {
return username;
}
public boolean isEnabled() {
return enabled;
}
public boolean isAccountNonExpired() {
return accountNonExpired;
}
public boolean isAccountNonLocked() {
return this.accountNonLocked;
return accountNonLocked;
}
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
public boolean isEnabled() {
return enabled;
public void eraseCredentials() {
password = null;
}
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.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
/**
* Tests {@link ProviderManager}.
@ -40,14 +39,33 @@ public class ProviderManagerTests {
@Test(expected=ProviderNotFoundException.class)
public void authenticationFailsWithUnsupportedToken() throws Exception {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password",
AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"));
Authentication token = new AbstractAuthenticationToken (null) {
public Object getCredentials() {
return "";
}
public Object getPrincipal() {
return "";
}
};
ProviderManager mgr = makeProviderManager();
mgr.setMessageSource(mock(MessageSource.class));
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
public void authenticationSucceedsWithSupportedTokenAndReturnsExpectedObject() throws Exception {
final Authentication a = mock(Authentication.class);
@ -126,6 +144,7 @@ public class ProviderManagerTests {
request.setDetails(details);
Authentication result = authMgr.authenticate(request);
assertNotNull(result.getCredentials());
assertSame(details, result.getDetails());
}
@ -278,7 +297,8 @@ public class ProviderManagerTests {
}
public boolean supports(Class<? extends Object> authentication) {
if (TestingAuthenticationToken.class.isAssignableFrom(authentication)) {
if (TestingAuthenticationToken.class.isAssignableFrom(authentication) ||
UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)) {
return true;
} else {
return false;