Skip to content

Commit

Permalink
SEC-1493: Added CredentialsContainer interface and implemented it in …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
tekul committed Jun 20, 2010
1 parent ea8d378 commit db913f6
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 18 deletions.
Expand Up @@ -22,6 +22,7 @@

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;

Expand All @@ -34,7 +35,7 @@
* @author Ben Alex
* @author Luke Taylor
*/
public abstract class AbstractAuthenticationToken implements Authentication {
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
//~ Instance fields ================================================================================================

private Object details;
Expand Down Expand Up @@ -99,6 +100,22 @@ public void setDetails(Object 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
public boolean equals(Object obj) {
if (!(obj instanceof AbstractAuthenticationToken)) {
Expand Down Expand Up @@ -174,7 +191,7 @@ public String toString() {
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("; ");

Expand Down
Expand Up @@ -26,6 +26,7 @@
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;

Expand All @@ -42,10 +43,17 @@
* <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
Expand All @@ -57,9 +65,10 @@
* 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
*
Expand All @@ -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 ========================================================================================================
Expand Down Expand Up @@ -150,6 +159,11 @@ public Authentication authenticate(Authentication authentication) throws Authent
}

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;
}
Expand Down Expand Up @@ -207,6 +221,22 @@ public void setAuthenticationEventPublisher(AuthenticationEventPublisher eventPu
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.
*
Expand Down
Expand Up @@ -15,7 +15,6 @@

package org.springframework.security.authentication;

import java.io.Serializable;
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
Expand All @@ -28,8 +27,9 @@
* <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;
Expand Down
Expand Up @@ -22,8 +22,8 @@


/**
* 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
Expand All @@ -34,8 +34,8 @@
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
//~ Instance fields ================================================================================================

private final Object credentials;
private final Object principal;
private Object credentials;

//~ Constructors ===================================================================================================

Expand Down Expand Up @@ -94,4 +94,10 @@ public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentExce

super.setAuthenticated(false);
}

@Override
public void eraseCredentials() {
super.eraseCredentials();
credentials = null;
}
}
@@ -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();
}
Expand Up @@ -24,6 +24,7 @@
import java.util.TreeSet;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.util.Assert;

/**
Expand All @@ -41,9 +42,9 @@
* @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;
Expand Down Expand Up @@ -113,20 +114,24 @@ public String getUsername() {
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) {
Expand Down
Expand Up @@ -28,7 +28,6 @@
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}.
Expand All @@ -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);
Expand Down Expand Up @@ -126,6 +144,7 @@ public void detailsAreSetOnAuthenticationTokenIfNotAlreadySetByProvider() throws
request.setDetails(details);

Authentication result = authMgr.authenticate(request);
assertNotNull(result.getCredentials());
assertSame(details, result.getDetails());
}

Expand Down Expand Up @@ -278,7 +297,8 @@ public Authentication authenticate(Authentication authentication) throws Authent
}

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;
Expand Down

0 comments on commit db913f6

Please sign in to comment.