Merge branch 'builder-enhancements'

Issue gh-18052
Issue gh-18053
This commit is contained in:
Josh Cummings 2025-10-15 12:01:48 -06:00
commit 95644fb73c
24 changed files with 144 additions and 277 deletions

View File

@ -23,6 +23,7 @@ import org.apereo.cas.client.validation.Assertion;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -34,7 +35,8 @@ import org.springframework.util.ObjectUtils;
* @author Ben Alex * @author Ben Alex
* @author Scott Battaglia * @author Scott Battaglia
*/ */
public class CasAuthenticationToken extends AbstractAuthenticationToken implements Serializable { public class CasAuthenticationToken extends AbstractAuthenticationToken
implements BuildableAuthentication, Serializable {
private static final long serialVersionUID = 620L; private static final long serialVersionUID = 620L;

View File

@ -22,6 +22,7 @@ import java.util.Collection;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -32,7 +33,8 @@ import org.springframework.util.Assert;
* @author Hal Deadman * @author Hal Deadman
* @since 6.1 * @since 6.1
*/ */
public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationToken { public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationToken
implements BuildableAuthentication {
static final String CAS_STATELESS_IDENTIFIER = "_cas_stateless_"; static final String CAS_STATELESS_IDENTIFIER = "_cas_stateless_";

View File

@ -20,6 +20,7 @@ import java.util.Collection;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -32,7 +33,7 @@ import org.springframework.util.Assert;
* @author Ben Alex * @author Ben Alex
* @author Luke Taylor * @author Luke Taylor
*/ */
public class RememberMeAuthenticationToken extends AbstractAuthenticationToken { public class RememberMeAuthenticationToken extends AbstractAuthenticationToken implements BuildableAuthentication {
private static final long serialVersionUID = 620L; private static final long serialVersionUID = 620L;

View File

@ -22,6 +22,7 @@ import java.util.List;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -34,7 +35,7 @@ import org.springframework.util.Assert;
* *
* @author Ben Alex * @author Ben Alex
*/ */
public class TestingAuthenticationToken extends AbstractAuthenticationToken { public class TestingAuthenticationToken extends AbstractAuthenticationToken implements BuildableAuthentication {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -20,6 +20,7 @@ import java.util.Collection;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -35,7 +36,8 @@ import org.springframework.util.Assert;
* @author Ben Alex * @author Ben Alex
* @author Norbert Nowak * @author Norbert Nowak
*/ */
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken { public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken
implements BuildableAuthentication {
private static final long serialVersionUID = 620L; private static final long serialVersionUID = 620L;

View File

@ -22,6 +22,7 @@ import java.util.Collection;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -31,7 +32,7 @@ import org.springframework.util.Assert;
* @author Josh Cummings * @author Josh Cummings
* @since 7.0 * @since 7.0
*/ */
public class OneTimeTokenAuthentication extends AbstractAuthenticationToken { public class OneTimeTokenAuthentication extends AbstractAuthenticationToken implements BuildableAuthentication {
@Serial @Serial
private static final long serialVersionUID = 1195893764725073959L; private static final long serialVersionUID = 1195893764725073959L;

View File

@ -19,7 +19,9 @@ package org.springframework.security.core;
import java.io.Serializable; import java.io.Serializable;
import java.security.Principal; import java.security.Principal;
import java.util.Collection; import java.util.Collection;
import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
@ -138,35 +140,37 @@ public interface Authentication extends Principal, Serializable {
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
/** /**
* Return an {@link Builder} based on this instance. By default, returns a builder * A builder based on a given {@link BuildableAuthentication} instance
* that builds a {@link SimpleAuthentication}.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* @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 * @author Josh Cummings
* @since 7.0 * @since 7.0
*/ */
interface Builder<B extends Builder<B>> { interface Builder<B extends Builder<B>> {
/**
* Apply this authentication instance
* <p>
* 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.
* </p>
* @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<String> 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}. * Mutate the authorities with this {@link Consumer}.
* <p> * <p>

View File

@ -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.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* @return an {@link Builder} for building a new {@link Authentication} based on this
* instance
*/
Builder<?> toBuilder();
}

View File

@ -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<GrantedAuthority> authorities;
private final @Nullable Object details;
private final boolean authenticated;
private SimpleAuthentication(Builder builder) {
this.principal = builder.principal;
this.credentials = builder.credentials;
this.authorities = builder.authorities;
this.details = builder.details;
this.authenticated = builder.authenticated;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public @Nullable Object getCredentials() {
return this.credentials;
}
@Override
public @Nullable Object getDetails() {
return this.details;
}
@Override
public @Nullable Object getPrincipal() {
return this.principal;
}
@Override
public boolean isAuthenticated() {
return this.authenticated;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
throw new IllegalArgumentException(
"Instead of calling this setter, please call toBuilder to create a new instance");
}
@Override
public String getName() {
return (this.principal == null) ? "" : this.principal.toString();
}
static final class Builder implements Authentication.Builder<Builder> {
private final Log logger = LogFactory.getLog(getClass());
private final Collection<GrantedAuthority> 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<Collection<GrantedAuthority>> 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);
}
}
}

View File

@ -37,6 +37,16 @@ class AbstractAuthenticationBuilderTests {
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO"); 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<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
}
private static final class TestAbstractAuthenticationBuilder private static final class TestAbstractAuthenticationBuilder
extends TestingAuthenticationToken.Builder<TestAbstractAuthenticationBuilder> { extends TestingAuthenticationToken.Builder<TestAbstractAuthenticationBuilder> {

View File

@ -8,6 +8,7 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication; import org.springframework.security.authentication.ott.OneTimeTokenAuthentication;
import org.springframework.security.core.Authentication; 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.AuthorityUtils;
import org.springframework.security.core.authority.FactorGrantedAuthority; import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -30,8 +31,9 @@ public class CopyAuthoritiesTests {
// tag::springSecurity[] // tag::springSecurity[]
Authentication lastestResult = authenticationManager.authenticate(authenticationRequest); Authentication lastestResult = authenticationManager.authenticate(authenticationRequest);
Authentication previousResult = SecurityContextHolder.getContext().getAuthentication(); Authentication previousResult = SecurityContextHolder.getContext().getAuthentication();
if (previousResult != null && previousResult.isAuthenticated()) { if (previousResult != null && previousResult.isAuthenticated() &&
lastestResult = lastestResult.toBuilder() lastestResult instanceof BuildableAuthentication buildable) {
lastestResult = buildable.toBuilder()
.authorities((a) -> a.addAll(previous.getAuthorities())) .authorities((a) -> a.addAll(previous.getAuthorities()))
.build(); .build();
} }

View File

@ -10,6 +10,7 @@ import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication import org.springframework.security.authentication.ott.OneTimeTokenAuthentication
import org.springframework.security.core.Authentication 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.AuthorityUtils
import org.springframework.security.core.authority.FactorGrantedAuthority import org.springframework.security.core.authority.FactorGrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.core.context.SecurityContextHolder
@ -28,7 +29,7 @@ class CopyAuthoritiesTests {
// tag::springSecurity[] // tag::springSecurity[]
var latestResult: Authentication = authenticationManager.authenticate(authenticationRequest) var latestResult: Authentication = authenticationManager.authenticate(authenticationRequest)
val previousResult = SecurityContextHolder.getContext().authentication; val previousResult = SecurityContextHolder.getContext().authentication;
if (previousResult?.isAuthenticated == true) { if (previousResult?.isAuthenticated == true && latestResult is BuildableAuthentication) {
latestResult = latestResult.toBuilder().authorities { a -> latestResult = latestResult.toBuilder().authorities { a ->
a.addAll(previousResult.authorities) a.addAll(previousResult.authorities)
}.build() }.build()

View File

@ -22,6 +22,7 @@ import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
@ -42,7 +43,7 @@ import org.springframework.util.Assert;
* @see OAuth2User * @see OAuth2User
* @see OAuth2AuthorizedClient * @see OAuth2AuthorizedClient
*/ */
public class OAuth2AuthenticationToken extends AbstractAuthenticationToken { public class OAuth2AuthenticationToken extends AbstractAuthenticationToken implements BuildableAuthentication {
private static final long serialVersionUID = 620L; private static final long serialVersionUID = 620L;

View File

@ -24,6 +24,7 @@ import java.util.Map;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient; import org.springframework.security.core.Transient;
import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AccessToken;
@ -38,7 +39,8 @@ import org.springframework.util.Assert;
* @since 5.2 * @since 5.2
*/ */
@Transient @Transient
public class BearerTokenAuthentication extends AbstractOAuth2TokenAuthenticationToken<OAuth2AccessToken> { public class BearerTokenAuthentication extends AbstractOAuth2TokenAuthenticationToken<OAuth2AccessToken>
implements BuildableAuthentication {
private static final long serialVersionUID = 620L; private static final long serialVersionUID = 620L;

View File

@ -22,6 +22,7 @@ import java.util.Map;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient; import org.springframework.security.core.Transient;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
@ -37,7 +38,8 @@ import org.springframework.util.Assert;
* @see Jwt * @see Jwt
*/ */
@Transient @Transient
public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationToken<Jwt> { public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationToken<Jwt>
implements BuildableAuthentication {
private static final long serialVersionUID = 620L; private static final long serialVersionUID = 620L;

View File

@ -30,6 +30,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; 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.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -182,9 +183,9 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter {
} }
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication(); Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current != null && current.isAuthenticated()) { if (current != null && current.isAuthenticated()) {
authenticationResult = authenticationResult.toBuilder() if (authenticationResult instanceof BuildableAuthentication buildable) {
.authorities((a) -> a.addAll(current.getAuthorities())) authenticationResult = buildable.toBuilder().authentication(current).build();
.build(); }
} }
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authenticationResult); context.setAuthentication(authenticationResult);

View File

@ -21,6 +21,7 @@ import java.util.Collection;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -33,7 +34,7 @@ import org.springframework.util.Assert;
* @see Saml2ResponseAssertionAccessor * @see Saml2ResponseAssertionAccessor
* @see Saml2ResponseAssertion * @see Saml2ResponseAssertion
*/ */
public class Saml2AssertionAuthentication extends Saml2Authentication { public class Saml2AssertionAuthentication extends Saml2Authentication implements BuildableAuthentication {
@Serial @Serial
private static final long serialVersionUID = -4194323643788693205L; private static final long serialVersionUID = -4194323643788693205L;

View File

@ -17,8 +17,6 @@
package org.springframework.security.web.authentication; package org.springframework.security.web.authentication;
import java.io.IOException; import java.io.IOException;
import java.util.Set;
import java.util.stream.Collectors;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; 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.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; 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.SpringSecurityMessageSource;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -253,20 +251,9 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
} }
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication(); Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current != null && current.isAuthenticated()) { if (current != null && current.isAuthenticated()) {
authenticationResult = authenticationResult.toBuilder() if (authenticationResult instanceof BuildableAuthentication buildable) {
// @formatter:off authenticationResult = buildable.toBuilder().authentication(current).build();
.authorities((a) -> { }
Set<String> 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); this.sessionStrategy.onAuthentication(authenticationResult, request, response);
// Authentication success // Authentication success

View File

@ -17,8 +17,6 @@
package org.springframework.security.web.authentication; package org.springframework.security.web.authentication;
import java.io.IOException; import java.io.IOException;
import java.util.Set;
import java.util.stream.Collectors;
import jakarta.servlet.Filter; import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
@ -33,7 +31,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; 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.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -189,20 +187,9 @@ public class AuthenticationFilter extends OncePerRequestFilter {
} }
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication(); Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current != null && current.isAuthenticated()) { if (current != null && current.isAuthenticated()) {
authenticationResult = authenticationResult.toBuilder() if (authenticationResult instanceof BuildableAuthentication buildable) {
// @formatter:off authenticationResult = buildable.toBuilder().authentication(current).build();
.authorities((a) -> { }
Set<String> 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); HttpSession session = request.getSession(false);
if (session != null) { if (session != null) {

View File

@ -17,8 +17,6 @@
package org.springframework.security.web.authentication.preauth; package org.springframework.security.web.authentication.preauth;
import java.io.IOException; import java.io.IOException;
import java.util.Set;
import java.util.stream.Collectors;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; 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.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; 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.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -209,20 +207,9 @@ public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFi
Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest); Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication(); Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current != null && current.isAuthenticated()) { if (current != null && current.isAuthenticated()) {
authenticationResult = authenticationResult.toBuilder() if (authenticationResult instanceof BuildableAuthentication buildable) {
// @formatter:off authenticationResult = buildable.toBuilder().authentication(current).build();
.authorities((a) -> { }
Set<String> 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); successfulAuthentication(request, response, authenticationResult);
} }

View File

@ -21,6 +21,7 @@ import java.util.Collection;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -31,7 +32,8 @@ import org.springframework.util.Assert;
* @author Ruud Senden * @author Ruud Senden
* @since 2.0 * @since 2.0
*/ */
public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationToken { public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationToken
implements BuildableAuthentication {
private static final long serialVersionUID = 620L; private static final long serialVersionUID = 620L;

View File

@ -18,8 +18,6 @@ package org.springframework.security.web.authentication.www;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Set;
import java.util.stream.Collectors;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
@ -33,7 +31,7 @@ import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; 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.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -191,20 +189,9 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter {
Authentication authResult = this.authenticationManager.authenticate(authRequest); Authentication authResult = this.authenticationManager.authenticate(authRequest);
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication(); Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current != null && current.isAuthenticated()) { if (current != null && current.isAuthenticated()) {
authResult = authResult.toBuilder() if (authResult instanceof BuildableAuthentication buildable) {
// @formatter:off authResult = buildable.toBuilder().authentication(current).build();
.authorities((a) -> { }
Set<String> 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(); SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authResult); context.setAuthentication(authResult);

View File

@ -16,9 +16,7 @@
package org.springframework.security.web.server.authentication; package org.springframework.security.web.server.authentication;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; 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.authentication.ReactiveAuthenticationManagerResolver;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; 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.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.WebFilterExchange;
@ -141,20 +139,10 @@ public class AuthenticationWebFilter implements WebFilter {
if (!current.isAuthenticated()) { if (!current.isAuthenticated()) {
return result; return result;
} }
return result.toBuilder() if (!(result instanceof BuildableAuthentication buildable)) {
// @formatter:off return result;
.authorities((a) -> { }
Set<String> newAuthorities = a.stream() return buildable.toBuilder().authentication(current).build();
.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)); }).switchIfEmpty(Mono.just(result));
} }

View File

@ -22,6 +22,7 @@ import java.util.Collection;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.BuildableAuthentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -34,7 +35,7 @@ import org.springframework.util.Assert;
* @since 6.4 * @since 6.4
* @see WebAuthnAuthenticationRequestToken * @see WebAuthnAuthenticationRequestToken
*/ */
public class WebAuthnAuthentication extends AbstractAuthenticationToken { public class WebAuthnAuthentication extends AbstractAuthenticationToken implements BuildableAuthentication {
@Serial @Serial
private static final long serialVersionUID = -4879907158750659197L; private static final long serialVersionUID = -4879907158750659197L;