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 720984d249..c05d5c5964 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,7 +23,6 @@ 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; @@ -35,8 +34,7 @@ import org.springframework.util.ObjectUtils; * @author Ben Alex * @author Scott Battaglia */ -public class CasAuthenticationToken extends AbstractAuthenticationToken - implements BuildableAuthentication, Serializable { +public class CasAuthenticationToken extends AbstractAuthenticationToken implements 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 40a0f50cf0..0337bafaf9 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,7 +22,6 @@ 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; @@ -33,8 +32,7 @@ import org.springframework.util.Assert; * @author Hal Deadman * @since 6.1 */ -public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationToken - implements BuildableAuthentication { +public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationToken { 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 dd693ac7ae..81d7edeafa 100644 --- a/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java @@ -20,7 +20,6 @@ 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; @@ -33,7 +32,7 @@ import org.springframework.util.Assert; * @author Ben Alex * @author Luke Taylor */ -public class RememberMeAuthenticationToken extends AbstractAuthenticationToken implements BuildableAuthentication { +public class RememberMeAuthenticationToken extends AbstractAuthenticationToken { 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 ea199ecbb9..e17e8d842c 100644 --- a/core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java @@ -22,7 +22,6 @@ 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; @@ -35,7 +34,7 @@ import org.springframework.util.Assert; * * @author Ben Alex */ -public class TestingAuthenticationToken extends AbstractAuthenticationToken implements BuildableAuthentication { +public class TestingAuthenticationToken extends AbstractAuthenticationToken { 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 22572514f2..c63e5dfb34 100644 --- a/core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java @@ -20,7 +20,6 @@ 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; @@ -36,8 +35,7 @@ import org.springframework.util.Assert; * @author Ben Alex * @author Norbert Nowak */ -public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken - implements BuildableAuthentication { +public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken { 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 9fa2f03f3a..2b961f3c32 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,7 +22,6 @@ 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 +31,7 @@ import org.springframework.util.Assert; * @author Josh Cummings * @since 7.0 */ -public class OneTimeTokenAuthentication extends AbstractAuthenticationToken implements BuildableAuthentication { +public class OneTimeTokenAuthentication extends AbstractAuthenticationToken { @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 6439711beb..af581bc9ba 100644 --- a/core/src/main/java/org/springframework/security/core/Authentication.java +++ b/core/src/main/java/org/springframework/security/core/Authentication.java @@ -19,9 +19,7 @@ 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; @@ -140,37 +138,35 @@ public interface Authentication extends Principal, Serializable { void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; /** - * A builder based on a given {@link BuildableAuthentication} instance + * 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 * * @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 deleted file mode 100644 index f49e9a5974..0000000000 --- a/core/src/main/java/org/springframework/security/core/BuildableAuthentication.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 new file mode 100644 index 0000000000..367c01d162 --- /dev/null +++ b/core/src/main/java/org/springframework/security/core/SimpleAuthentication.java @@ -0,0 +1,151 @@ +/* + * 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 15b61d6f46..6bd66b7166 100644 --- a/core/src/test/java/org/springframework/security/authentication/AbstractAuthenticationBuilderTests.java +++ b/core/src/test/java/org/springframework/security/authentication/AbstractAuthenticationBuilderTests.java @@ -37,16 +37,6 @@ 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 f7ea9ed270..8dd98e7feb 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,7 +8,6 @@ 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; @@ -31,9 +30,8 @@ public class CopyAuthoritiesTests { // tag::springSecurity[] Authentication lastestResult = authenticationManager.authenticate(authenticationRequest); Authentication previousResult = SecurityContextHolder.getContext().getAuthentication(); - if (previousResult != null && previousResult.isAuthenticated() && - lastestResult instanceof BuildableAuthentication buildable) { - lastestResult = buildable.toBuilder() + if (previousResult != null && previousResult.isAuthenticated()) { + lastestResult = lastestResult.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 d8ed53a67d..a4d9b9d10b 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,7 +10,6 @@ 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 @@ -29,7 +28,7 @@ class CopyAuthoritiesTests { // tag::springSecurity[] var latestResult: Authentication = authenticationManager.authenticate(authenticationRequest) val previousResult = SecurityContextHolder.getContext().authentication; - if (previousResult?.isAuthenticated == true && latestResult is BuildableAuthentication) { + if (previousResult?.isAuthenticated == true) { 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 71ba397c29..44e6875dc1 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,7 +22,6 @@ 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; @@ -43,7 +42,7 @@ import org.springframework.util.Assert; * @see OAuth2User * @see OAuth2AuthorizedClient */ -public class OAuth2AuthenticationToken extends AbstractAuthenticationToken implements BuildableAuthentication { +public class OAuth2AuthenticationToken extends AbstractAuthenticationToken { 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 8c17af7276..0da2633c7a 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,7 +24,6 @@ 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; @@ -39,8 +38,7 @@ import org.springframework.util.Assert; * @since 5.2 */ @Transient -public class BearerTokenAuthentication extends AbstractOAuth2TokenAuthenticationToken - implements BuildableAuthentication { +public class BearerTokenAuthentication extends AbstractOAuth2TokenAuthenticationToken { 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 10ac07ab06..7e52b30e00 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,7 +22,6 @@ 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; @@ -38,8 +37,7 @@ import org.springframework.util.Assert; * @see Jwt */ @Transient -public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationToken - implements BuildableAuthentication { +public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationToken { 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 1bc65bbd37..89adbf9f5c 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java @@ -30,7 +30,6 @@ 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; @@ -183,9 +182,9 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { } Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication(); if (current != null && current.isAuthenticated()) { - if (authenticationResult instanceof BuildableAuthentication buildable) { - authenticationResult = buildable.toBuilder().authentication(current).build(); - } + authenticationResult = authenticationResult.toBuilder() + .authorities((a) -> a.addAll(current.getAuthorities())) + .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 ff1f9f6b90..faf3d13db4 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,7 +21,6 @@ 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; @@ -34,7 +33,7 @@ import org.springframework.util.Assert; * @see Saml2ResponseAssertionAccessor * @see Saml2ResponseAssertion */ -public class Saml2AssertionAuthentication extends Saml2Authentication implements BuildableAuthentication { +public class Saml2AssertionAuthentication extends Saml2Authentication { @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 a7113235df..19c1dca43a 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,6 +17,8 @@ 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; @@ -39,7 +41,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.BuildableAuthentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -251,9 +253,20 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt } Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication(); if (current != null && current.isAuthenticated()) { - if (authenticationResult instanceof BuildableAuthentication buildable) { - authenticationResult = buildable.toBuilder().authentication(current).build(); - } + 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 } 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 17c32292f7..c1d6d08a24 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,6 +17,8 @@ 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; @@ -31,7 +33,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.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; @@ -187,9 +189,20 @@ public class AuthenticationFilter extends OncePerRequestFilter { } Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication(); if (current != null && current.isAuthenticated()) { - if (authenticationResult instanceof BuildableAuthentication buildable) { - authenticationResult = buildable.toBuilder().authentication(current).build(); - } + 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 } 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 9da11bbaf4..3d8f8acc3f 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,6 +17,8 @@ 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; @@ -35,7 +37,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.BuildableAuthentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; @@ -207,9 +209,20 @@ public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFi Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest); Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication(); if (current != null && current.isAuthenticated()) { - if (authenticationResult instanceof BuildableAuthentication buildable) { - authenticationResult = buildable.toBuilder().authentication(current).build(); - } + 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 } 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 28983c0134..e7d75c3206 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,7 +21,6 @@ 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,8 +31,7 @@ import org.springframework.util.Assert; * @author Ruud Senden * @since 2.0 */ -public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationToken - implements BuildableAuthentication { +public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationToken { 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 db542b2196..e2f6435e78 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,6 +18,8 @@ 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; @@ -31,7 +33,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.BuildableAuthentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; @@ -189,9 +191,20 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter { Authentication authResult = this.authenticationManager.authenticate(authRequest); Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication(); if (current != null && current.isAuthenticated()) { - if (authResult instanceof BuildableAuthentication buildable) { - authResult = buildable.toBuilder().authentication(current).build(); - } + 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 } 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 fa89400309..47d06ee1ac 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,7 +16,9 @@ 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; @@ -27,7 +29,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.BuildableAuthentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.web.server.WebFilterExchange; @@ -139,10 +141,20 @@ public class AuthenticationWebFilter implements WebFilter { if (!current.isAuthenticated()) { return result; } - if (!(result instanceof BuildableAuthentication buildable)) { - return result; - } - return buildable.toBuilder().authentication(current).build(); + 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 }).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 e05f9eae37..bb5b462e92 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,7 +22,6 @@ 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; @@ -35,7 +34,7 @@ import org.springframework.util.Assert; * @since 6.4 * @see WebAuthnAuthenticationRequestToken */ -public class WebAuthnAuthentication extends AbstractAuthenticationToken implements BuildableAuthentication { +public class WebAuthnAuthentication extends AbstractAuthenticationToken { @Serial private static final long serialVersionUID = -4879907158750659197L;