Polish Builders

- Added remaining properties
- Removed apply method since Spring Security isn't using
it right now
- Made builders extensible since the authentications are
extensible

Issue gh-17861
This commit is contained in:
Josh Cummings 2025-08-29 13:13:09 -06:00
parent 44fef786aa
commit a0fe6a5fee
37 changed files with 662 additions and 379 deletions

View File

@ -20,7 +20,7 @@ import java.io.Serializable;
import java.util.Collection;
import org.apereo.cas.client.validation.Assertion;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
@ -106,6 +106,19 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen
setAuthenticated(true);
}
protected CasAuthenticationToken(Builder<?> builder) {
super(builder);
Assert.isTrue(!"".equals(builder.principal), "principal cannot be null or empty");
Assert.notNull(!"".equals(builder.credentials), "credentials cannot be null or empty");
Assert.notNull(builder.userDetails, "userDetails cannot be null");
Assert.notNull(builder.assertion, "assertion cannot be null");
this.keyHash = builder.keyHash;
this.principal = builder.principal;
this.credentials = builder.credentials;
this.userDetails = builder.userDetails;
this.assertion = builder.assertion;
}
private static Integer extractKeyHash(String key) {
Assert.hasLength(key, "key cannot be null or empty");
return key.hashCode();
@ -156,8 +169,8 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen
}
@Override
public Builder toBuilder() {
return new Builder().apply(this);
public Builder<?> toBuilder() {
return new Builder<>(this);
}
@Override
@ -174,7 +187,7 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen
*
* @since 7.0
*/
public static final class Builder extends AbstractAuthenticationBuilder<@NonNull CasAuthenticationToken, Builder> {
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<Object, Object, B> {
private Integer keyHash;
@ -186,47 +199,47 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen
private Assertion assertion;
private Builder() {
protected Builder(CasAuthenticationToken token) {
super(token);
this.keyHash = token.keyHash;
this.principal = token.principal;
this.credentials = token.credentials;
this.userDetails = token.userDetails;
this.assertion = token.assertion;
}
public Builder apply(CasAuthenticationToken authentication) {
return super.apply(authentication).keyHash(authentication.keyHash)
.principal(authentication.principal)
.credentials(authentication.credentials)
.userDetails(authentication.userDetails)
.assertion(authentication.assertion);
}
public Builder keyHash(Integer keyHash) {
public B keyHash(Integer keyHash) {
this.keyHash = keyHash;
return this;
}
public Builder principal(Object principal) {
this.principal = principal;
return this;
}
public Builder credentials(Object credentials) {
this.credentials = credentials;
return this;
}
public Builder userDetails(UserDetails userDetails) {
this.userDetails = userDetails;
return this;
}
public Builder assertion(Assertion assertion) {
this.assertion = assertion;
return this;
return (B) this;
}
@Override
protected @NonNull CasAuthenticationToken build(Collection<GrantedAuthority> authorities) {
return new CasAuthenticationToken(this.keyHash, this.principal, this.credentials, authorities,
this.userDetails, this.assertion);
public B principal(@Nullable Object principal) {
Assert.notNull(principal, "principal cannot be null");
this.principal = principal;
return (B) this;
}
@Override
public B credentials(@Nullable Object credentials) {
Assert.notNull(credentials, "credentials cannot be null");
this.credentials = credentials;
return (B) this;
}
public B userDetails(UserDetails userDetails) {
this.userDetails = userDetails;
return (B) this;
}
public B assertion(Assertion assertion) {
this.assertion = assertion;
return (B) this;
}
@Override
public CasAuthenticationToken build() {
return new CasAuthenticationToken(this);
}
}

View File

@ -22,6 +22,7 @@ import java.util.Collection;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
@ -52,7 +53,7 @@ public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationT
*
*/
public CasServiceTicketAuthenticationToken(String identifier, Object credentials) {
super(null);
super((Collection<? extends GrantedAuthority>) null);
this.identifier = identifier;
this.credentials = credentials;
setAuthenticated(false);
@ -75,6 +76,12 @@ public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationT
super.setAuthenticated(true);
}
protected CasServiceTicketAuthenticationToken(Builder<?> builder) {
super(builder);
this.identifier = builder.principal;
this.credentials = builder.credentials;
}
public static CasServiceTicketAuthenticationToken stateful(Object credentials) {
return new CasServiceTicketAuthenticationToken(CAS_STATEFUL_IDENTIFIER, credentials);
}
@ -110,4 +117,46 @@ public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationT
this.credentials = null;
}
public Builder<?> toBuilder() {
return new Builder<>(this);
}
/**
* A builder preserving the concrete {@link Authentication} type
*
* @since 7.0
*/
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<String, Object, B> {
private String principal;
private @Nullable Object credentials;
protected Builder(CasServiceTicketAuthenticationToken token) {
super(token);
this.principal = token.identifier;
this.credentials = token.credentials;
}
@Override
public B principal(@Nullable String principal) {
Assert.notNull(principal, "principal cannot be null");
this.principal = principal;
return (B) this;
}
@Override
public B credentials(@Nullable Object credentials) {
Assert.notNull(credentials, "credentials cannot be null");
this.credentials = credentials;
return (B) this;
}
@Override
public CasServiceTicketAuthenticationToken build() {
return new CasServiceTicketAuthenticationToken(this);
}
}
}

View File

@ -165,7 +165,14 @@ public class CasAuthenticationTokenTests {
Assertion assertionTwo = new AssertionImpl("test");
CasAuthenticationToken factorTwo = new CasAuthenticationToken("yek", "bob", "ssap",
AuthorityUtils.createAuthorityList("FACTOR_TWO"), PasswordEncodedUser.admin(), assertionTwo);
CasAuthenticationToken authentication = factorOne.toBuilder().apply(factorTwo).build();
CasAuthenticationToken authentication = factorOne.toBuilder()
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
.keyHash(factorTwo.getKeyHash())
.principal(factorTwo.getPrincipal())
.credentials(factorTwo.getCredentials())
.userDetails(factorTwo.getUserDetails())
.assertion(factorTwo.getAssertion())
.build();
Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
assertThat(authentication.getKeyHash()).isEqualTo(factorTwo.getKeyHash());
assertThat(authentication.getPrincipal()).isEqualTo(factorTwo.getPrincipal());

View File

@ -16,6 +16,8 @@
package org.springframework.security.config.annotation.web.configurers;
import java.util.Collection;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -31,6 +33,7 @@ import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
@ -113,7 +116,7 @@ public class SessionManagementConfigurerTransientAuthenticationTests {
static class SomeTransientAuthentication extends AbstractAuthenticationToken {
SomeTransientAuthentication() {
super(null);
super((Collection<? extends GrantedAuthority>) null);
}
@Override

View File

@ -16,6 +16,8 @@
package org.springframework.security.config.http;
import java.util.Collection;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -26,6 +28,7 @@ import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@ -82,7 +85,7 @@ public class SessionManagementConfigTransientAuthenticationTests {
static class SomeTransientAuthentication extends AbstractAuthenticationToken {
SomeTransientAuthentication() {
super(null);
super((Collection<? extends GrantedAuthority>) null);
}
@Override

View File

@ -20,7 +20,7 @@ import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
@ -43,6 +43,8 @@ import org.springframework.util.Assert;
*/
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
private static final long serialVersionUID = -3194696462184782834L;
private final Collection<GrantedAuthority> authorities;
private @Nullable Object details;
@ -65,6 +67,12 @@ public abstract class AbstractAuthenticationToken implements Authentication, Cre
this.authorities = Collections.unmodifiableList(new ArrayList<>(authorities));
}
protected AbstractAuthenticationToken(AbstractAuthenticationBuilder<?, ?, ?> builder) {
this(builder.authorities);
this.authenticated = builder.authenticated;
this.details = builder.details;
}
@Override
public Collection<GrantedAuthority> getAuthorities() {
return this.authorities;
@ -187,36 +195,40 @@ public abstract class AbstractAuthenticationToken implements Authentication, Cre
return sb.toString();
}
protected abstract static class AbstractAuthenticationBuilder<A extends Authentication, B extends AbstractAuthenticationBuilder<A, B>>
implements Builder<A, B> {
protected abstract static class AbstractAuthenticationBuilder<P, C, B extends AbstractAuthenticationBuilder<P, C, B>>
implements Authentication.Builder<P, C, B> {
private final Collection<GrantedAuthority> authorities = new HashSet<>();
protected boolean authenticated;
protected AbstractAuthenticationBuilder() {
protected @Nullable Object details;
protected final Collection<GrantedAuthority> authorities;
protected AbstractAuthenticationBuilder(AbstractAuthenticationToken token) {
this.authorities = new LinkedHashSet<>(token.getAuthorities());
this.authenticated = token.isAuthenticated();
this.details = token.getDetails();
}
@Override
public B authenticated(boolean authenticated) {
this.authenticated = authenticated;
return (B) this;
}
@Override
public B details(@Nullable Object details) {
this.details = details;
return (B) this;
}
@Override
public B authorities(Consumer<Collection<GrantedAuthority>> authorities) {
authorities.accept(this.authorities);
this.authenticated = true;
return (B) this;
}
@Override
public A build() {
return build(this.authorities);
}
@Override
public B apply(Authentication token) {
Assert.isTrue(token.isAuthenticated(), "cannot mutate an unauthenticated token");
Assert.notNull(token.getPrincipal(), "principal cannot be null");
this.authorities.addAll(token.getAuthorities());
return (B) this;
}
protected abstract A build(Collection<GrantedAuthority> authorities);
}
}

View File

@ -74,6 +74,12 @@ public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
setAuthenticated(true);
}
protected RememberMeAuthenticationToken(Builder<?> builder) {
super(builder);
this.keyHash = builder.keyHash;
this.principal = builder.principal;
}
/**
* Always returns an empty <code>String</code>
* @return an empty String
@ -94,7 +100,7 @@ public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
@Override
public Builder toBuilder() {
return new Builder().apply(this);
return new Builder(this);
}
@Override
@ -120,35 +126,33 @@ public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
*
* @since 7.0
*/
public static final class Builder extends AbstractAuthenticationBuilder<RememberMeAuthenticationToken, Builder> {
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<Object, Object, B> {
private @Nullable Integer keyHash;
private Integer keyHash;
private @Nullable Object principal;
private Object principal;
private Builder() {
}
public Builder apply(RememberMeAuthenticationToken token) {
return super.apply(token).keyHash(token.getKeyHash()).principal(token.getPrincipal());
}
public Builder principal(Object principal) {
this.principal = principal;
return this;
}
public Builder keyHash(int keyHash) {
this.keyHash = keyHash;
return this;
protected Builder(RememberMeAuthenticationToken token) {
super(token);
this.keyHash = token.getKeyHash();
this.principal = token.getPrincipal();
}
@Override
protected RememberMeAuthenticationToken build(Collection<GrantedAuthority> authorities) {
Assert.notNull(this.keyHash, "keyHash cannot be null");
Assert.notNull(this.principal, "principal cannot be null");
return new RememberMeAuthenticationToken(this.keyHash, this.principal, authorities);
public B principal(@Nullable Object principal) {
Assert.notNull(principal, "principal cannot be null");
this.principal = principal;
return (B) this;
}
public B keyHash(int keyHash) {
this.keyHash = keyHash;
return (B) this;
}
@Override
public RememberMeAuthenticationToken build() {
return new RememberMeAuthenticationToken(this);
}
}

View File

@ -43,7 +43,7 @@ public class TestingAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
public TestingAuthenticationToken(Object principal, Object credentials) {
super(null);
super((Collection<? extends GrantedAuthority>) null);
this.principal = principal;
this.credentials = credentials;
}
@ -65,6 +65,12 @@ public class TestingAuthenticationToken extends AbstractAuthenticationToken {
setAuthenticated(true);
}
protected TestingAuthenticationToken(Builder<?> builder) {
super(builder);
this.principal = builder.principal;
this.credentials = builder.credentials;
}
@Override
public Object getCredentials() {
return this.credentials;
@ -76,8 +82,8 @@ public class TestingAuthenticationToken extends AbstractAuthenticationToken {
}
@Override
public Builder toBuilder() {
return new Builder().apply(this);
public Builder<?> toBuilder() {
return new Builder<>(this);
}
/**
@ -85,36 +91,35 @@ public class TestingAuthenticationToken extends AbstractAuthenticationToken {
*
* @since 7.0
*/
public static final class Builder extends AbstractAuthenticationBuilder<TestingAuthenticationToken, Builder> {
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<Object, Object, B> {
private @Nullable Object principal;
private Object principal;
private @Nullable Object credentials;
private Object credentials;
private Builder() {
}
public Builder apply(TestingAuthenticationToken authentication) {
return super.apply(authentication).principal(authentication.getPrincipal())
.credentials(authentication.getCredentials());
}
public Builder principal(Object principal) {
this.principal = principal;
return this;
}
public Builder credentials(Object credentials) {
this.credentials = credentials;
return this;
protected Builder(TestingAuthenticationToken token) {
super(token);
this.principal = token.principal;
this.credentials = token.credentials;
}
@Override
protected TestingAuthenticationToken build(Collection<GrantedAuthority> authorities) {
Assert.notNull(this.principal, "principal cannot be null");
Assert.notNull(this.credentials, "credentials cannot be null");
return new TestingAuthenticationToken(this.principal, this.credentials, authorities);
public B principal(@Nullable Object principal) {
Assert.notNull(principal, "principal cannot be null");
this.principal = principal;
return (B) this;
}
@Override
public B credentials(@Nullable Object credentials) {
Assert.notNull(credentials, "credentials cannot be null");
this.credentials = credentials;
return (B) this;
}
@Override
public TestingAuthenticationToken build() {
return new TestingAuthenticationToken(this);
}
}

View File

@ -51,7 +51,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
*
*/
public UsernamePasswordAuthenticationToken(@Nullable Object principal, @Nullable Object credentials) {
super(null);
super((Collection<? extends GrantedAuthority>) null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
@ -74,6 +74,12 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
super.setAuthenticated(true); // must use super, as we override
}
protected UsernamePasswordAuthenticationToken(Builder<?> builder) {
super(builder);
this.principal = builder.principal;
this.credentials = builder.credentials;
}
/**
* This factory method can be safely used by any code that wishes to create a
* unauthenticated <code>UsernamePasswordAuthenticationToken</code>.
@ -126,8 +132,8 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
}
@Override
public Builder<?, ?> toBuilder() {
return new Builder<>().apply(this);
public Builder<?> toBuilder() {
return new Builder<>(this);
}
/**
@ -135,35 +141,34 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
*
* @since 7.0
*/
public static class Builder<A extends UsernamePasswordAuthenticationToken, B extends Builder<A, B>>
extends AbstractAuthenticationBuilder<A, B> {
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<Object, Object, B> {
private @Nullable Object principal;
protected @Nullable Object principal;
private @Nullable Object credentials;
protected @Nullable Object credentials;
protected Builder() {
protected Builder(UsernamePasswordAuthenticationToken token) {
super(token);
this.principal = token.principal;
this.credentials = token.credentials;
}
public B apply(UsernamePasswordAuthenticationToken authentication) {
return super.apply(authentication).principal(authentication.getPrincipal())
.credentials(authentication.getCredentials());
}
public B principal(Object principal) {
@Override
public B principal(@Nullable Object principal) {
Assert.notNull(principal, "principal cannot be null");
this.principal = principal;
return (B) this;
}
@Override
public B credentials(@Nullable Object credentials) {
this.credentials = credentials;
return (B) this;
}
@Override
protected A build(Collection<GrantedAuthority> authorities) {
Assert.notNull(this.principal, "principal cannot be null");
return (A) new UsernamePasswordAuthenticationToken(this.principal, this.credentials, authorities);
public UsernamePasswordAuthenticationToken build() {
return new UsernamePasswordAuthenticationToken(this);
}
}

View File

@ -16,7 +16,6 @@
package org.springframework.security.authentication.jaas;
import java.util.Collection;
import java.util.List;
import javax.security.auth.login.LoginContext;
@ -51,13 +50,18 @@ public class JaasAuthenticationToken extends UsernamePasswordAuthenticationToken
this.loginContext = loginContext;
}
protected JaasAuthenticationToken(Builder<?> builder) {
super(builder);
this.loginContext = builder.loginContext;
}
public LoginContext getLoginContext() {
return this.loginContext;
}
@Override
public Builder toBuilder() {
return new Builder().apply(this);
public Builder<?> toBuilder() {
return new Builder<>(this);
}
/**
@ -65,17 +69,13 @@ public class JaasAuthenticationToken extends UsernamePasswordAuthenticationToken
*
* @since 7.0
*/
public static final class Builder
extends UsernamePasswordAuthenticationToken.Builder<JaasAuthenticationToken, Builder> {
public static class Builder<B extends Builder<B>> extends UsernamePasswordAuthenticationToken.Builder<B> {
private @Nullable LoginContext loginContext;
private LoginContext loginContext;
private Builder() {
}
public Builder apply(JaasAuthenticationToken authentication) {
return super.apply(authentication).loginContext(authentication.getLoginContext());
protected Builder(JaasAuthenticationToken token) {
super(token);
this.loginContext = token.getLoginContext();
}
/**
@ -83,17 +83,15 @@ public class JaasAuthenticationToken extends UsernamePasswordAuthenticationToken
* @param loginContext the {@link LoginContext} to use
* @return the {@link Builder} for further configuration
*/
public Builder loginContext(LoginContext loginContext) {
public B loginContext(LoginContext loginContext) {
this.loginContext = loginContext;
return this;
return (B) this;
}
@Override
protected JaasAuthenticationToken build(Collection<GrantedAuthority> authorities) {
UsernamePasswordAuthenticationToken token = super.build(authorities);
Assert.notNull(this.loginContext, "loginContext cannot be null");
return new JaasAuthenticationToken(token.getPrincipal(), token.getCredentials(),
(List<GrantedAuthority>) token.getAuthorities(), this.loginContext);
public JaasAuthenticationToken build() {
Assert.notNull(this.principal, "principal cannot be null");
return new JaasAuthenticationToken(this);
}
}

View File

@ -44,6 +44,11 @@ public class OneTimeTokenAuthentication extends AbstractAuthenticationToken {
setAuthenticated(true);
}
protected OneTimeTokenAuthentication(Builder<?> builder) {
super(builder);
this.principal = builder.principal;
}
@Override
public Object getPrincipal() {
return this.principal;
@ -55,42 +60,36 @@ public class OneTimeTokenAuthentication extends AbstractAuthenticationToken {
}
@Override
public Builder toBuilder() {
return new Builder().apply(this);
public Builder<?> toBuilder() {
return new Builder<>(this);
}
/**
* A builder for constructing a {@link OneTimeTokenAuthentication} instance
*/
public static final class Builder extends AbstractAuthenticationBuilder<OneTimeTokenAuthentication, Builder> {
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<Object, Object, B> {
private @Nullable Object principal;
private Object principal;
private Builder() {
}
/**
* Apply this {@link OneTimeTokenAuthentication}
* @return the {@link Builder} for further configuration
*/
public Builder apply(OneTimeTokenAuthentication authentication) {
return super.apply(authentication).principal(authentication.principal);
protected Builder(OneTimeTokenAuthentication token) {
super(token);
this.principal = token.principal;
}
/**
* Use this principal
* @return the {@link Builder} for further configuration
*/
public Builder principal(Object principal) {
@Override
public B principal(@Nullable Object principal) {
Assert.notNull(principal, "principal cannot be null");
this.principal = principal;
return this;
return (B) this;
}
@Override
protected OneTimeTokenAuthentication build(Collection<GrantedAuthority> authorities) {
Assert.notNull(this.principal, "principal cannot be null");
return new OneTimeTokenAuthentication(this.principal, authorities);
public OneTimeTokenAuthentication build() {
return new OneTimeTokenAuthentication(this);
}
}

View File

@ -16,7 +16,6 @@
package org.springframework.security.core;
import java.io.Serial;
import java.io.Serializable;
import java.security.Principal;
import java.util.Collection;
@ -26,7 +25,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
/**
* Represents the token for an authentication request or for an authenticated principal
@ -57,9 +55,6 @@ import org.springframework.util.Assert;
*/
public interface Authentication extends Principal, Serializable {
@Serial
long serialVersionUID = -3884394378624019849L;
/**
* Set by an <code>AuthenticationManager</code> to indicate the authorities that the
* principal has been granted. Note that classes should not rely on this value as
@ -148,43 +143,36 @@ public interface Authentication extends Principal, Serializable {
* instance
* @since 7.0
*/
default Builder<?, ?> toBuilder() {
return new NoopAuthenticationBuilder<>(this);
default Builder<?, ?, ?> toBuilder() {
return new NoopAuthenticationBuilder(this);
}
/**
* A builder based on a given {@link Authentication} instance
*
* @param <A> the type of {@link Authentication}
* @author Josh Cummings
* @since 7.0
*/
interface Builder<A extends Authentication, B extends Builder<A, B>> {
interface Builder<P, C, B extends Builder<P, C, B>> {
/**
* Apply this {@link Authentication} to the builder.
* <p>
* By default, this method adds the authorities from {@code authentication} to
* this builder
* @return the {@link Builder} for further configuration
*/
default B apply(Authentication authentication) {
Assert.isTrue(authentication.isAuthenticated(), "cannot apply an unauthenticated token");
return authorities((a) -> a.addAll(authentication.getAuthorities()));
B authorities(Consumer<Collection<GrantedAuthority>> authorities);
default B credentials(@Nullable C credentials) {
throw new UnsupportedOperationException(
String.format("%s does not store credentials", this.getClass().getSimpleName()));
}
/**
* Apply these authorities to the builder.
* @param authorities the authorities to apply
* @return the {@link Builder} for further configuration
*/
B authorities(Consumer<Collection<GrantedAuthority>> authorities);
B details(@Nullable Object details);
B principal(@Nullable P principal);
B authenticated(boolean authenticated);
/**
* Build an {@link Authentication} instance
* @return the {@link Authentication} instance
*/
A build();
Authentication build();
}

View File

@ -19,34 +19,50 @@ package org.springframework.security.core;
import java.util.Collection;
import java.util.function.Consumer;
import org.springframework.util.Assert;
import org.jspecify.annotations.Nullable;
/**
* An adapter implementation of {@link Authentication.Builder} that provides a no-op
* implementation for the principal, credentials, and authorities
*
* @param <A> the type of {@link Authentication}
* @author Josh Cummings
* @since 7.0
*/
class NoopAuthenticationBuilder<A extends Authentication>
implements Authentication.Builder<A, NoopAuthenticationBuilder<A>> {
class NoopAuthenticationBuilder implements Authentication.Builder<Object, Object, NoopAuthenticationBuilder> {
private A original;
private Authentication original;
NoopAuthenticationBuilder(A authentication) {
Assert.isTrue(authentication.isAuthenticated(), "cannot mutate an unauthenticated token");
Assert.notNull(authentication.getPrincipal(), "principal cannot be null");
NoopAuthenticationBuilder(Authentication authentication) {
this.original = authentication;
}
@Override
public NoopAuthenticationBuilder<A> authorities(Consumer<Collection<GrantedAuthority>> authorities) {
public NoopAuthenticationBuilder authenticated(boolean authenticated) {
return this;
}
@Override
public A build() {
public NoopAuthenticationBuilder principal(@Nullable Object principal) {
return this;
}
@Override
public NoopAuthenticationBuilder details(@Nullable Object details) {
return this;
}
@Override
public NoopAuthenticationBuilder credentials(@Nullable Object credentials) {
return this;
}
@Override
public NoopAuthenticationBuilder authorities(Consumer<Collection<GrantedAuthority>> authorities) {
return this;
}
@Override
public Authentication build() {
return this.original;
}

View File

@ -16,44 +16,44 @@
package org.springframework.security.authentication;
import java.util.Collection;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.AbstractAuthenticationToken.AbstractAuthenticationBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
class AbstractAuthenticationBuilderTests {
@Test
void applyWhenUnauthenticatedThenErrors() {
TestAbstractAuthenticationBuilder builder = new TestAbstractAuthenticationBuilder();
TestingAuthenticationToken unauthenticated = new TestingAuthenticationToken("user", "password");
assertThatIllegalArgumentException().isThrownBy(() -> builder.apply(unauthenticated));
}
@Test
void applyWhenAuthoritiesThenAdds() {
TestAbstractAuthenticationBuilder builder = new TestAbstractAuthenticationBuilder();
TestingAuthenticationToken factorOne = new TestingAuthenticationToken("user", "pass", "FACTOR_ONE");
TestingAuthenticationToken factorTwo = new TestingAuthenticationToken("user", "pass", "FACTOR_TWO");
Authentication result = builder.apply(factorOne).apply(factorTwo).build();
TestAbstractAuthenticationBuilder builder = new TestAbstractAuthenticationBuilder(factorOne);
Authentication result = builder.authorities((a) -> a.addAll(factorTwo.getAuthorities())).build();
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
}
private static final class TestAbstractAuthenticationBuilder
extends AbstractAuthenticationBuilder<Authentication, TestAbstractAuthenticationBuilder> {
extends AbstractAuthenticationBuilder<Object, Object, TestAbstractAuthenticationBuilder> {
private TestAbstractAuthenticationBuilder(TestingAuthenticationToken token) {
super(token);
}
@Override
protected Authentication build(Collection<GrantedAuthority> authorities) {
return new TestingAuthenticationToken("user", "password", authorities);
public TestAbstractAuthenticationBuilder principal(@Nullable Object principal) {
return this;
}
@Override
public TestingAuthenticationToken build() {
return new TestingAuthenticationToken("user", "password", this.authorities);
}
}

View File

@ -57,7 +57,11 @@ public class TestingAuthenticationTokenTests {
AuthorityUtils.createAuthorityList("FACTOR_ONE"));
TestingAuthenticationToken factorTwo = new TestingAuthenticationToken("bob", "ssap",
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
TestingAuthenticationToken result = factorOne.toBuilder().apply(factorTwo).build();
TestingAuthenticationToken result = factorOne.toBuilder()
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
.principal(factorTwo.getPrincipal())
.credentials(factorTwo.getCredentials())
.build();
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials());

View File

@ -20,7 +20,6 @@ import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -94,10 +93,14 @@ public class UsernamePasswordAuthenticationTokenTests {
AuthorityUtils.createAuthorityList("FACTOR_ONE"));
UsernamePasswordAuthenticationToken factorTwo = new UsernamePasswordAuthenticationToken("bob", "ssap",
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
Authentication authentication = factorOne.toBuilder().apply(factorTwo).build();
Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
assertThat(authentication.getPrincipal()).isEqualTo("bob");
assertThat(authentication.getCredentials()).isEqualTo("ssap");
UsernamePasswordAuthenticationToken result = factorOne.toBuilder()
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
.principal(factorTwo.getPrincipal())
.credentials(factorTwo.getCredentials())
.build();
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
assertThat(result.getPrincipal()).isEqualTo("bob");
assertThat(result.getCredentials()).isEqualTo("ssap");
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
}

View File

@ -35,7 +35,12 @@ class JaasAuthenticationTokenTests {
AuthorityUtils.createAuthorityList("FACTOR_ONE"), mock(LoginContext.class));
JaasAuthenticationToken factorTwo = new JaasAuthenticationToken("bob", "ssap",
AuthorityUtils.createAuthorityList("FACTOR_TWO"), mock(LoginContext.class));
JaasAuthenticationToken result = factorOne.toBuilder().apply(factorTwo).build();
JaasAuthenticationToken result = factorOne.toBuilder()
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
.principal(factorTwo.getPrincipal())
.credentials(factorTwo.getCredentials())
.loginContext(factorTwo.getLoginContext())
.build();
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials());

View File

@ -32,7 +32,10 @@ class OneTimeTokenAuthenticationTests {
AuthorityUtils.createAuthorityList("FACTOR_ONE"));
OneTimeTokenAuthentication factorTwo = new OneTimeTokenAuthentication("bob",
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
OneTimeTokenAuthentication result = factorOne.toBuilder().apply(factorTwo).build();
OneTimeTokenAuthentication result = factorOne.toBuilder()
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
.principal(factorTwo.getPrincipal())
.build();
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");

View File

@ -16,6 +16,7 @@
package org.springframework.security.oauth2.client;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
@ -25,6 +26,7 @@ import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@ -157,7 +159,7 @@ public final class OAuth2AuthorizeRequest {
private static Authentication createAuthentication(final String principalName) {
Assert.hasText(principalName, "principalName cannot be empty");
return new AbstractAuthenticationToken(null) {
return new AbstractAuthenticationToken((Collection<? extends GrantedAuthority>) null) {
@Override
public Object getCredentials() {

View File

@ -18,6 +18,8 @@ package org.springframework.security.oauth2.client.authentication;
import java.util.Collection;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
@ -65,6 +67,14 @@ public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
this.setAuthenticated(true);
}
protected OAuth2AuthenticationToken(Builder<?> builder) {
super(builder);
Assert.notNull(builder.principal, "principal cannot be null");
Assert.hasText(builder.authorizedClientRegistrationId, "authorizedClientRegistrationId cannot be empty");
this.principal = builder.principal;
this.authorizedClientRegistrationId = builder.authorizedClientRegistrationId;
}
@Override
public OAuth2User getPrincipal() {
return this.principal;
@ -86,8 +96,8 @@ public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
}
@Override
public Builder toBuilder() {
return new Builder().apply(this);
public Builder<?> toBuilder() {
return new Builder<>(this);
}
/**
@ -95,34 +105,33 @@ public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
*
* @since 7.0
*/
public static final class Builder extends AbstractAuthenticationBuilder<OAuth2AuthenticationToken, Builder> {
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<OAuth2User, Object, B> {
private OAuth2User principal;
private String authorizedClientRegistrationId;
private Builder() {
}
public Builder apply(OAuth2AuthenticationToken authentication) {
return super.apply(authentication).principal(authentication.getPrincipal())
.authorizedClientRegistrationId(authentication.authorizedClientRegistrationId);
}
public Builder principal(OAuth2User principal) {
this.principal = principal;
return this;
}
public Builder authorizedClientRegistrationId(String authorizedClientRegistrationId) {
this.authorizedClientRegistrationId = authorizedClientRegistrationId;
return this;
protected Builder(OAuth2AuthenticationToken token) {
super(token);
this.principal = token.principal;
this.authorizedClientRegistrationId = token.authorizedClientRegistrationId;
}
@Override
protected OAuth2AuthenticationToken build(Collection<GrantedAuthority> authorities) {
return new OAuth2AuthenticationToken(this.principal, authorities, this.authorizedClientRegistrationId);
public B principal(@Nullable OAuth2User principal) {
Assert.notNull(principal, "principal cannot be null");
this.principal = principal;
return (B) this;
}
public B authorizedClientRegistrationId(String authorizedClientRegistrationId) {
this.authorizedClientRegistrationId = authorizedClientRegistrationId;
return (B) this;
}
@Override
public OAuth2AuthenticationToken build() {
return new OAuth2AuthenticationToken(this);
}
}

View File

@ -16,6 +16,7 @@
package org.springframework.security.oauth2.client.web.reactive.function.client;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
@ -36,6 +37,7 @@ import org.springframework.http.HttpStatusCode;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -551,7 +553,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
private static Authentication createAuthentication(final String principalName) {
Assert.hasText(principalName, "principalName cannot be empty");
return new AbstractAuthenticationToken(null) {
return new AbstractAuthenticationToken((Collection<? extends GrantedAuthority>) null) {
@Override
public Object getCredentials() {

View File

@ -91,7 +91,11 @@ public class OAuth2AuthenticationTokenTests {
AuthorityUtils.createAuthorityList("FACTOR_ONE"), "alice");
OAuth2AuthenticationToken factorTwo = new OAuth2AuthenticationToken(TestOAuth2Users.create(),
AuthorityUtils.createAuthorityList("FACTOR_TWO"), "bob");
OAuth2AuthenticationToken result = factorOne.toBuilder().apply(factorTwo).build();
OAuth2AuthenticationToken result = factorOne.toBuilder()
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
.principal(factorTwo.getPrincipal())
.authorizedClientRegistrationId(factorTwo.getAuthorizedClientRegistrationId())
.build();
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
assertThat(result.getAuthorizedClientRegistrationId()).isSameAs(factorTwo.getAuthorizedClientRegistrationId());

View File

@ -19,6 +19,8 @@ package org.springframework.security.oauth2.server.resource.authentication;
import java.util.Collection;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
@ -83,6 +85,15 @@ public abstract class AbstractOAuth2TokenAuthenticationToken<T extends OAuth2Tok
this.token = token;
}
protected AbstractOAuth2TokenAuthenticationToken(AbstractOAuth2TokenAuthenticationBuilder<T, ?> builder) {
super(builder);
Assert.notNull(builder.credentials, "token cannot be null");
Assert.notNull(builder.principal, "principal cannot be null");
this.principal = builder.principal;
this.credentials = builder.credentials;
this.token = builder.token;
}
@Override
public Object getPrincipal() {
return this.principal;
@ -106,4 +117,47 @@ public abstract class AbstractOAuth2TokenAuthenticationToken<T extends OAuth2Tok
*/
public abstract Map<String, Object> getTokenAttributes();
/**
* A builder preserving the concrete {@link Authentication} type
*
* @since 7.0
*/
public abstract static class AbstractOAuth2TokenAuthenticationBuilder<T extends OAuth2Token, B extends AbstractOAuth2TokenAuthenticationBuilder<T, B>>
extends AbstractAuthenticationBuilder<Object, Object, B> {
private Object principal;
private Object credentials;
private T token;
protected AbstractOAuth2TokenAuthenticationBuilder(AbstractOAuth2TokenAuthenticationToken<T> token) {
super(token);
this.principal = token.getPrincipal();
this.credentials = token.getCredentials();
this.token = token.getToken();
}
@Override
public B principal(@Nullable Object principal) {
Assert.notNull(principal, "principal cannot be null");
this.principal = principal;
return (B) this;
}
@Override
public B credentials(@Nullable Object credentials) {
Assert.notNull(credentials, "credentials cannot be null");
this.credentials = credentials;
return (B) this;
}
public B token(T token) {
Assert.notNull(token, "credentials cannot be null");
this.token = token;
return (B) this;
}
}
}

View File

@ -21,6 +21,8 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient;
@ -57,14 +59,19 @@ public class BearerTokenAuthentication extends AbstractOAuth2TokenAuthentication
setAuthenticated(true);
}
protected BearerTokenAuthentication(Builder<?> builder) {
super(builder);
this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(builder.attributes));
}
@Override
public Map<String, Object> getTokenAttributes() {
return this.attributes;
}
@Override
public Builder toBuilder() {
return new Builder().apply(this);
public Builder<?> toBuilder() {
return new Builder<>(this);
}
/**
@ -72,34 +79,34 @@ public class BearerTokenAuthentication extends AbstractOAuth2TokenAuthentication
*
* @since 7.0
*/
public static final class Builder extends AbstractAuthenticationBuilder<BearerTokenAuthentication, Builder> {
public static class Builder<B extends Builder<B>>
extends AbstractOAuth2TokenAuthenticationBuilder<OAuth2AccessToken, B> {
private OAuth2AuthenticatedPrincipal principal;
private Map<String, Object> attributes;
private OAuth2AccessToken token;
private Builder() {
}
public Builder apply(BearerTokenAuthentication authentication) {
return super.apply(authentication).principal((OAuth2AuthenticatedPrincipal) authentication.getPrincipal())
.credentials(authentication.getToken());
}
public Builder principal(OAuth2AuthenticatedPrincipal principal) {
this.principal = principal;
return this;
}
public Builder credentials(OAuth2AccessToken credentials) {
this.token = credentials;
return this;
protected Builder(BearerTokenAuthentication token) {
super(token);
this.attributes = token.getTokenAttributes();
}
@Override
protected BearerTokenAuthentication build(Collection<GrantedAuthority> authorities) {
return new BearerTokenAuthentication(this.principal, this.token, authorities);
public B principal(@Nullable Object principal) {
Assert.isInstanceOf(OAuth2AuthenticatedPrincipal.class, principal,
"principal must be of type OAuth2AuthenticatedPrincipal");
this.attributes = ((OAuth2AuthenticatedPrincipal) principal).getAttributes();
return super.principal(principal);
}
@Override
public B token(OAuth2AccessToken token) {
Assert.isTrue(token.getTokenType() == OAuth2AccessToken.TokenType.BEARER,
"credentials must be a bearer token");
return super.token(token);
}
@Override
public BearerTokenAuthentication build() {
return new BearerTokenAuthentication(this);
}
}

View File

@ -72,6 +72,11 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
this.name = name;
}
protected JwtAuthenticationToken(Builder<?> builder) {
super(builder);
this.name = builder.name;
}
@Override
public Map<String, Object> getTokenAttributes() {
return this.getToken().getClaims();
@ -86,8 +91,8 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
}
@Override
public Builder toBuilder() {
return new Builder().apply(this);
public Builder<?> toBuilder() {
return new Builder<>(this);
}
/**
@ -95,33 +100,23 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
*
* @since 7.0
*/
public static final class Builder extends AbstractAuthenticationBuilder<JwtAuthenticationToken, Builder> {
private Jwt jwt;
public static class Builder<B extends Builder<B>> extends AbstractOAuth2TokenAuthenticationBuilder<Jwt, B> {
private String name;
private Builder() {
protected Builder(JwtAuthenticationToken token) {
super(token);
this.name = token.getName();
}
public Builder apply(JwtAuthenticationToken token) {
return super.apply(token).jwt(token.getToken()).name(token.getName());
}
public Builder jwt(Jwt jwt) {
this.jwt = jwt;
return this;
}
public Builder name(String name) {
public B name(String name) {
this.name = name;
return this;
return (B) this;
}
@Override
protected JwtAuthenticationToken build(Collection<GrantedAuthority> authorities) {
return new JwtAuthenticationToken(this.jwt, authorities, this.name);
public JwtAuthenticationToken build() {
return new JwtAuthenticationToken(this);
}
}

View File

@ -180,6 +180,12 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter {
BearerTokenError error = BearerTokenErrors.invalidToken("Invalid bearer token");
throw new OAuth2AuthenticationException(error);
}
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current != null && current.isAuthenticated()) {
authenticationResult = authenticationResult.toBuilder()
.authorities((a) -> a.addAll(current.getAuthorities()))
.build();
}
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authenticationResult);
this.securityContextHolderStrategy.setContext(context);

View File

@ -162,7 +162,11 @@ public class BearerTokenAuthenticationTests {
new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "nekot", Instant.now(),
Instant.now().plusSeconds(3600)),
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
BearerTokenAuthentication authentication = factorOne.toBuilder().apply(factorTwo).build();
BearerTokenAuthentication authentication = factorOne.toBuilder()
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
.principal(factorTwo.getPrincipal())
.token(factorTwo.getToken())
.build();
Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
assertThat(authentication.getPrincipal()).isSameAs(factorTwo.getPrincipal());
assertThat(authentication.getToken()).isSameAs(factorTwo.getToken());

View File

@ -55,7 +55,7 @@ public class JwtAuthenticationTokenTests {
@Test
public void constructorWhenJwtIsNullThenThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> new JwtAuthenticationToken(null))
assertThatIllegalArgumentException().isThrownBy(() -> new JwtAuthenticationToken((Jwt) null))
.withMessageContaining("token cannot be null");
}
@ -122,7 +122,11 @@ public class JwtAuthenticationTokenTests {
AuthorityUtils.createAuthorityList("FACTOR_ONE"), "alice");
JwtAuthenticationToken factorTwo = new JwtAuthenticationToken(builder().claim("d", "w").build(),
AuthorityUtils.createAuthorityList("FACTOR_TWO"), "bob");
JwtAuthenticationToken result = factorOne.toBuilder().apply(factorTwo).build();
JwtAuthenticationToken result = factorOne.toBuilder()
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
.principal(factorTwo.getPrincipal())
.name(factorTwo.getName())
.build();
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
assertThat(result.getName()).isSameAs(factorTwo.getName());

View File

@ -18,7 +18,9 @@ package org.springframework.security.oauth2.server.resource.web.authentication;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import jakarta.servlet.Filter;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.BeforeEach;
@ -37,8 +39,11 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
@ -240,6 +245,7 @@ public class BearerTokenAuthenticationFilterTests {
new BearerTokenAuthenticationFilter(this.authenticationManager));
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
given(strategy.createEmptyContext()).willReturn(new SecurityContextImpl());
given(strategy.getContext()).willReturn(new SecurityContextImpl());
filter.setSecurityContextHolderStrategy(strategy);
filter.doFilter(this.request, this.response, this.filterChain);
verify(strategy).setContext(any());
@ -339,6 +345,23 @@ public class BearerTokenAuthenticationFilterTests {
// @formatter:on
}
@Test
void authenticateWhenPreviousAuthenticationThenApplies() throws Exception {
Authentication first = new TestingAuthenticationToken("user", "pass", "FACTOR_ONE");
Authentication second = new TestingAuthenticationToken("user", "pass", "FACTOR_TWO");
Filter filter = addMocks(new BearerTokenAuthenticationFilter(this.authenticationManager));
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
given(this.authenticationManager.authenticate(any())).willReturn(second);
SecurityContextHolder.getContext().setAuthentication(first);
filter.doFilter(this.request, this.response, this.filterChain);
Authentication result = SecurityContextHolder.getContext().getAuthentication();
SecurityContextHolder.clearContext();
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
}
private BearerTokenAuthenticationFilter addMocks(BearerTokenAuthenticationFilter filter) {
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
filter.setBearerTokenResolver(this.bearerTokenResolver);

View File

@ -19,10 +19,11 @@ package org.springframework.security.saml2.provider.service.authentication;
import java.io.Serial;
import java.util.Collection;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
/**
* An authentication based off of a SAML 2.0 Assertion
@ -56,6 +57,12 @@ public class Saml2AssertionAuthentication extends Saml2Authentication {
setAuthenticated(true);
}
protected Saml2AssertionAuthentication(Builder<?> builder) {
super(builder);
this.assertion = builder.assertion;
this.relyingPartyRegistrationId = builder.relyingPartyRegistrationId;
}
@Override
public Saml2ResponseAssertionAccessor getCredentials() {
return this.assertion;
@ -66,8 +73,8 @@ public class Saml2AssertionAuthentication extends Saml2Authentication {
}
@Override
public Builder toBuilder() {
return new Builder().apply(this);
public Builder<?> toBuilder() {
return new Builder<>(this);
}
/**
@ -75,44 +82,35 @@ public class Saml2AssertionAuthentication extends Saml2Authentication {
*
* @since 7.0
*/
public static final class Builder
extends AbstractAuthenticationBuilder<@NonNull Saml2AssertionAuthentication, @NonNull Builder> {
private Object principal;
public static class Builder<B extends Builder<B>>
extends Saml2Authentication.Builder<Saml2ResponseAssertionAccessor, B> {
private Saml2ResponseAssertionAccessor assertion;
private String relyingPartyRegistrationId;
private Builder() {
}
public Builder apply(Saml2AssertionAuthentication authentication) {
return super.apply(authentication).principal(authentication.getPrincipal())
.assertion(authentication.assertion)
.relyingPartyRegistrationId(authentication.relyingPartyRegistrationId);
}
public Builder principal(Object principal) {
this.principal = principal;
return this;
}
public Builder assertion(Saml2ResponseAssertionAccessor assertion) {
this.assertion = assertion;
return this;
}
public Builder relyingPartyRegistrationId(String relyingPartyRegistrationId) {
this.relyingPartyRegistrationId = relyingPartyRegistrationId;
return this;
protected Builder(Saml2AssertionAuthentication token) {
super(token);
this.assertion = token.assertion;
this.relyingPartyRegistrationId = token.relyingPartyRegistrationId;
}
@Override
protected Saml2AssertionAuthentication build(Collection<GrantedAuthority> authorities) {
return new Saml2AssertionAuthentication(this.principal, this.assertion, authorities,
this.relyingPartyRegistrationId);
public B credentials(@Nullable Saml2ResponseAssertionAccessor credentials) {
saml2Response(credentials.getResponseValue());
Assert.notNull(credentials, "assertion cannot be null");
this.assertion = credentials;
return (B) this;
}
public B relyingPartyRegistrationId(String relyingPartyRegistrationId) {
this.relyingPartyRegistrationId = relyingPartyRegistrationId;
return (B) this;
}
@Override
public Saml2AssertionAuthentication build() {
return new Saml2AssertionAuthentication(this);
}
}

View File

@ -19,6 +19,8 @@ package org.springframework.security.saml2.provider.service.authentication;
import java.io.Serial;
import java.util.Collection;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.security.core.Authentication;
@ -69,6 +71,12 @@ public class Saml2Authentication extends AbstractAuthenticationToken {
setAuthenticated(true);
}
Saml2Authentication(Builder<?, ?> builder) {
super(builder);
this.principal = builder.principal;
this.saml2Response = builder.saml2Response;
}
@Override
public Object getPrincipal() {
return this.principal;
@ -87,4 +95,29 @@ public class Saml2Authentication extends AbstractAuthenticationToken {
return getSaml2Response();
}
abstract static class Builder<C, B extends Builder<C, B>> extends AbstractAuthenticationBuilder<Object, C, B> {
private Object principal;
String saml2Response;
Builder(Saml2Authentication token) {
super(token);
this.principal = token.principal;
this.saml2Response = token.saml2Response;
}
@Override
public B principal(@Nullable Object principal) {
Assert.notNull(principal, "principal cannot be null");
this.principal = principal;
return (B) this;
}
void saml2Response(String saml2Response) {
this.saml2Response = saml2Response;
}
}
}

View File

@ -33,7 +33,12 @@ class Saml2AssertionAuthenticationTests {
prototype.nameId("alice").build(), AuthorityUtils.createAuthorityList("FACTOR_ONE"), "alice");
Saml2AssertionAuthentication factorTwo = new Saml2AssertionAuthentication("bob",
prototype.nameId("alice").build(), AuthorityUtils.createAuthorityList("FACTOR_TWO"), "bob");
Saml2AssertionAuthentication result = factorOne.toBuilder().apply(factorTwo).build();
Saml2AssertionAuthentication result = factorOne.toBuilder()
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
.principal(factorTwo.getPrincipal())
.credentials(factorTwo.getCredentials())
.relyingPartyRegistrationId(factorTwo.getRelyingPartyRegistrationId())
.build();
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials());

View File

@ -23,6 +23,7 @@ import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
/**
* {@link org.springframework.security.core.Authentication} implementation for
@ -47,7 +48,7 @@ public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationT
* @param aCredentials The pre-authenticated credentials
*/
public PreAuthenticatedAuthenticationToken(Object aPrincipal, @Nullable Object aCredentials) {
super(null);
super((Collection<? extends GrantedAuthority>) null);
this.principal = aPrincipal;
this.credentials = aCredentials;
}
@ -67,6 +68,12 @@ public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationT
setAuthenticated(true);
}
protected PreAuthenticatedAuthenticationToken(Builder<?> builder) {
super(builder);
this.principal = builder.principal;
this.credentials = builder.credentials;
}
/**
* Get the credentials
*/
@ -84,8 +91,8 @@ public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationT
}
@Override
public Builder toBuilder() {
return new Builder().apply(this);
public Builder<?> toBuilder() {
return new Builder<>(this);
}
/**
@ -93,34 +100,34 @@ public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationT
*
* @since 7.0
*/
public static final class Builder
extends AbstractAuthenticationBuilder<PreAuthenticatedAuthenticationToken, Builder> {
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<Object, Object, B> {
private Object principal;
private Object credentials;
private @Nullable Object credentials;
private Builder() {
}
public Builder apply(PreAuthenticatedAuthenticationToken token) {
return super.apply(token).principal(token.getPrincipal()).credentials(token.getCredentials());
}
public Builder principal(Object principal) {
this.principal = principal;
return this;
}
public Builder credentials(Object credentials) {
this.credentials = credentials;
return this;
protected Builder(PreAuthenticatedAuthenticationToken token) {
super(token);
this.principal = token.principal;
this.credentials = token.credentials;
}
@Override
protected PreAuthenticatedAuthenticationToken build(Collection<GrantedAuthority> authorities) {
return new PreAuthenticatedAuthenticationToken(this.principal, this.credentials, authorities);
public B principal(@Nullable Object principal) {
Assert.notNull(principal, "principal cannot be null");
this.principal = principal;
return (B) this;
}
@Override
public B credentials(@Nullable Object credentials) {
this.credentials = credentials;
return (B) this;
}
@Override
public PreAuthenticatedAuthenticationToken build() {
return new PreAuthenticatedAuthenticationToken(this);
}
}

View File

@ -80,7 +80,11 @@ public class PreAuthenticatedAuthenticationTokenTests {
AuthorityUtils.createAuthorityList("FACTOR_ONE"));
PreAuthenticatedAuthenticationToken factorTwo = new PreAuthenticatedAuthenticationToken("bob", "ssap",
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
PreAuthenticatedAuthenticationToken result = factorOne.toBuilder().apply(factorTwo).build();
PreAuthenticatedAuthenticationToken result = factorOne.toBuilder()
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
.principal(factorTwo.getPrincipal())
.credentials(factorTwo.getCredentials())
.build();
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials());

View File

@ -21,6 +21,7 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collection;
import java.util.Collections;
import jakarta.servlet.Filter;
@ -46,6 +47,7 @@ import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
@ -810,7 +812,7 @@ public class HttpSessionSecurityContextRepositoryTests {
private static class SomeTransientAuthentication extends AbstractAuthenticationToken {
SomeTransientAuthentication() {
super(null);
super((Collection<? extends GrantedAuthority>) null);
}
@Override
@ -840,7 +842,7 @@ public class HttpSessionSecurityContextRepositoryTests {
private static class SomeOtherTransientAuthentication extends AbstractAuthenticationToken {
SomeOtherTransientAuthentication() {
super(null);
super((Collection<? extends GrantedAuthority>) null);
}
@Override

View File

@ -49,6 +49,11 @@ public class WebAuthnAuthentication extends AbstractAuthenticationToken {
super.setAuthenticated(true);
}
private WebAuthnAuthentication(Builder<?> builder) {
super(builder);
this.principal = builder.principal;
}
@Override
public void setAuthenticated(boolean authenticated) {
Assert.isTrue(!authenticated, "Cannot set this token to trusted");
@ -71,8 +76,8 @@ public class WebAuthnAuthentication extends AbstractAuthenticationToken {
}
@Override
public Builder toBuilder() {
return new Builder().apply(this);
public Builder<?> toBuilder() {
return new Builder<>(this);
}
/**
@ -80,26 +85,25 @@ public class WebAuthnAuthentication extends AbstractAuthenticationToken {
*
* @since 7.0
*/
public static final class Builder extends AbstractAuthenticationBuilder<WebAuthnAuthentication, Builder> {
public static final class Builder<B extends Builder<B>>
extends AbstractAuthenticationBuilder<PublicKeyCredentialUserEntity, Object, B> {
private PublicKeyCredentialUserEntity principal;
private Builder() {
}
public Builder apply(WebAuthnAuthentication authentication) {
return super.apply(authentication).principal(authentication.getPrincipal());
}
public Builder principal(PublicKeyCredentialUserEntity principal) {
this.principal = principal;
return this;
private Builder(WebAuthnAuthentication token) {
super(token);
}
@Override
protected WebAuthnAuthentication build(Collection<GrantedAuthority> authorities) {
return new WebAuthnAuthentication(this.principal, authorities);
public B principal(@Nullable PublicKeyCredentialUserEntity principal) {
Assert.notNull(principal, "principal cannot be null");
this.principal = principal;
return (B) this;
}
@Override
public WebAuthnAuthentication build() {
return new WebAuthnAuthentication(this);
}
}

View File

@ -64,7 +64,10 @@ class WebAuthnAuthenticationTests {
PublicKeyCredentialUserEntity bob = TestPublicKeyCredentialUserEntities.userEntity().build();
WebAuthnAuthentication factorTwo = new WebAuthnAuthentication(bob,
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
WebAuthnAuthentication result = factorOne.toBuilder().apply(factorTwo).build();
WebAuthnAuthentication result = factorOne.toBuilder()
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
.principal(factorTwo.getPrincipal())
.build();
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");