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 c05d5c5964..720984d249 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
@@ -23,6 +23,7 @@ import org.apereo.cas.client.validation.Assertion;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
@@ -34,7 +35,8 @@ import org.springframework.util.ObjectUtils;
* @author Ben Alex
* @author Scott Battaglia
*/
-public class CasAuthenticationToken extends AbstractAuthenticationToken implements Serializable {
+public class CasAuthenticationToken extends AbstractAuthenticationToken
+ implements BuildableAuthentication, Serializable {
private static final long serialVersionUID = 620L;
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 0337bafaf9..40a0f50cf0 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.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
@@ -32,7 +33,8 @@ import org.springframework.util.Assert;
* @author Hal Deadman
* @since 6.1
*/
-public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationToken {
+public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationToken
+ implements BuildableAuthentication {
static final String CAS_STATELESS_IDENTIFIER = "_cas_stateless_";
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 81d7edeafa..dd693ac7ae 100644
--- a/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java
+++ b/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java
@@ -20,6 +20,7 @@ import java.util.Collection;
import org.jspecify.annotations.Nullable;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
@@ -32,7 +33,7 @@ import org.springframework.util.Assert;
* @author Ben Alex
* @author Luke Taylor
*/
-public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
+public class RememberMeAuthenticationToken extends AbstractAuthenticationToken implements BuildableAuthentication {
private static final long serialVersionUID = 620L;
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 e17e8d842c..ea199ecbb9 100644
--- a/core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java
+++ b/core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java
@@ -22,6 +22,7 @@ import java.util.List;
import org.jspecify.annotations.Nullable;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.util.Assert;
@@ -34,7 +35,7 @@ import org.springframework.util.Assert;
*
* @author Ben Alex
*/
-public class TestingAuthenticationToken extends AbstractAuthenticationToken {
+public class TestingAuthenticationToken extends AbstractAuthenticationToken implements BuildableAuthentication {
private static final long serialVersionUID = 1L;
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 c63e5dfb34..22572514f2 100644
--- a/core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java
+++ b/core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java
@@ -20,6 +20,7 @@ import java.util.Collection;
import org.jspecify.annotations.Nullable;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
@@ -35,7 +36,8 @@ import org.springframework.util.Assert;
* @author Ben Alex
* @author Norbert Nowak
*/
-public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
+public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken
+ implements BuildableAuthentication {
private static final long serialVersionUID = 620L;
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 2b961f3c32..9fa2f03f3a 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
@@ -22,6 +22,7 @@ import java.util.Collection;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
@@ -31,7 +32,7 @@ import org.springframework.util.Assert;
* @author Josh Cummings
* @since 7.0
*/
-public class OneTimeTokenAuthentication extends AbstractAuthenticationToken {
+public class OneTimeTokenAuthentication extends AbstractAuthenticationToken implements BuildableAuthentication {
@Serial
private static final long serialVersionUID = 1195893764725073959L;
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 af581bc9ba..6439711beb 100644
--- a/core/src/main/java/org/springframework/security/core/Authentication.java
+++ b/core/src/main/java/org/springframework/security/core/Authentication.java
@@ -19,7 +19,9 @@ package org.springframework.security.core;
import java.io.Serializable;
import java.security.Principal;
import java.util.Collection;
+import java.util.Set;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable;
@@ -138,35 +140,37 @@ public interface Authentication extends Principal, Serializable {
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
/**
- * Return an {@link Builder} based on this instance. By default, returns a builder
- * that builds a {@link SimpleAuthentication}.
- *
- * Although a {@code default} method, all {@link Authentication} implementations
- * should implement this. The reason is to ensure that the {@link Authentication} type
- * is preserved when {@link Builder#build} is invoked. This is especially important in
- * the event that your authentication implementation contains custom fields.
- *
- *
- * This isn't strictly necessary since it is recommended that applications code to the
- * {@link Authentication} interface and that custom information is often contained in
- * the {@link Authentication#getPrincipal} value.
- *
- * @return an {@link Builder} for building a new {@link Authentication} based on this
- * instance
- * @since 7.0
- */
- default Builder> toBuilder() {
- return new SimpleAuthentication.Builder(this);
- }
-
- /**
- * A builder based on a given {@link Authentication} instance
+ * A builder based on a given {@link BuildableAuthentication} instance
*
* @author Josh Cummings
* @since 7.0
*/
interface Builder> {
+ /**
+ * Apply this authentication instance
+ *
+ * By default, merges the authorities in the provided {@code authentication} with
+ * the authentication being built. Only those authorities that haven't already
+ * been specified to the builder will be added.
+ *
+ * @param authentication the {@link Authentication} to appluy
+ * @return the {@link Builder} for additional configuration
+ * @see BuildableAuthentication#getAuthorities
+ */
+ default B authentication(Authentication authentication) {
+ return authorities((a) -> {
+ Set newAuthorities = a.stream()
+ .map(GrantedAuthority::getAuthority)
+ .collect(Collectors.toUnmodifiableSet());
+ for (GrantedAuthority currentAuthority : authentication.getAuthorities()) {
+ if (!newAuthorities.contains(currentAuthority.getAuthority())) {
+ a.add(currentAuthority);
+ }
+ }
+ });
+ }
+
/**
* Mutate the authorities with this {@link Consumer}.
*
diff --git a/core/src/main/java/org/springframework/security/core/BuildableAuthentication.java b/core/src/main/java/org/springframework/security/core/BuildableAuthentication.java
new file mode 100644
index 0000000000..f49e9a5974
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/core/BuildableAuthentication.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * 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;
+
+/**
+ * An {@link Authentication} that is also buildable.
+ *
+ * @author Josh Cummings
+ * @since 7.0
+ */
+public interface BuildableAuthentication extends Authentication {
+
+ /**
+ * Return an {@link Builder} based on this instance.
+ *
+ * Although a {@code default} method, all {@link BuildableAuthentication}
+ * implementations should implement this. The reason is to ensure that the
+ * {@link BuildableAuthentication} type is preserved when {@link Builder#build} is
+ * invoked. This is especially important in the event that your authentication
+ * implementation contains custom fields.
+ *
+ *
+ * This isn't strictly necessary since it is recommended that applications code to the
+ * {@link Authentication} interface and that custom information is often contained in
+ * the {@link Authentication#getPrincipal} value.
+ *
+ * @return an {@link Builder} for building a new {@link Authentication} based on this
+ * instance
+ */
+ Builder> toBuilder();
+
+}
diff --git a/core/src/main/java/org/springframework/security/core/SimpleAuthentication.java b/core/src/main/java/org/springframework/security/core/SimpleAuthentication.java
deleted file mode 100644
index 367c01d162..0000000000
--- a/core/src/main/java/org/springframework/security/core/SimpleAuthentication.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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.io.Serial;
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import java.util.function.Consumer;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.jspecify.annotations.Nullable;
-
-@Transient
-final class SimpleAuthentication implements Authentication {
-
- @Serial
- private static final long serialVersionUID = 3194696462184782814L;
-
- private final @Nullable Object principal;
-
- private final @Nullable Object credentials;
-
- private final Collection authorities;
-
- private final @Nullable Object details;
-
- private final boolean authenticated;
-
- private SimpleAuthentication(Builder builder) {
- this.principal = builder.principal;
- this.credentials = builder.credentials;
- this.authorities = builder.authorities;
- this.details = builder.details;
- this.authenticated = builder.authenticated;
- }
-
- @Override
- public Collection extends GrantedAuthority> getAuthorities() {
- return this.authorities;
- }
-
- @Override
- public @Nullable Object getCredentials() {
- return this.credentials;
- }
-
- @Override
- public @Nullable Object getDetails() {
- return this.details;
- }
-
- @Override
- public @Nullable Object getPrincipal() {
- return this.principal;
- }
-
- @Override
- public boolean isAuthenticated() {
- return this.authenticated;
- }
-
- @Override
- public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
- throw new IllegalArgumentException(
- "Instead of calling this setter, please call toBuilder to create a new instance");
- }
-
- @Override
- public String getName() {
- return (this.principal == null) ? "" : this.principal.toString();
- }
-
- static final class Builder implements Authentication.Builder {
-
- private final Log logger = LogFactory.getLog(getClass());
-
- private final Collection authorities = new LinkedHashSet<>();
-
- private @Nullable Object principal;
-
- private @Nullable Object credentials;
-
- private @Nullable Object details;
-
- private boolean authenticated;
-
- Builder(Authentication authentication) {
- this.logger.debug("Creating a builder which will result in exchanging an authentication of type "
- + authentication.getClass() + " for " + SimpleAuthentication.class.getSimpleName() + ";"
- + " consider implementing " + authentication.getClass().getSimpleName() + "#toBuilder");
- this.authorities.addAll(authentication.getAuthorities());
- this.principal = authentication.getPrincipal();
- this.credentials = authentication.getCredentials();
- this.details = authentication.getDetails();
- this.authenticated = authentication.isAuthenticated();
-
- }
-
- @Override
- public Builder authorities(Consumer> authorities) {
- authorities.accept(this.authorities);
- return this;
- }
-
- @Override
- public Builder details(@Nullable Object details) {
- this.details = details;
- return this;
- }
-
- @Override
- public Builder principal(@Nullable Object principal) {
- this.principal = principal;
- return this;
- }
-
- @Override
- public Builder credentials(@Nullable Object credentials) {
- this.credentials = credentials;
- return this;
- }
-
- @Override
- public Builder authenticated(boolean authenticated) {
- this.authenticated = authenticated;
- return this;
- }
-
- @Override
- public Authentication build() {
- return new SimpleAuthentication(this);
- }
-
- }
-
-}
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 6bd66b7166..15b61d6f46 100644
--- a/core/src/test/java/org/springframework/security/authentication/AbstractAuthenticationBuilderTests.java
+++ b/core/src/test/java/org/springframework/security/authentication/AbstractAuthenticationBuilderTests.java
@@ -37,6 +37,16 @@ class AbstractAuthenticationBuilderTests {
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
}
+ @Test
+ void authenticationWhenAuthoritiesThenAdds() {
+ TestingAuthenticationToken factorOne = new TestingAuthenticationToken("user", "pass", "FACTOR_ONE");
+ TestingAuthenticationToken factorTwo = new TestingAuthenticationToken("user", "pass", "FACTOR_TWO");
+ TestAbstractAuthenticationBuilder builder = new TestAbstractAuthenticationBuilder(factorOne);
+ Authentication result = builder.authentication(factorTwo).build();
+ Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
+ assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
+ }
+
private static final class TestAbstractAuthenticationBuilder
extends TestingAuthenticationToken.Builder {
diff --git a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletauthenticationauthentication/CopyAuthoritiesTests.java b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletauthenticationauthentication/CopyAuthoritiesTests.java
index 8dd98e7feb..f7ea9ed270 100644
--- a/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletauthenticationauthentication/CopyAuthoritiesTests.java
+++ b/docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletauthenticationauthentication/CopyAuthoritiesTests.java
@@ -8,6 +8,7 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -30,8 +31,9 @@ public class CopyAuthoritiesTests {
// tag::springSecurity[]
Authentication lastestResult = authenticationManager.authenticate(authenticationRequest);
Authentication previousResult = SecurityContextHolder.getContext().getAuthentication();
- if (previousResult != null && previousResult.isAuthenticated()) {
- lastestResult = lastestResult.toBuilder()
+ if (previousResult != null && previousResult.isAuthenticated() &&
+ lastestResult instanceof BuildableAuthentication buildable) {
+ lastestResult = buildable.toBuilder()
.authorities((a) -> a.addAll(previous.getAuthorities()))
.build();
}
diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletauthenticationauthentication/CopyAuthoritiesTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletauthenticationauthentication/CopyAuthoritiesTests.kt
index a4d9b9d10b..d8ed53a67d 100644
--- a/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletauthenticationauthentication/CopyAuthoritiesTests.kt
+++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletauthenticationauthentication/CopyAuthoritiesTests.kt
@@ -10,6 +10,7 @@ import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication
import org.springframework.security.core.Authentication
+import org.springframework.security.core.BuildableAuthentication
import org.springframework.security.core.authority.AuthorityUtils
import org.springframework.security.core.authority.FactorGrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder
@@ -28,7 +29,7 @@ class CopyAuthoritiesTests {
// tag::springSecurity[]
var latestResult: Authentication = authenticationManager.authenticate(authenticationRequest)
val previousResult = SecurityContextHolder.getContext().authentication;
- if (previousResult?.isAuthenticated == true) {
+ if (previousResult?.isAuthenticated == true && latestResult is BuildableAuthentication) {
latestResult = latestResult.toBuilder().authorities { a ->
a.addAll(previousResult.authorities)
}.build()
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 44e6875dc1..71ba397c29 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
@@ -22,6 +22,7 @@ import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.core.user.OAuth2User;
@@ -42,7 +43,7 @@ import org.springframework.util.Assert;
* @see OAuth2User
* @see OAuth2AuthorizedClient
*/
-public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
+public class OAuth2AuthenticationToken extends AbstractAuthenticationToken implements BuildableAuthentication {
private static final long serialVersionUID = 620L;
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 0da2633c7a..8c17af7276 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
@@ -24,6 +24,7 @@ import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
@@ -38,7 +39,8 @@ import org.springframework.util.Assert;
* @since 5.2
*/
@Transient
-public class BearerTokenAuthentication extends AbstractOAuth2TokenAuthenticationToken {
+public class BearerTokenAuthentication extends AbstractOAuth2TokenAuthenticationToken
+ implements BuildableAuthentication {
private static final long serialVersionUID = 620L;
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 7e52b30e00..10ac07ab06 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
@@ -22,6 +22,7 @@ import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient;
import org.springframework.security.oauth2.jwt.Jwt;
@@ -37,7 +38,8 @@ import org.springframework.util.Assert;
* @see Jwt
*/
@Transient
-public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationToken {
+public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationToken
+ implements BuildableAuthentication {
private static final long serialVersionUID = 620L;
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 89adbf9f5c..1bc65bbd37 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
@@ -30,6 +30,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -182,9 +183,9 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter {
}
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current != null && current.isAuthenticated()) {
- authenticationResult = authenticationResult.toBuilder()
- .authorities((a) -> a.addAll(current.getAuthorities()))
- .build();
+ if (authenticationResult instanceof BuildableAuthentication buildable) {
+ authenticationResult = buildable.toBuilder().authentication(current).build();
+ }
}
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authenticationResult);
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 faf3d13db4..ff1f9f6b90 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
@@ -21,6 +21,7 @@ import java.util.Collection;
import org.jspecify.annotations.Nullable;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.util.Assert;
@@ -33,7 +34,7 @@ import org.springframework.util.Assert;
* @see Saml2ResponseAssertionAccessor
* @see Saml2ResponseAssertion
*/
-public class Saml2AssertionAuthentication extends Saml2Authentication {
+public class Saml2AssertionAuthentication extends Saml2Authentication implements BuildableAuthentication {
@Serial
private static final long serialVersionUID = -4194323643788693205L;
diff --git a/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java b/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java
index 19c1dca43a..a7113235df 100644
--- a/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java
+++ b/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java
@@ -17,8 +17,6 @@
package org.springframework.security.web.authentication;
import java.io.IOException;
-import java.util.Set;
-import java.util.stream.Collectors;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
@@ -41,7 +39,7 @@ import org.springframework.security.authentication.InternalAuthenticationService
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -253,20 +251,9 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
}
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current != null && current.isAuthenticated()) {
- authenticationResult = authenticationResult.toBuilder()
- // @formatter:off
- .authorities((a) -> {
- Set newAuthorities = a.stream()
- .map(GrantedAuthority::getAuthority)
- .collect(Collectors.toUnmodifiableSet());
- for (GrantedAuthority currentAuthority : current.getAuthorities()) {
- if (!newAuthorities.contains(currentAuthority.getAuthority())) {
- a.add(currentAuthority);
- }
- }
- })
- .build();
- // @formatter:on
+ if (authenticationResult instanceof BuildableAuthentication buildable) {
+ authenticationResult = buildable.toBuilder().authentication(current).build();
+ }
}
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
// Authentication success
diff --git a/web/src/main/java/org/springframework/security/web/authentication/AuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/AuthenticationFilter.java
index c1d6d08a24..17c32292f7 100644
--- a/web/src/main/java/org/springframework/security/web/authentication/AuthenticationFilter.java
+++ b/web/src/main/java/org/springframework/security/web/authentication/AuthenticationFilter.java
@@ -17,8 +17,6 @@
package org.springframework.security.web.authentication;
import java.io.IOException;
-import java.util.Set;
-import java.util.stream.Collectors;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
@@ -33,7 +31,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -189,20 +187,9 @@ public class AuthenticationFilter extends OncePerRequestFilter {
}
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current != null && current.isAuthenticated()) {
- authenticationResult = authenticationResult.toBuilder()
- // @formatter:off
- .authorities((a) -> {
- Set newAuthorities = a.stream()
- .map(GrantedAuthority::getAuthority)
- .collect(Collectors.toUnmodifiableSet());
- for (GrantedAuthority currentAuthority : current.getAuthorities()) {
- if (!newAuthorities.contains(currentAuthority.getAuthority())) {
- a.add(currentAuthority);
- }
- }
- })
- .build();
- // @formatter:on
+ if (authenticationResult instanceof BuildableAuthentication buildable) {
+ authenticationResult = buildable.toBuilder().authentication(current).build();
+ }
}
HttpSession session = request.getSession(false);
if (session != null) {
diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java
index 3d8f8acc3f..9da11bbaf4 100755
--- a/web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java
+++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java
@@ -17,8 +17,6 @@
package org.springframework.security.web.authentication.preauth;
import java.io.IOException;
-import java.util.Set;
-import java.util.stream.Collectors;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
@@ -37,7 +35,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -209,20 +207,9 @@ public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFi
Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current != null && current.isAuthenticated()) {
- authenticationResult = authenticationResult.toBuilder()
- // @formatter:off
- .authorities((a) -> {
- Set newAuthorities = a.stream()
- .map(GrantedAuthority::getAuthority)
- .collect(Collectors.toUnmodifiableSet());
- for (GrantedAuthority currentAuthority : current.getAuthorities()) {
- if (!newAuthorities.contains(currentAuthority.getAuthority())) {
- a.add(currentAuthority);
- }
- }
- })
- .build();
- // @formatter:on
+ if (authenticationResult instanceof BuildableAuthentication buildable) {
+ authenticationResult = buildable.toBuilder().authentication(current).build();
+ }
}
successfulAuthentication(request, response, authenticationResult);
}
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 e7d75c3206..28983c0134 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.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
@@ -31,7 +32,8 @@ import org.springframework.util.Assert;
* @author Ruud Senden
* @since 2.0
*/
-public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationToken {
+public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationToken
+ implements BuildableAuthentication {
private static final long serialVersionUID = 620L;
diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java
index e2f6435e78..db542b2196 100644
--- a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java
+++ b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java
@@ -18,8 +18,6 @@ package org.springframework.security.web.authentication.www;
import java.io.IOException;
import java.nio.charset.Charset;
-import java.util.Set;
-import java.util.stream.Collectors;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
@@ -33,7 +31,7 @@ import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -191,20 +189,9 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter {
Authentication authResult = this.authenticationManager.authenticate(authRequest);
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current != null && current.isAuthenticated()) {
- authResult = authResult.toBuilder()
- // @formatter:off
- .authorities((a) -> {
- Set newAuthorities = a.stream()
- .map(GrantedAuthority::getAuthority)
- .collect(Collectors.toUnmodifiableSet());
- for (GrantedAuthority currentAuthority : current.getAuthorities()) {
- if (!newAuthorities.contains(currentAuthority.getAuthority())) {
- a.add(currentAuthority);
- }
- }
- })
- .build();
- // @formatter:on
+ if (authResult instanceof BuildableAuthentication buildable) {
+ authResult = buildable.toBuilder().authentication(current).build();
+ }
}
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authResult);
diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java b/web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java
index 47d06ee1ac..fa89400309 100644
--- a/web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java
+++ b/web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java
@@ -16,9 +16,7 @@
package org.springframework.security.web.server.authentication;
-import java.util.Set;
import java.util.function.Function;
-import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -29,7 +27,7 @@ import org.springframework.security.authentication.ReactiveAuthenticationManager
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.server.WebFilterExchange;
@@ -141,20 +139,10 @@ public class AuthenticationWebFilter implements WebFilter {
if (!current.isAuthenticated()) {
return result;
}
- return result.toBuilder()
- // @formatter:off
- .authorities((a) -> {
- Set newAuthorities = a.stream()
- .map(GrantedAuthority::getAuthority)
- .collect(Collectors.toUnmodifiableSet());
- for (GrantedAuthority currentAuthority : current.getAuthorities()) {
- if (!newAuthorities.contains(currentAuthority.getAuthority())) {
- a.add(currentAuthority);
- }
- }
- })
- .build();
- // @formatter:on
+ if (!(result instanceof BuildableAuthentication buildable)) {
+ return result;
+ }
+ return buildable.toBuilder().authentication(current).build();
}).switchIfEmpty(Mono.just(result));
}
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 bb5b462e92..e05f9eae37 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.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.util.Assert;
@@ -34,7 +35,7 @@ import org.springframework.util.Assert;
* @since 6.4
* @see WebAuthnAuthenticationRequestToken
*/
-public class WebAuthnAuthentication extends AbstractAuthenticationToken {
+public class WebAuthnAuthentication extends AbstractAuthenticationToken implements BuildableAuthentication {
@Serial
private static final long serialVersionUID = -4879907158750659197L;