> {
+
+ /**
+ * 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()));
+ }
+
+ /**
+ * Apply these authorities to the builder.
+ * @param authorities the authorities to apply
+ * @return the {@link Builder} for further configuration
+ */
+ B authorities(Consumer> authorities);
+
+ /**
+ * Build an {@link Authentication} instance
+ * @return the {@link Authentication} instance
+ */
+ A 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
new file mode 100644
index 0000000000..163574b2db
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/core/NoopAuthenticationBuilder.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2004-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.core;
+
+import java.util.Collection;
+import java.util.function.Consumer;
+
+import org.springframework.util.Assert;
+
+/**
+ * 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> {
+
+ private A original;
+
+ NoopAuthenticationBuilder(A authentication) {
+ Assert.isTrue(authentication.isAuthenticated(), "cannot mutate an unauthenticated token");
+ Assert.notNull(authentication.getPrincipal(), "principal cannot be null");
+ this.original = authentication;
+ }
+
+ @Override
+ public NoopAuthenticationBuilder authorities(Consumer> authorities) {
+ return this;
+ }
+
+ @Override
+ public A 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
new file mode 100644
index 0000000000..f3babe70f8
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/authentication/AbstractAuthenticationBuilderTests.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2004-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.authentication;
+
+import java.util.Collection;
+import java.util.Set;
+
+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();
+ Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
+ assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
+ }
+
+ private static final class TestAbstractAuthenticationBuilder
+ extends AbstractAuthenticationBuilder {
+
+ @Override
+ protected Authentication build(Collection authorities) {
+ return new TestingAuthenticationToken("user", "password", 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 cfab36a2e1..2c17042a7d 100644
--- a/core/src/test/java/org/springframework/security/authentication/TestingAuthenticationTokenTests.java
+++ b/core/src/test/java/org/springframework/security/authentication/TestingAuthenticationTokenTests.java
@@ -17,9 +17,11 @@
package org.springframework.security.authentication;
import java.util.Arrays;
+import java.util.Set;
import org.junit.jupiter.api.Test;
+import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import static org.assertj.core.api.Assertions.assertThat;
@@ -49,4 +51,17 @@ public class TestingAuthenticationTokenTests {
assertThat(authenticated.isAuthenticated()).isTrue();
}
+ @Test
+ public void toBuilderWhenApplyThenCopies() {
+ TestingAuthenticationToken factorOne = new TestingAuthenticationToken("alice", "pass",
+ AuthorityUtils.createAuthorityList("FACTOR_ONE"));
+ TestingAuthenticationToken factorTwo = new TestingAuthenticationToken("bob", "ssap",
+ AuthorityUtils.createAuthorityList("FACTOR_TWO"));
+ TestingAuthenticationToken result = factorOne.toBuilder().apply(factorTwo).build();
+ Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
+ assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
+ assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials());
+ assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
+ }
+
}
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 0e25aef80d..334e9bfc6b 100644
--- a/core/src/test/java/org/springframework/security/authentication/UsernamePasswordAuthenticationTokenTests.java
+++ b/core/src/test/java/org/springframework/security/authentication/UsernamePasswordAuthenticationTokenTests.java
@@ -16,8 +16,11 @@
package org.springframework.security.authentication;
+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;
@@ -85,4 +88,17 @@ public class UsernamePasswordAuthenticationTokenTests {
assertThat(grantedToken.isAuthenticated()).isTrue();
}
+ @Test
+ public void toBuilderWhenApplyThenCopies() {
+ UsernamePasswordAuthenticationToken factorOne = new UsernamePasswordAuthenticationToken("alice", "pass",
+ 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");
+ 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
new file mode 100644
index 0000000000..f307c2d380
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/authentication/jaas/JaasAuthenticationTokenTests.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2004-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.authentication.jaas;
+
+import java.util.Set;
+
+import javax.security.auth.login.LoginContext;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.security.core.authority.AuthorityUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+class JaasAuthenticationTokenTests {
+
+ @Test
+ void toBuilderWhenApplyThenCopies() {
+ JaasAuthenticationToken factorOne = new JaasAuthenticationToken("alice", "pass",
+ 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();
+ Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
+ assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
+ assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials());
+ assertThat(result.getLoginContext()).isSameAs(factorTwo.getLoginContext());
+ assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
+ }
+
+}
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
new file mode 100644
index 0000000000..c8012b82f5
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationTests.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2004-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.authentication.ott;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.security.core.authority.AuthorityUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class OneTimeTokenAuthenticationTests {
+
+ @Test
+ void toBuilderWhenApplyThenCopies() {
+ OneTimeTokenAuthentication factorOne = new OneTimeTokenAuthentication("alice",
+ AuthorityUtils.createAuthorityList("FACTOR_ONE"));
+ OneTimeTokenAuthentication factorTwo = new OneTimeTokenAuthentication("bob",
+ AuthorityUtils.createAuthorityList("FACTOR_TWO"));
+ OneTimeTokenAuthentication result = factorOne.toBuilder().apply(factorTwo).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/authentication/OAuth2AuthenticationToken.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthenticationToken.java
index c766522199..6b5ac6119b 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
@@ -85,4 +85,46 @@ public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
return this.authorizedClientRegistrationId;
}
+ @Override
+ public Builder toBuilder() {
+ return new Builder().apply(this);
+ }
+
+ /**
+ * A builder preserving the concrete {@link Authentication} type
+ *
+ * @since 7.0
+ */
+ public static final 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;
+ }
+
+ @Override
+ protected OAuth2AuthenticationToken build(Collection authorities) {
+ return new OAuth2AuthenticationToken(this.principal, authorities, this.authorizedClientRegistrationId);
+ }
+
+ }
+
}
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 4c590fcaf4..839cd7ce7b 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
@@ -18,12 +18,15 @@ package org.springframework.security.oauth2.client.authentication;
import java.util.Collection;
import java.util.Collections;
+import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.security.oauth2.core.user.TestOAuth2Users;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -82,4 +85,17 @@ public class OAuth2AuthenticationTokenTests {
assertThat(authentication.isAuthenticated()).isEqualTo(true);
}
+ @Test
+ public void toBuilderWhenApplyThenCopies() {
+ OAuth2AuthenticationToken factorOne = new OAuth2AuthenticationToken(TestOAuth2Users.create(),
+ AuthorityUtils.createAuthorityList("FACTOR_ONE"), "alice");
+ OAuth2AuthenticationToken factorTwo = new OAuth2AuthenticationToken(TestOAuth2Users.create(),
+ AuthorityUtils.createAuthorityList("FACTOR_TWO"), "bob");
+ OAuth2AuthenticationToken result = factorOne.toBuilder().apply(factorTwo).build();
+ Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
+ assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
+ assertThat(result.getAuthorizedClientRegistrationId()).isSameAs(factorTwo.getAuthorizedClientRegistrationId());
+ assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
+ }
+
}
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 f3dfb83270..6f8606df2e 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,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
+import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
@@ -61,4 +62,46 @@ public class BearerTokenAuthentication extends AbstractOAuth2TokenAuthentication
return this.attributes;
}
+ @Override
+ public Builder toBuilder() {
+ return new Builder().apply(this);
+ }
+
+ /**
+ * A builder preserving the concrete {@link Authentication} type
+ *
+ * @since 7.0
+ */
+ public static final class Builder extends AbstractAuthenticationBuilder {
+
+ private OAuth2AuthenticatedPrincipal principal;
+
+ 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;
+ }
+
+ @Override
+ protected BearerTokenAuthentication build(Collection authorities) {
+ return new BearerTokenAuthentication(this.principal, this.token, authorities);
+ }
+
+ }
+
}
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 43cc749d9d..dfb23244b3 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
@@ -19,6 +19,7 @@ package org.springframework.security.oauth2.server.resource.authentication;
import java.util.Collection;
import java.util.Map;
+import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient;
import org.springframework.security.oauth2.jwt.Jwt;
@@ -84,4 +85,45 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
return this.name;
}
+ @Override
+ public Builder toBuilder() {
+ return new Builder().apply(this);
+ }
+
+ /**
+ * A builder preserving the concrete {@link Authentication} type
+ *
+ * @since 7.0
+ */
+ public static final class Builder extends AbstractAuthenticationBuilder {
+
+ private Jwt jwt;
+
+ private String name;
+
+ private Builder() {
+
+ }
+
+ 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) {
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ protected JwtAuthenticationToken build(Collection authorities) {
+ return new JwtAuthenticationToken(this.jwt, authorities, this.name);
+ }
+
+ }
+
}
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 39360f862d..eab8208656 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
@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import net.minidev.json.JSONObject;
import org.junit.jupiter.api.BeforeEach;
@@ -34,6 +35,7 @@ import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrinci
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
+import org.springframework.security.oauth2.core.TestOAuth2AuthenticatedPrincipals;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -151,4 +153,20 @@ public class BearerTokenAuthenticationTests {
token.toString();
}
+ @Test
+ public void toBuilderWhenApplyThenCopies() {
+ BearerTokenAuthentication factorOne = new BearerTokenAuthentication(TestOAuth2AuthenticatedPrincipals.active(),
+ this.token, AuthorityUtils.createAuthorityList("FACTOR_ONE"));
+ BearerTokenAuthentication factorTwo = new BearerTokenAuthentication(
+ TestOAuth2AuthenticatedPrincipals.active((m) -> m.put("k", "v")),
+ new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "nekot", Instant.now(),
+ Instant.now().plusSeconds(3600)),
+ AuthorityUtils.createAuthorityList("FACTOR_TWO"));
+ BearerTokenAuthentication authentication = factorOne.toBuilder().apply(factorTwo).build();
+ Set authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
+ assertThat(authentication.getPrincipal()).isSameAs(factorTwo.getPrincipal());
+ assertThat(authentication.getToken()).isSameAs(factorTwo.getToken());
+ assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
+ }
+
}
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 d6af03cc3b..7780c05c77 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
@@ -17,6 +17,7 @@
package org.springframework.security.oauth2.server.resource.authentication;
import java.util.Collection;
+import java.util.Set;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -115,6 +116,19 @@ public class JwtAuthenticationTokenTests {
assertThat(new JwtAuthenticationToken(jwt).getName()).isNull();
}
+ @Test
+ public void toBuilderWhenApplyThenCopies() {
+ JwtAuthenticationToken factorOne = new JwtAuthenticationToken(builder().claim("c", "v").build(),
+ 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();
+ Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
+ assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
+ assertThat(result.getName()).isSameAs(factorTwo.getName());
+ assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
+ }
+
private Jwt.Builder builder() {
return Jwt.withTokenValue("token").header("alg", JwsAlgorithms.RS256);
}
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 3b528c04a3..3b2aa37a2a 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,6 +19,9 @@ package org.springframework.security.saml2.provider.service.authentication;
import java.io.Serial;
import java.util.Collection;
+import org.jspecify.annotations.NonNull;
+
+import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
/**
@@ -62,4 +65,56 @@ public class Saml2AssertionAuthentication extends Saml2Authentication {
return this.relyingPartyRegistrationId;
}
+ @Override
+ public Builder toBuilder() {
+ return new Builder().apply(this);
+ }
+
+ /**
+ * A builder preserving the concrete {@link Authentication} type
+ *
+ * @since 7.0
+ */
+ public static final class Builder
+ extends AbstractAuthenticationBuilder<@NonNull Saml2AssertionAuthentication, @NonNull Builder> {
+
+ private Object principal;
+
+ 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;
+ }
+
+ @Override
+ protected Saml2AssertionAuthentication build(Collection authorities) {
+ return new Saml2AssertionAuthentication(this.principal, this.assertion, authorities,
+ this.relyingPartyRegistrationId);
+ }
+
+ }
+
}
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
new file mode 100644
index 0000000000..bfa0875d06
--- /dev/null
+++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/Saml2AssertionAuthenticationTests.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2004-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.saml2.provider.service.authentication;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.security.core.authority.AuthorityUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class Saml2AssertionAuthenticationTests {
+
+ @Test
+ void toBuilderWhenApplyThenCopies() {
+ Saml2ResponseAssertion.Builder prototype = Saml2ResponseAssertion.withResponseValue("response");
+ Saml2AssertionAuthentication factorOne = new Saml2AssertionAuthentication("alice",
+ 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();
+ Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
+ assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
+ assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials());
+ assertThat(result.getRelyingPartyRegistrationId()).isSameAs(factorTwo.getRelyingPartyRegistrationId());
+ assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
+ }
+
+}
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 fefda2ca49..cb5c523590 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
@@ -21,6 +21,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;
/**
@@ -82,4 +83,46 @@ public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationT
return this.principal;
}
+ @Override
+ public Builder toBuilder() {
+ return new Builder().apply(this);
+ }
+
+ /**
+ * A builder preserving the concrete {@link Authentication} type
+ *
+ * @since 7.0
+ */
+ public static final class Builder
+ extends AbstractAuthenticationBuilder {
+
+ private Object principal;
+
+ private 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;
+ }
+
+ @Override
+ protected PreAuthenticatedAuthenticationToken build(Collection authorities) {
+ return new PreAuthenticatedAuthenticationToken(this.principal, this.credentials, authorities);
+ }
+
+ }
+
}
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 99825bd7d0..606dd50f03 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
@@ -18,6 +18,7 @@ package org.springframework.security.web.authentication.preauth;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import org.junit.jupiter.api.Test;
@@ -73,4 +74,17 @@ public class PreAuthenticatedAuthenticationTokenTests {
.isTrue();
}
+ @Test
+ public void toBuilderWhenApplyThenCopies() {
+ PreAuthenticatedAuthenticationToken factorOne = new PreAuthenticatedAuthenticationToken("alice", "pass",
+ AuthorityUtils.createAuthorityList("FACTOR_ONE"));
+ PreAuthenticatedAuthenticationToken factorTwo = new PreAuthenticatedAuthenticationToken("bob", "ssap",
+ AuthorityUtils.createAuthorityList("FACTOR_TWO"));
+ PreAuthenticatedAuthenticationToken result = factorOne.toBuilder().apply(factorTwo).build();
+ Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
+ assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
+ assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials());
+ assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
+ }
+
}
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 42007f9ece..9855d17a3e 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
@@ -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.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.util.Assert;
@@ -69,4 +70,38 @@ public class WebAuthnAuthentication extends AbstractAuthenticationToken {
return this.principal.getName();
}
+ @Override
+ public Builder toBuilder() {
+ return new Builder().apply(this);
+ }
+
+ /**
+ * A builder preserving the concrete {@link Authentication} type
+ *
+ * @since 7.0
+ */
+ 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;
+ }
+
+ @Override
+ protected WebAuthnAuthentication build(Collection authorities) {
+ return new WebAuthnAuthentication(this.principal, authorities);
+ }
+
+ }
+
}
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 8ad6e92ea4..7b3bc6f37a 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
@@ -17,6 +17,7 @@
package org.springframework.security.web.webauthn.authentication;
import java.util.List;
+import java.util.Set;
import org.junit.jupiter.api.Test;
@@ -55,4 +56,18 @@ class WebAuthnAuthenticationTests {
assertThat(authentication.isAuthenticated()).isFalse();
}
+ @Test
+ void toBuilderWhenApplyThenCopies() {
+ PublicKeyCredentialUserEntity alice = TestPublicKeyCredentialUserEntities.userEntity().build();
+ WebAuthnAuthentication factorOne = new WebAuthnAuthentication(alice,
+ AuthorityUtils.createAuthorityList("FACTOR_ONE"));
+ PublicKeyCredentialUserEntity bob = TestPublicKeyCredentialUserEntities.userEntity().build();
+ WebAuthnAuthentication factorTwo = new WebAuthnAuthentication(bob,
+ AuthorityUtils.createAuthorityList("FACTOR_TWO"));
+ WebAuthnAuthentication result = factorOne.toBuilder().apply(factorTwo).build();
+ Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
+ assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
+ assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
+ }
+
}