mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-10-23 10:48:51 +00:00
Polish Core Authentication Builders
Issue gh-17861
This commit is contained in:
parent
18fbf88993
commit
c66a028332
@ -197,14 +197,22 @@ public abstract class AbstractAuthenticationToken implements Authentication, Cre
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A common abstract implementation of {@link Authentication.Builder}. It implements
|
||||||
|
* the builder methods that correspond to the {@link Authentication} methods that
|
||||||
|
* {@link AbstractAuthenticationToken} implements
|
||||||
|
*
|
||||||
|
* @param <B>
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
protected abstract static class AbstractAuthenticationBuilder<B extends AbstractAuthenticationBuilder<B>>
|
protected abstract static class AbstractAuthenticationBuilder<B extends AbstractAuthenticationBuilder<B>>
|
||||||
implements Authentication.Builder<B> {
|
implements Authentication.Builder<B> {
|
||||||
|
|
||||||
protected boolean authenticated;
|
private boolean authenticated;
|
||||||
|
|
||||||
protected @Nullable Object details;
|
private @Nullable Object details;
|
||||||
|
|
||||||
protected final Collection<GrantedAuthority> authorities;
|
private final Collection<GrantedAuthority> authorities;
|
||||||
|
|
||||||
protected AbstractAuthenticationBuilder(AbstractAuthenticationToken token) {
|
protected AbstractAuthenticationBuilder(AbstractAuthenticationToken token) {
|
||||||
this.authorities = new LinkedHashSet<>(token.getAuthorities());
|
this.authorities = new LinkedHashSet<>(token.getAuthorities());
|
||||||
|
@ -20,7 +20,6 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
@ -99,8 +98,8 @@ public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Builder toBuilder() {
|
public Builder<?> toBuilder() {
|
||||||
return new Builder(this);
|
return new Builder<>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -122,7 +121,7 @@ public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder preserving the concrete {@link Authentication} type
|
* A builder of {@link RememberMeAuthenticationToken} instances
|
||||||
*
|
*
|
||||||
* @since 7.0
|
* @since 7.0
|
||||||
*/
|
*/
|
||||||
@ -145,8 +144,13 @@ public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
|
|||||||
return (B) this;
|
return (B) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public B keyHash(int keyHash) {
|
/**
|
||||||
this.keyHash = keyHash;
|
* Use this key
|
||||||
|
* @param key the key to use
|
||||||
|
* @return the {@link Builder} for further configurations
|
||||||
|
*/
|
||||||
|
public B key(String key) {
|
||||||
|
this.keyHash = key.hashCode();
|
||||||
return (B) this;
|
return (B) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@ -87,7 +86,7 @@ public class TestingAuthenticationToken extends AbstractAuthenticationToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder preserving the concrete {@link Authentication} type
|
* A builder of {@link TestingAuthenticationToken} instances
|
||||||
*
|
*
|
||||||
* @since 7.0
|
* @since 7.0
|
||||||
*/
|
*/
|
||||||
|
@ -20,7 +20,6 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
@ -137,15 +136,15 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder preserving the concrete {@link Authentication} type
|
* A builder of {@link UsernamePasswordAuthenticationToken} instances
|
||||||
*
|
*
|
||||||
* @since 7.0
|
* @since 7.0
|
||||||
*/
|
*/
|
||||||
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
||||||
|
|
||||||
protected @Nullable Object principal;
|
private @Nullable Object principal;
|
||||||
|
|
||||||
protected @Nullable Object credentials;
|
private @Nullable Object credentials;
|
||||||
|
|
||||||
protected Builder(UsernamePasswordAuthenticationToken token) {
|
protected Builder(UsernamePasswordAuthenticationToken token) {
|
||||||
super(token);
|
super(token);
|
||||||
|
@ -23,9 +23,7 @@ import javax.security.auth.login.LoginContext;
|
|||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UsernamePasswordAuthenticationToken extension to carry the Jaas LoginContext that the
|
* UsernamePasswordAuthenticationToken extension to carry the Jaas LoginContext that the
|
||||||
@ -65,7 +63,7 @@ public class JaasAuthenticationToken extends UsernamePasswordAuthenticationToken
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder preserving the concrete {@link Authentication} type
|
* A builder of {@link JaasAuthenticationToken} instances
|
||||||
*
|
*
|
||||||
* @since 7.0
|
* @since 7.0
|
||||||
*/
|
*/
|
||||||
@ -81,7 +79,7 @@ public class JaasAuthenticationToken extends UsernamePasswordAuthenticationToken
|
|||||||
/**
|
/**
|
||||||
* Use this {@link LoginContext}
|
* Use this {@link LoginContext}
|
||||||
* @param loginContext the {@link LoginContext} to use
|
* @param loginContext the {@link LoginContext} to use
|
||||||
* @return the {@link Builder} for further configuration
|
* @return the {@link Builder} for further configurations
|
||||||
*/
|
*/
|
||||||
public B loginContext(LoginContext loginContext) {
|
public B loginContext(LoginContext loginContext) {
|
||||||
this.loginContext = loginContext;
|
this.loginContext = loginContext;
|
||||||
@ -90,7 +88,6 @@ public class JaasAuthenticationToken extends UsernamePasswordAuthenticationToken
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JaasAuthenticationToken build() {
|
public JaasAuthenticationToken build() {
|
||||||
Assert.notNull(this.principal, "principal cannot be null");
|
|
||||||
return new JaasAuthenticationToken(this);
|
return new JaasAuthenticationToken(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,9 @@ public class OneTimeTokenAuthentication extends AbstractAuthenticationToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder for constructing a {@link OneTimeTokenAuthentication} instance
|
* A builder of {@link OneTimeTokenAuthentication} instances
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
*/
|
*/
|
||||||
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
||||||
|
|
||||||
@ -77,7 +79,7 @@ public class OneTimeTokenAuthentication extends AbstractAuthenticationToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this principal
|
* Use this principal.
|
||||||
* @return the {@link Builder} for further configuration
|
* @return the {@link Builder} for further configuration
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -138,7 +138,19 @@ public interface Authentication extends Principal, Serializable {
|
|||||||
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
|
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an {@link Builder} based on this instance
|
* Return an {@link Builder} based on this instance. By default, returns a builder
|
||||||
|
* that builds a {@link SimpleAuthentication}.
|
||||||
|
* <p>
|
||||||
|
* Although a {@code default} method, all {@link Authentication} implementations
|
||||||
|
* should implement this. The reason is to ensure that the {@link Authentication} type
|
||||||
|
* is preserved when {@link Builder#build} is invoked. This is especially important in
|
||||||
|
* the event that your authentication implementation contains custom fields.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* This isn't strictly necessary since it is recommended that applications code to the
|
||||||
|
* {@link Authentication} interface and that custom information is often contained in
|
||||||
|
* the {@link Authentication#getPrincipal} value.
|
||||||
|
* </p>
|
||||||
* @return an {@link Builder} for building a new {@link Authentication} based on this
|
* @return an {@link Builder} for building a new {@link Authentication} based on this
|
||||||
* instance
|
* instance
|
||||||
* @since 7.0
|
* @since 7.0
|
||||||
@ -155,17 +167,72 @@ public interface Authentication extends Principal, Serializable {
|
|||||||
*/
|
*/
|
||||||
interface Builder<B extends Builder<B>> {
|
interface Builder<B extends Builder<B>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutate the authorities with this {@link Consumer}.
|
||||||
|
* <p>
|
||||||
|
* Note that since a non-empty set of authorities implies an
|
||||||
|
* {@link Authentication} is authenticated, this method also marks the
|
||||||
|
* authentication as {@link #authenticated} by default.
|
||||||
|
* </p>
|
||||||
|
* @param authorities a consumer that receives the full set of authorities
|
||||||
|
* @return the {@link Builder} for additional configuration
|
||||||
|
* @see Authentication#getAuthorities
|
||||||
|
*/
|
||||||
B authorities(Consumer<Collection<GrantedAuthority>> authorities);
|
B authorities(Consumer<Collection<GrantedAuthority>> authorities);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this credential.
|
||||||
|
* <p>
|
||||||
|
* Note that since some credentials are insecure to store, this method is
|
||||||
|
* implemented as unsupported by default. Only implement or use this method if you
|
||||||
|
* support secure storage of the credential or if your implementation also
|
||||||
|
* implements {@link CredentialsContainer} and the credentials are thereby erased.
|
||||||
|
* </p>
|
||||||
|
* @param credentials the credentials to use
|
||||||
|
* @return the {@link Builder} for additional configuration
|
||||||
|
* @see Authentication#getCredentials
|
||||||
|
*/
|
||||||
default B credentials(@Nullable Object credentials) {
|
default B credentials(@Nullable Object credentials) {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
String.format("%s does not store credentials", this.getClass().getSimpleName()));
|
String.format("%s does not store credentials", this.getClass().getSimpleName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this details object.
|
||||||
|
* <p>
|
||||||
|
* Implementations may choose to use these {@code details} in combination with any
|
||||||
|
* principal from the pre-existing {@link Authentication} instance.
|
||||||
|
* </p>
|
||||||
|
* @param details the details to use
|
||||||
|
* @return the {@link Builder} for additional configuration
|
||||||
|
* @see Authentication#getDetails
|
||||||
|
*/
|
||||||
B details(@Nullable Object details);
|
B details(@Nullable Object details);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this principal.
|
||||||
|
* <p>
|
||||||
|
* Note that in many cases, the principal is strongly-typed. Implementations may
|
||||||
|
* choose to do a type check and are not necessarily expected to allow any object
|
||||||
|
* as a principal.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Implementations may choose to use this {@code principal} in combination with
|
||||||
|
* any principal from the pre-existing {@link Authentication} instance.
|
||||||
|
* </p>
|
||||||
|
* @param principal the principal to use
|
||||||
|
* @return the {@link Builder} for additional configuration
|
||||||
|
* @see Authentication#getPrincipal
|
||||||
|
*/
|
||||||
B principal(@Nullable Object principal);
|
B principal(@Nullable Object principal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark this authentication as authenticated or not
|
||||||
|
* @param authenticated whether this is an authenticated {@link Authentication}
|
||||||
|
* instance
|
||||||
|
* @return the {@link Builder} for additional configuration
|
||||||
|
* @see Authentication#isAuthenticated
|
||||||
|
*/
|
||||||
B authenticated(boolean authenticated);
|
B authenticated(boolean authenticated);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,10 +18,8 @@ package org.springframework.security.authentication;
|
|||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jspecify.annotations.Nullable;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken.AbstractAuthenticationBuilder;
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
|
||||||
@ -40,20 +38,15 @@ class AbstractAuthenticationBuilderTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final class TestAbstractAuthenticationBuilder
|
private static final class TestAbstractAuthenticationBuilder
|
||||||
extends AbstractAuthenticationBuilder<TestAbstractAuthenticationBuilder> {
|
extends TestingAuthenticationToken.Builder<TestAbstractAuthenticationBuilder> {
|
||||||
|
|
||||||
private TestAbstractAuthenticationBuilder(TestingAuthenticationToken token) {
|
private TestAbstractAuthenticationBuilder(TestingAuthenticationToken token) {
|
||||||
super(token);
|
super(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public TestAbstractAuthenticationBuilder principal(@Nullable Object principal) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TestingAuthenticationToken build() {
|
public TestingAuthenticationToken build() {
|
||||||
return new TestingAuthenticationToken("user", "password", this.authorities);
|
return new TestingAuthenticationToken(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package org.springframework.security.authentication.rememberme;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ import org.springframework.security.authentication.RememberMeAuthenticationToken
|
|||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
@ -96,4 +98,21 @@ public class RememberMeAuthenticationTokenTests {
|
|||||||
assertThat(!token.isAuthenticated()).isTrue();
|
assertThat(!token.isAuthenticated()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toBuilderWhenApplyThenCopies() {
|
||||||
|
RememberMeAuthenticationToken factorOne = new RememberMeAuthenticationToken("key", PasswordEncodedUser.user(),
|
||||||
|
AuthorityUtils.createAuthorityList("FACTOR_ONE"));
|
||||||
|
RememberMeAuthenticationToken factorTwo = new RememberMeAuthenticationToken("yek", PasswordEncodedUser.admin(),
|
||||||
|
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
|
||||||
|
RememberMeAuthenticationToken authentication = factorOne.toBuilder()
|
||||||
|
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||||
|
.key("yek")
|
||||||
|
.principal(factorTwo.getPrincipal())
|
||||||
|
.build();
|
||||||
|
Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
|
||||||
|
assertThat(authentication.getKeyHash()).isEqualTo(factorTwo.getKeyHash());
|
||||||
|
assertThat(authentication.getPrincipal()).isEqualTo(factorTwo.getPrincipal());
|
||||||
|
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user