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 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;