From e535e61c8b9745e77d799a7e40e093af40dd0727 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 14 Oct 2025 16:14:57 -0600 Subject: [PATCH] Move toBuilder to BuildableAuthentication Closes gh-18052 --- .../CasAuthenticationToken.java | 4 +- .../CasServiceTicketAuthenticationToken.java | 4 +- .../AbstractAuthenticationToken.java | 9 +- .../RememberMeAuthenticationToken.java | 3 +- .../TestingAuthenticationToken.java | 3 +- .../UsernamePasswordAuthenticationToken.java | 4 +- .../ott/OneTimeTokenAuthentication.java | 3 +- .../security/core/Authentication.java | 107 ------------ .../core/BuildableAuthentication.java | 163 ++++++++++++++++++ .../security/core/SimpleAuthentication.java | 151 ---------------- .../CopyAuthoritiesTests.java | 6 +- .../CopyAuthoritiesTests.kt | 3 +- .../OAuth2AuthenticationToken.java | 3 +- .../BearerTokenAuthentication.java | 4 +- .../JwtAuthenticationToken.java | 4 +- .../BearerTokenAuthenticationFilter.java | 9 +- .../Saml2AssertionAuthentication.java | 3 +- ...bstractAuthenticationProcessingFilter.java | 29 ++-- .../authentication/AuthenticationFilter.java | 29 ++-- ...tractPreAuthenticatedProcessingFilter.java | 29 ++-- .../PreAuthenticatedAuthenticationToken.java | 4 +- .../www/BasicAuthenticationFilter.java | 29 ++-- .../AuthenticationWebFilter.java | 6 +- .../WebAuthnAuthentication.java | 3 +- 24 files changed, 279 insertions(+), 333 deletions(-) create mode 100644 core/src/main/java/org/springframework/security/core/BuildableAuthentication.java delete mode 100644 core/src/main/java/org/springframework/security/core/SimpleAuthentication.java 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/AbstractAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/AbstractAuthenticationToken.java index 18307f8b64..135066c618 100644 --- a/core/src/main/java/org/springframework/security/authentication/AbstractAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/AbstractAuthenticationToken.java @@ -28,6 +28,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.security.core.AuthenticatedPrincipal; import org.springframework.security.core.Authentication; +import org.springframework.security.core.BuildableAuthentication; import org.springframework.security.core.CredentialsContainer; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; @@ -198,15 +199,15 @@ public abstract class AbstractAuthenticationToken implements Authentication, Cre } /** - * A common abstract implementation of {@link Authentication.Builder}. It implements - * the builder methods that correspond to the {@link Authentication} methods that - * {@link AbstractAuthenticationToken} implements + * A common abstract implementation of {@link BuildableAuthentication.Builder}. It + * implements the builder methods that correspond to the {@link Authentication} + * methods that {@link AbstractAuthenticationToken} implements * * @param * @since 7.0 */ protected abstract static class AbstractAuthenticationBuilder> - implements Authentication.Builder { + implements BuildableAuthentication.Builder { private boolean authenticated; 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..c8515b25fc 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,6 @@ package org.springframework.security.core; import java.io.Serializable; import java.security.Principal; import java.util.Collection; -import java.util.function.Consumer; import org.jspecify.annotations.Nullable; @@ -137,110 +136,4 @@ 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 - * - * @author Josh Cummings - * @since 7.0 - */ - interface Builder> { - - /** - * Mutate the authorities with this {@link Consumer}. - *

- * Note that since a non-empty set of authorities implies an - * {@link Authentication} is authenticated, this method also marks the - * authentication as {@link #authenticated} by default. - *

- * @param authorities a consumer that receives the full set of authorities - * @return the {@link Builder} for additional configuration - * @see Authentication#getAuthorities - */ - B authorities(Consumer> authorities); - - /** - * Use this credential. - *

- * Note that since some credentials are insecure to store, this method is - * implemented as unsupported by default. Only implement or use this method if you - * support secure storage of the credential or if your implementation also - * implements {@link CredentialsContainer} and the credentials are thereby erased. - *

- * @param credentials the credentials to use - * @return the {@link Builder} for additional configuration - * @see Authentication#getCredentials - */ - default B credentials(@Nullable Object credentials) { - throw new UnsupportedOperationException( - String.format("%s does not store credentials", this.getClass().getSimpleName())); - } - - /** - * Use this details object. - *

- * Implementations may choose to use these {@code details} in combination with any - * principal from the pre-existing {@link Authentication} instance. - *

- * @param details the details to use - * @return the {@link Builder} for additional configuration - * @see Authentication#getDetails - */ - B details(@Nullable Object details); - - /** - * Use this principal. - *

- * Note that in many cases, the principal is strongly-typed. Implementations may - * choose to do a type check and are not necessarily expected to allow any object - * as a principal. - *

- *

- * Implementations may choose to use this {@code principal} in combination with - * any principal from the pre-existing {@link Authentication} instance. - *

- * @param principal the principal to use - * @return the {@link Builder} for additional configuration - * @see Authentication#getPrincipal - */ - B principal(@Nullable Object principal); - - /** - * Mark this authentication as authenticated or not - * @param authenticated whether this is an authenticated {@link Authentication} - * instance - * @return the {@link Builder} for additional configuration - * @see Authentication#isAuthenticated - */ - B authenticated(boolean authenticated); - - /** - * Build an {@link Authentication} instance - * @return the {@link Authentication} instance - */ - Authentication build(); - - } - } 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..b4ce0d3c34 --- /dev/null +++ b/core/src/main/java/org/springframework/security/core/BuildableAuthentication.java @@ -0,0 +1,163 @@ +/* + * 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; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.jspecify.annotations.Nullable; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * Represents the token for an authentication request or for an authenticated principal + * once the request has been processed by the + * {@link AuthenticationManager#authenticate(Authentication)} method. + *

+ * Once the request has been authenticated, the Authentication will usually be + * stored in a thread-local SecurityContext managed by the + * {@link SecurityContextHolder} by the authentication mechanism which is being used. An + * explicit authentication can be achieved, without using one of Spring Security's + * authentication mechanisms, by creating an Authentication instance and using + * the code: + * + *

+ * SecurityContext context = SecurityContextHolder.createEmptyContext();
+ * context.setAuthentication(anAuthentication);
+ * SecurityContextHolder.setContext(context);
+ * 
+ * + * Note that unless the Authentication has the authenticated property + * set to true, it will still be authenticated by any security interceptor (for + * method or web invocations) which encounters it. + *

+ * In most cases, the framework transparently takes care of managing the security context + * and authentication objects for you. + * + * @author Ben Alex + */ +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 BuildableAuthentication} based + * on this instance + * @since 7.0 + */ + Builder toBuilder(); + + /** + * A builder based on a given {@link BuildableAuthentication} instance + * + * @author Josh Cummings + * @since 7.0 + */ + interface Builder> { + + /** + * Mutate the authorities with this {@link Consumer}. + *

+ * Note that since a non-empty set of authorities implies an + * {@link Authentication} is authenticated, this method also marks the + * authentication as {@link #authenticated} by default. + *

+ * @param authorities a consumer that receives the full set of authorities + * @return the {@link Builder} for additional configuration + * @see Authentication#getAuthorities + */ + B authorities(Consumer> authorities); + + /** + * Use this credential. + *

+ * Note that since some credentials are insecure to store, this method is + * implemented as unsupported by default. Only implement or use this method if you + * support secure storage of the credential or if your implementation also + * implements {@link CredentialsContainer} and the credentials are thereby erased. + *

+ * @param credentials the credentials to use + * @return the {@link Builder} for additional configuration + * @see Authentication#getCredentials + */ + default B credentials(@Nullable Object credentials) { + throw new UnsupportedOperationException( + String.format("%s does not store credentials", this.getClass().getSimpleName())); + } + + /** + * Use this details object. + *

+ * Implementations may choose to use these {@code details} in combination with any + * principal from the pre-existing {@link Authentication} instance. + *

+ * @param details the details to use + * @return the {@link Builder} for additional configuration + * @see Authentication#getDetails + */ + B details(@Nullable Object details); + + /** + * Use this principal. + *

+ * Note that in many cases, the principal is strongly-typed. Implementations may + * choose to do a type check and are not necessarily expected to allow any object + * as a principal. + *

+ *

+ * Implementations may choose to use this {@code principal} in combination with + * any principal from the pre-existing {@link Authentication} instance. + *

+ * @param principal the principal to use + * @return the {@link Builder} for additional configuration + * @see Authentication#getPrincipal + */ + B principal(@Nullable Object principal); + + /** + * Mark this authentication as authenticated or not + * @param authenticated whether this is an authenticated + * {@link Authentication} instance + * @return the {@link Builder} for additional configuration + * @see Authentication#isAuthenticated + */ + B authenticated(boolean authenticated); + + /** + * Build an {@link Authentication} instance + * @return the {@link Authentication} instance + */ + Authentication build(); + + } + +} 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/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..88366dca7f 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,11 @@ 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() + .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 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..6f90f475cc 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 @@ -41,6 +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; @@ -253,20 +254,22 @@ 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); + if (authenticationResult instanceof BuildableAuthentication buildable) { + authenticationResult = buildable.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 + }) + .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 c1d6d08a24..40ef7f4951 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 @@ -33,6 +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; @@ -189,20 +190,22 @@ 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); + if (authenticationResult instanceof BuildableAuthentication buildable) { + authenticationResult = buildable.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 + }) + .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 3d8f8acc3f..dab00ce0f1 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 @@ -37,6 +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; @@ -209,20 +210,22 @@ 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); + if (authenticationResult instanceof BuildableAuthentication buildable) { + authenticationResult = buildable.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 + }) + .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 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..92b96e8274 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 @@ -33,6 +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; @@ -191,20 +192,22 @@ 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); + if (authResult instanceof BuildableAuthentication buildable) { + authResult = buildable.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 + }) + .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 47d06ee1ac..207783223a 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 @@ -29,6 +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; @@ -141,7 +142,10 @@ public class AuthenticationWebFilter implements WebFilter { if (!current.isAuthenticated()) { return result; } - return result.toBuilder() + if (!(result instanceof BuildableAuthentication buildable)) { + return result; + } + return buildable.toBuilder() // @formatter:off .authorities((a) -> { Set newAuthorities = a.stream() 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;