-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closes gh-14352 #14369
Closed
Closed
Closes gh-14352 #14369
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
170 changes: 170 additions & 0 deletions
170
...n/java/org/springframework/security/authentication/AbstractUserDetailsAuthentication.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package org.springframework.security.authentication; | ||
|
||
import org.springframework.security.core.CredentialsContainer; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.TypedAuthentication; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.util.Assert; | ||
|
||
import java.util.Collection; | ||
import java.util.List; | ||
|
||
/** | ||
* Base class for {@link TypedAuthentication} objects, where a {@link UserDetails} is the | ||
* Principal. | ||
* <p> | ||
* Implementations which use this class should be immutable. | ||
* <p> | ||
* Based on {@link AbstractAuthenticationToken}. | ||
* | ||
* @param <C> The type the Credentials are bound to. | ||
* @param <D> The type the Details are bound to. | ||
* @author Peter Eastham | ||
*/ | ||
public abstract class AbstractUserDetailsAuthentication<C, D> | ||
implements TypedAuthentication<C, D, UserDetails>, CredentialsContainer { | ||
|
||
private final List<? extends GrantedAuthority> authorities; | ||
|
||
private final UserDetails principal; | ||
|
||
private D details; | ||
|
||
private boolean authenticated = false; | ||
|
||
/** | ||
* Creates a token based on a supplied <tt>UserDetails</tt> Object. | ||
* | ||
* @param principal a nonnull <tt>UserDetails</tt> for the principal. | ||
* @throws IllegalArgumentException if {@code principal.getAuthorities()} is null, or | ||
* any Authorities in it are null. | ||
*/ | ||
public AbstractUserDetailsAuthentication(UserDetails principal) { | ||
Assert.notNull(principal, "Principal Provided cannot be null"); | ||
Assert.notNull(principal.getAuthorities(), "Principal Authorities cannot be null"); | ||
for (GrantedAuthority a : principal.getAuthorities()) { | ||
Assert.notNull(a, "Authorities collection cannot contain any null elements"); | ||
} | ||
this.authorities = List.copyOf(principal.getAuthorities()); | ||
this.principal = principal; | ||
} | ||
|
||
@Override | ||
public Collection<? extends GrantedAuthority> getAuthorities() { | ||
return this.authorities; | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return getPrincipal().getUsername(); | ||
} | ||
|
||
@Override | ||
public boolean isAuthenticated() { | ||
return this.authenticated; | ||
} | ||
|
||
@Override | ||
public void setAuthenticated(boolean authenticated) { | ||
this.authenticated = authenticated; | ||
} | ||
|
||
@Override | ||
public D getDetails() { | ||
return this.details; | ||
} | ||
|
||
public void setDetails(D details) { | ||
this.details = details; | ||
} | ||
|
||
@Override | ||
public UserDetails getPrincipal() { | ||
return this.principal; | ||
} | ||
|
||
/** | ||
* Checks the {@code credentials}, {@code principal} and {@code details} objects, | ||
* invoking the {@code eraseCredentials} method on any which implement | ||
* {@link CredentialsContainer}. | ||
*/ | ||
@Override | ||
public void eraseCredentials() { | ||
eraseSecret(getCredentials()); | ||
eraseSecret(this.principal); | ||
eraseSecret(this.details); | ||
} | ||
|
||
private void eraseSecret(Object secret) { | ||
if (secret instanceof CredentialsContainer cc) { | ||
cc.eraseCredentials(); | ||
} | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (!(obj instanceof AbstractUserDetailsAuthentication<?, ?> test)) { | ||
return false; | ||
} | ||
if (!this.authorities.equals(test.getAuthorities())) { | ||
return false; | ||
} | ||
if ((this.details == null) && (test.getDetails() != null)) { | ||
return false; | ||
} | ||
if ((this.details != null) && (test.getDetails() == null)) { | ||
return false; | ||
} | ||
if ((this.details != null) && (!this.details.equals(test.getDetails()))) { | ||
return false; | ||
} | ||
if ((this.getCredentials() == null) && (test.getCredentials() != null)) { | ||
return false; | ||
} | ||
if ((this.getCredentials() != null) && !this.getCredentials().equals(test.getCredentials())) { | ||
return false; | ||
} | ||
if (this.getPrincipal() == null && test.getPrincipal() != null) { | ||
return false; | ||
} | ||
if (this.getPrincipal() != null && !this.getPrincipal().equals(test.getPrincipal())) { | ||
return false; | ||
} | ||
return this.isAuthenticated() == test.isAuthenticated(); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
int code = 31; | ||
for (GrantedAuthority authority : this.authorities) { | ||
code ^= authority.hashCode(); | ||
} | ||
if (this.getPrincipal() != null) { | ||
code ^= this.getPrincipal().hashCode(); | ||
} | ||
if (this.getCredentials() != null) { | ||
code ^= this.getCredentials().hashCode(); | ||
} | ||
if (this.getDetails() != null) { | ||
code ^= this.getDetails().hashCode(); | ||
} | ||
if (this.isAuthenticated()) { | ||
code ^= -37; | ||
} | ||
return code; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
StringBuilder sb = new StringBuilder(); | ||
sb.append(getClass().getSimpleName()).append(" ["); | ||
sb.append("Principal=").append(getPrincipal()).append(", "); | ||
sb.append("Credentials=[PROTECTED], "); | ||
sb.append("Authenticated=").append(isAuthenticated()).append(", "); | ||
sb.append("Details=").append(getDetails()).append(", "); | ||
sb.append("Granted Authorities=").append(this.authenticated); | ||
sb.append("]"); | ||
return sb.toString(); | ||
} | ||
|
||
} |
85 changes: 85 additions & 0 deletions
85
...org/springframework/security/authentication/UsernamePasswordTypedAuthenticationToken.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package org.springframework.security.authentication; | ||
|
||
import org.springframework.security.core.SpringSecurityCoreVersion; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.util.Assert; | ||
|
||
/** | ||
* A {@link AbstractUserDetailsAuthentication} implementation that is designed for simple | ||
* presentation of a username and password. | ||
* <p> | ||
* | ||
* @param <D> The Details Object the Token can map to. | ||
* @author Peter Eastham | ||
*/ | ||
public class UsernamePasswordTypedAuthenticationToken<D> extends AbstractUserDetailsAuthentication<String, D> { | ||
|
||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; | ||
|
||
private String credentials; | ||
|
||
/** | ||
* Creates a token based on a supplied <tt>UserDetails</tt> instance and | ||
* <tt>String</tt> Object. | ||
* | ||
* @param principal a nonnull <tt>UserDetails</tt> for the principal. | ||
* @throws IllegalArgumentException if {@code principal.getAuthorities()} is null, or | ||
* any Authorities in it are null. | ||
*/ | ||
private UsernamePasswordTypedAuthenticationToken(UserDetails principal, String credentials, boolean authenticated) { | ||
super(principal); | ||
this.credentials = credentials; | ||
super.setAuthenticated(authenticated); | ||
} | ||
|
||
/** | ||
* Factory method that support creation of an unauthenticated | ||
* <code>UsernamePasswordTypedAuthenticationToken</code> | ||
* | ||
* @param principal Nonnull UserDetails object to set to the Principal | ||
* @param credentials Nullable String which represents the User's Password | ||
* @param <D> The Type assigned to the <code>TypedAuthentication::getDetails</code> | ||
* method. | ||
* @return <code>UsernamePasswordTypedAuthenticationToken</code> with false | ||
* isAuthenticated() result. | ||
*/ | ||
public static <D> UsernamePasswordTypedAuthenticationToken<D> unauthenticated(UserDetails principal, | ||
String credentials) { | ||
return new UsernamePasswordTypedAuthenticationToken<>(principal, credentials, false); | ||
} | ||
|
||
/** | ||
* Factory method that support creation of an unauthenticated | ||
* <code>UsernamePasswordTypedAuthenticationToken</code> | ||
* | ||
* @param principal Nonnull UserDetails object to set to the Principal | ||
* @param credentials Nullable String which represents the User's Password | ||
* @param <D> The Type assigned to the <code>TypedAuthentication::getDetails</code> | ||
* method. | ||
* @return <code>UsernamePasswordTypedAuthenticationToken</code> with true | ||
* isAuthenticated() result. | ||
*/ | ||
public static <D> UsernamePasswordTypedAuthenticationToken<D> authenticated(UserDetails principal, | ||
String credentials) { | ||
return new UsernamePasswordTypedAuthenticationToken<>(principal, credentials, true); | ||
} | ||
|
||
@Override | ||
public String getCredentials() { | ||
return this.credentials; | ||
} | ||
|
||
@Override | ||
public void eraseCredentials() { | ||
super.eraseCredentials(); | ||
this.credentials = null; | ||
} | ||
|
||
@Override | ||
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { | ||
Assert.isTrue(!isAuthenticated, | ||
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); | ||
super.setAuthenticated(false); | ||
} | ||
|
||
} |
20 changes: 20 additions & 0 deletions
20
core/src/main/java/org/springframework/security/core/TypedAuthentication.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package org.springframework.security.core; | ||
|
||
/** | ||
* Provided as a wrapper for {@link Authentication} | ||
* | ||
* @author Peter Eastham | ||
* @see Authentication | ||
*/ | ||
public interface TypedAuthentication<C, D, P> extends Authentication { | ||
|
||
@Override | ||
C getCredentials(); | ||
|
||
@Override | ||
D getDetails(); | ||
|
||
@Override | ||
P getPrincipal(); | ||
|
||
} |
75 changes: 75 additions & 0 deletions
75
...pringframework/security/authentication/UsernamePasswordTypedAuthenticationTokenTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package org.springframework.security.authentication; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.springframework.security.core.authority.AuthorityUtils; | ||
import org.springframework.security.core.userdetails.User; | ||
|
||
import static org.assertj.core.api.Assertions.*; | ||
|
||
/** | ||
* Tests {@link UsernamePasswordTypedAuthenticationToken}. | ||
* Replicates {@link UsernamePasswordAuthenticationTokenTests} | ||
* @author Peter Eastham | ||
*/ | ||
public class UsernamePasswordTypedAuthenticationTokenTests { | ||
|
||
@Test | ||
public void authenticatedPropertyContractIsSatisfied() { | ||
User simpleUser = new User("Test", "Password", AuthorityUtils.NO_AUTHORITIES); | ||
UsernamePasswordTypedAuthenticationToken<Object> grantedToken = UsernamePasswordTypedAuthenticationToken | ||
.authenticated(simpleUser, simpleUser.getPassword()); | ||
// check default given we passed some GrantedAuthority[]s (well, we passed empty | ||
// list) | ||
assertThat(grantedToken.isAuthenticated()).isTrue(); | ||
// check explicit set to untrusted (we can safely go from trusted to untrusted, | ||
// but not the reverse) | ||
grantedToken.setAuthenticated(false); | ||
assertThat(!grantedToken.isAuthenticated()).isTrue(); | ||
// Now let's create a UsernamePasswordAuthenticationToken without any | ||
// GrantedAuthority[]s (different constructor) | ||
UsernamePasswordTypedAuthenticationToken<Object> noneGrantedToken = UsernamePasswordTypedAuthenticationToken | ||
.unauthenticated(simpleUser, simpleUser.getPassword()); | ||
assertThat(!noneGrantedToken.isAuthenticated()).isTrue(); | ||
// check we're allowed to still set it to untrusted | ||
noneGrantedToken.setAuthenticated(false); | ||
assertThat(!noneGrantedToken.isAuthenticated()).isTrue(); | ||
// check denied changing it to trusted | ||
assertThatIllegalArgumentException().isThrownBy(() -> noneGrantedToken.setAuthenticated(true)); | ||
} | ||
|
||
@Test | ||
public void gettersReturnCorrectData() { | ||
User simpleUser = new User("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO")); | ||
UsernamePasswordTypedAuthenticationToken<Object> token = UsernamePasswordTypedAuthenticationToken | ||
.authenticated(simpleUser, simpleUser.getPassword()); | ||
assertThat(token.getName()).isEqualTo("Test"); | ||
assertThat(token.getPrincipal().getUsername()).isEqualTo("Test"); | ||
assertThat(token.getCredentials()).isEqualTo("Password"); | ||
assertThat(AuthorityUtils.authorityListToSet(token.getAuthorities())).contains("ROLE_ONE"); | ||
assertThat(AuthorityUtils.authorityListToSet(token.getAuthorities())).contains("ROLE_TWO"); | ||
} | ||
|
||
@Test | ||
public void testNoArgConstructorDoesntExist() throws Exception { | ||
Class<?> clazz = UsernamePasswordTypedAuthenticationToken.class; | ||
assertThatExceptionOfType(NoSuchMethodException.class) | ||
.isThrownBy(() -> clazz.getDeclaredConstructor((Class[]) null)); | ||
} | ||
|
||
@Test | ||
public void unauthenticatedFactoryMethodResultsUnauthenticatedToken() { | ||
User simpleUser = new User("Test", "Password", AuthorityUtils.NO_AUTHORITIES); | ||
UsernamePasswordTypedAuthenticationToken<Object> grantedToken = UsernamePasswordTypedAuthenticationToken | ||
.unauthenticated(simpleUser, simpleUser.getPassword()); | ||
assertThat(grantedToken.isAuthenticated()).isFalse(); | ||
} | ||
|
||
@Test | ||
public void authenticatedFactoryMethodResultsAuthenticatedToken() { | ||
User simpleUser = new User("Test", "Password", AuthorityUtils.NO_AUTHORITIES); | ||
UsernamePasswordTypedAuthenticationToken<Object> grantedToken = UsernamePasswordTypedAuthenticationToken | ||
.authenticated(simpleUser, simpleUser.getPassword()); | ||
assertThat(grantedToken.isAuthenticated()).isTrue(); | ||
} | ||
|
||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another thing to note. I was very torn on if this should be a public or private constructor. In a review of how
UsernamePasswordAuthenticationToken
is handled, I arrived to the conclusion the maximum visibility should beprotected
, however with no sub-classes or other reasons to expose it, I left it as private.