diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java index 9fd4ba204e..68761fccf4 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java @@ -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> extends AbstractAuthenticationBuilder { 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 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); } } diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/CasServiceTicketAuthenticationToken.java b/cas/src/main/java/org/springframework/security/cas/authentication/CasServiceTicketAuthenticationToken.java index ac77f48c5b..4d791adccc 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/CasServiceTicketAuthenticationToken.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/CasServiceTicketAuthenticationToken.java @@ -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) 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> extends AbstractAuthenticationBuilder { + + 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); + } + + } + } diff --git a/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java b/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java index bdd26916a6..d489d00ff3 100644 --- a/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java +++ b/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java @@ -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 authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities()); assertThat(authentication.getKeyHash()).isEqualTo(factorTwo.getKeyHash()); assertThat(authentication.getPrincipal()).isEqualTo(factorTwo.getPrincipal()); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTransientAuthenticationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTransientAuthenticationTests.java index 2616edb1fc..4dc7c3ee3e 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTransientAuthenticationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTransientAuthenticationTests.java @@ -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) null); } @Override diff --git a/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests.java b/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests.java index c2aeef4c0a..97c18412b7 100644 --- a/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests.java +++ b/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests.java @@ -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) null); } @Override diff --git a/core/src/main/java/org/springframework/security/authentication/AbstractAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/AbstractAuthenticationToken.java index 243a3f8fb8..02e60bf25e 100644 --- a/core/src/main/java/org/springframework/security/authentication/AbstractAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/AbstractAuthenticationToken.java @@ -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 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 getAuthorities() { return this.authorities; @@ -187,36 +195,40 @@ public abstract class AbstractAuthenticationToken implements Authentication, Cre return sb.toString(); } - protected abstract static class AbstractAuthenticationBuilder> - implements Builder { + protected abstract static class AbstractAuthenticationBuilder> + implements Authentication.Builder { - private final Collection authorities = new HashSet<>(); + protected boolean authenticated; - protected AbstractAuthenticationBuilder() { + protected @Nullable Object details; + protected final Collection 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> 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 authorities); - } } diff --git a/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java index fa1972b3c0..5ff410ecec 100644 --- a/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java @@ -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 String * @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 { + public static class Builder> extends AbstractAuthenticationBuilder { - 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 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); } } diff --git a/core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java index 657b596741..f001674ca4 100644 --- a/core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java @@ -43,7 +43,7 @@ public class TestingAuthenticationToken extends AbstractAuthenticationToken { private final Object principal; public TestingAuthenticationToken(Object principal, Object credentials) { - super(null); + super((Collection) 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 { + public static class Builder> extends AbstractAuthenticationBuilder { - 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 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); } } diff --git a/core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java index 33d6b13e02..4bba35a27d 100644 --- a/core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java @@ -51,7 +51,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT * */ public UsernamePasswordAuthenticationToken(@Nullable Object principal, @Nullable Object credentials) { - super(null); + super((Collection) 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 UsernamePasswordAuthenticationToken. @@ -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> - extends AbstractAuthenticationBuilder { + public static class Builder> extends AbstractAuthenticationBuilder { - 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 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); } } diff --git a/core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationToken.java index 52d8392709..a1dd08a37b 100644 --- a/core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationToken.java @@ -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 { + public static class Builder> extends UsernamePasswordAuthenticationToken.Builder { - 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 authorities) { - UsernamePasswordAuthenticationToken token = super.build(authorities); - Assert.notNull(this.loginContext, "loginContext cannot be null"); - return new JaasAuthenticationToken(token.getPrincipal(), token.getCredentials(), - (List) token.getAuthorities(), this.loginContext); + public JaasAuthenticationToken build() { + Assert.notNull(this.principal, "principal cannot be null"); + return new JaasAuthenticationToken(this); } } diff --git a/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthentication.java b/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthentication.java index 2ccf53b27a..8e73adf65b 100644 --- a/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthentication.java +++ b/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthentication.java @@ -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 { + public static class Builder> extends AbstractAuthenticationBuilder { - 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 authorities) { - Assert.notNull(this.principal, "principal cannot be null"); - return new OneTimeTokenAuthentication(this.principal, authorities); + public OneTimeTokenAuthentication build() { + return new OneTimeTokenAuthentication(this); } } diff --git a/core/src/main/java/org/springframework/security/core/Authentication.java b/core/src/main/java/org/springframework/security/core/Authentication.java index b07bdd977f..4531842aab 100644 --- a/core/src/main/java/org/springframework/security/core/Authentication.java +++ b/core/src/main/java/org/springframework/security/core/Authentication.java @@ -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 AuthenticationManager 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 the type of {@link Authentication} * @author Josh Cummings * @since 7.0 */ - interface Builder> { + interface Builder> { - /** - * Apply this {@link Authentication} to the builder. - *

- * 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> 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> 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(); } diff --git a/core/src/main/java/org/springframework/security/core/NoopAuthenticationBuilder.java b/core/src/main/java/org/springframework/security/core/NoopAuthenticationBuilder.java index 163574b2db..982630c8e4 100644 --- a/core/src/main/java/org/springframework/security/core/NoopAuthenticationBuilder.java +++ b/core/src/main/java/org/springframework/security/core/NoopAuthenticationBuilder.java @@ -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 the type of {@link Authentication} * @author Josh Cummings * @since 7.0 */ -class NoopAuthenticationBuilder - implements Authentication.Builder> { +class NoopAuthenticationBuilder implements Authentication.Builder { - 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 authorities(Consumer> 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> authorities) { + return this; + } + + @Override + public Authentication build() { return this.original; } diff --git a/core/src/test/java/org/springframework/security/authentication/AbstractAuthenticationBuilderTests.java b/core/src/test/java/org/springframework/security/authentication/AbstractAuthenticationBuilderTests.java index f3babe70f8..50b64ebe27 100644 --- a/core/src/test/java/org/springframework/security/authentication/AbstractAuthenticationBuilderTests.java +++ b/core/src/test/java/org/springframework/security/authentication/AbstractAuthenticationBuilderTests.java @@ -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 authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO"); } private static final class TestAbstractAuthenticationBuilder - extends AbstractAuthenticationBuilder { + extends AbstractAuthenticationBuilder { + + private TestAbstractAuthenticationBuilder(TestingAuthenticationToken token) { + super(token); + } @Override - protected Authentication build(Collection 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); } } diff --git a/core/src/test/java/org/springframework/security/authentication/TestingAuthenticationTokenTests.java b/core/src/test/java/org/springframework/security/authentication/TestingAuthenticationTokenTests.java index 2c17042a7d..6490f2bc3d 100644 --- a/core/src/test/java/org/springframework/security/authentication/TestingAuthenticationTokenTests.java +++ b/core/src/test/java/org/springframework/security/authentication/TestingAuthenticationTokenTests.java @@ -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 authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal()); assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials()); diff --git a/core/src/test/java/org/springframework/security/authentication/UsernamePasswordAuthenticationTokenTests.java b/core/src/test/java/org/springframework/security/authentication/UsernamePasswordAuthenticationTokenTests.java index 334e9bfc6b..d09bbdabf8 100644 --- a/core/src/test/java/org/springframework/security/authentication/UsernamePasswordAuthenticationTokenTests.java +++ b/core/src/test/java/org/springframework/security/authentication/UsernamePasswordAuthenticationTokenTests.java @@ -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 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 authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); + assertThat(result.getPrincipal()).isEqualTo("bob"); + assertThat(result.getCredentials()).isEqualTo("ssap"); assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO"); } diff --git a/core/src/test/java/org/springframework/security/authentication/jaas/JaasAuthenticationTokenTests.java b/core/src/test/java/org/springframework/security/authentication/jaas/JaasAuthenticationTokenTests.java index f307c2d380..a3409848f0 100644 --- a/core/src/test/java/org/springframework/security/authentication/jaas/JaasAuthenticationTokenTests.java +++ b/core/src/test/java/org/springframework/security/authentication/jaas/JaasAuthenticationTokenTests.java @@ -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 authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal()); assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials()); diff --git a/core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationTests.java b/core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationTests.java index c8012b82f5..aec07a280d 100644 --- a/core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationTests.java +++ b/core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationTests.java @@ -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 authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal()); assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO"); diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizeRequest.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizeRequest.java index 741189304c..82184888b3 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizeRequest.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizeRequest.java @@ -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) null) { @Override public Object getCredentials() { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthenticationToken.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthenticationToken.java index 6b5ac6119b..613e16afbf 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthenticationToken.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthenticationToken.java @@ -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 { + public static class Builder> extends AbstractAuthenticationBuilder { 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 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); } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java index 47c27acbaa..6f7f8d3a12 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java @@ -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) null) { @Override public Object getCredentials() { diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthenticationTokenTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthenticationTokenTests.java index 839cd7ce7b..2cd5a9d5e3 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthenticationTokenTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthenticationTokenTests.java @@ -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 authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal()); assertThat(result.getAuthorizedClientRegistrationId()).isSameAs(factorTwo.getAuthorizedClientRegistrationId()); diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/AbstractOAuth2TokenAuthenticationToken.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/AbstractOAuth2TokenAuthenticationToken.java index b8e2be61e0..68898af461 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/AbstractOAuth2TokenAuthenticationToken.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/AbstractOAuth2TokenAuthenticationToken.java @@ -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 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 getTokenAttributes(); + /** + * A builder preserving the concrete {@link Authentication} type + * + * @since 7.0 + */ + public abstract static class AbstractOAuth2TokenAuthenticationBuilder> + extends AbstractAuthenticationBuilder { + + private Object principal; + + private Object credentials; + + private T token; + + protected AbstractOAuth2TokenAuthenticationBuilder(AbstractOAuth2TokenAuthenticationToken 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; + } + + } + } diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthentication.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthentication.java index 6f8606df2e..1fcbe71db5 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthentication.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthentication.java @@ -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 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 { + public static class Builder> + extends AbstractOAuth2TokenAuthenticationBuilder { - private OAuth2AuthenticatedPrincipal principal; + private Map 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 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); } } diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationToken.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationToken.java index dfb23244b3..5201e7ab9a 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationToken.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationToken.java @@ -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 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 { - - private Jwt jwt; + public static class Builder> extends AbstractOAuth2TokenAuthenticationBuilder { 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 authorities) { - return new JwtAuthenticationToken(this.jwt, authorities, this.name); + public JwtAuthenticationToken build() { + return new JwtAuthenticationToken(this); } } diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java index 1deb3a7689..89adbf9f5c 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java @@ -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); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthenticationTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthenticationTests.java index eab8208656..d1fef9d1f8 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthenticationTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthenticationTests.java @@ -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 authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities()); assertThat(authentication.getPrincipal()).isSameAs(factorTwo.getPrincipal()); assertThat(authentication.getToken()).isSameAs(factorTwo.getToken()); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationTokenTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationTokenTests.java index 7780c05c77..2114c6f87b 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationTokenTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationTokenTests.java @@ -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 authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal()); assertThat(result.getName()).isSameAs(factorTwo.getName()); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java index 8f0f91093c..0fc4974a6c 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java @@ -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 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); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthentication.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthentication.java index 3b2aa37a2a..e86494f9fc 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthentication.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthentication.java @@ -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> + extends Saml2Authentication.Builder { 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 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); } } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java index 82b4042c49..2a9fe34bd9 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2Authentication.java @@ -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> extends AbstractAuthenticationBuilder { + + 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; + } + + } + } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthenticationTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthenticationTests.java index bfa0875d06..57b08dee02 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthenticationTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthenticationTests.java @@ -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 authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal()); assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials()); diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationToken.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationToken.java index cb5c523590..0f994bd751 100755 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationToken.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationToken.java @@ -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) 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 { + public static class Builder> extends AbstractAuthenticationBuilder { 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 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); } } diff --git a/web/src/test/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationTokenTests.java b/web/src/test/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationTokenTests.java index 606dd50f03..fc111528ff 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationTokenTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationTokenTests.java @@ -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 authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal()); assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials()); diff --git a/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java b/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java index 979c6c8138..a27873d230 100644 --- a/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java +++ b/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java @@ -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) null); } @Override @@ -840,7 +842,7 @@ public class HttpSessionSecurityContextRepositoryTests { private static class SomeOtherTransientAuthentication extends AbstractAuthenticationToken { SomeOtherTransientAuthentication() { - super(null); + super((Collection) null); } @Override diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthentication.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthentication.java index 9855d17a3e..615781e2d1 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthentication.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthentication.java @@ -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 { + public static final class Builder> + extends AbstractAuthenticationBuilder { 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 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); } } diff --git a/webauthn/src/test/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationTests.java b/webauthn/src/test/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationTests.java index 7b3bc6f37a..409e05623e 100644 --- a/webauthn/src/test/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationTests.java +++ b/webauthn/src/test/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationTests.java @@ -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 authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal()); assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");