Skip to content

Commit 7c441d5

Browse files
committed
Apply Existing Authorities
1 parent 4ee5dcc commit 7c441d5

File tree

37 files changed

+1160
-3
lines changed

37 files changed

+1160
-3
lines changed

cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import java.util.Collection;
2121

2222
import org.apereo.cas.client.validation.Assertion;
23+
import org.jspecify.annotations.NonNull;
2324

2425
import org.springframework.security.authentication.AbstractAuthenticationToken;
26+
import org.springframework.security.core.Authentication;
2527
import org.springframework.security.core.GrantedAuthority;
2628
import org.springframework.security.core.userdetails.UserDetails;
2729
import org.springframework.util.Assert;
@@ -153,6 +155,11 @@ public UserDetails getUserDetails() {
153155
return this.userDetails;
154156
}
155157

158+
@Override
159+
public Builder toBuilder() {
160+
return new Builder().apply(this);
161+
}
162+
156163
@Override
157164
public String toString() {
158165
StringBuilder sb = new StringBuilder();
@@ -162,4 +169,66 @@ public String toString() {
162169
return (sb.toString());
163170
}
164171

172+
/**
173+
* A builder preserving the concrete {@link Authentication} type
174+
*
175+
* @since 7.0
176+
*/
177+
public static final class Builder extends AbstractAuthenticationBuilder<@NonNull CasAuthenticationToken, Builder> {
178+
179+
private Integer keyHash;
180+
181+
private Object principal;
182+
183+
private Object credentials;
184+
185+
private UserDetails userDetails;
186+
187+
private Assertion assertion;
188+
189+
private Builder() {
190+
191+
}
192+
193+
public Builder apply(CasAuthenticationToken authentication) {
194+
return super.apply(authentication).keyHash(authentication.keyHash)
195+
.principal(authentication.principal)
196+
.credentials(authentication.credentials)
197+
.userDetails(authentication.userDetails)
198+
.assertion(authentication.assertion);
199+
}
200+
201+
public Builder keyHash(Integer keyHash) {
202+
this.keyHash = keyHash;
203+
return this;
204+
}
205+
206+
public Builder principal(Object principal) {
207+
this.principal = principal;
208+
return this;
209+
}
210+
211+
public Builder credentials(Object credentials) {
212+
this.credentials = credentials;
213+
return this;
214+
}
215+
216+
public Builder userDetails(UserDetails userDetails) {
217+
this.userDetails = userDetails;
218+
return this;
219+
}
220+
221+
public Builder assertion(Assertion assertion) {
222+
this.assertion = assertion;
223+
return this;
224+
}
225+
226+
@Override
227+
protected @NonNull CasAuthenticationToken build(Collection<GrantedAuthority> authorities) {
228+
return new CasAuthenticationToken(this.keyHash, this.principal, this.credentials, authorities,
229+
this.userDetails, this.assertion);
230+
}
231+
232+
}
233+
165234
}

cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.Collections;
2020
import java.util.List;
21+
import java.util.Set;
2122

2223
import org.apereo.cas.client.validation.Assertion;
2324
import org.apereo.cas.client.validation.AssertionImpl;
@@ -26,6 +27,7 @@
2627
import org.springframework.security.core.GrantedAuthority;
2728
import org.springframework.security.core.authority.AuthorityUtils;
2829
import org.springframework.security.core.authority.SimpleGrantedAuthority;
30+
import org.springframework.security.core.userdetails.PasswordEncodedUser;
2931
import org.springframework.security.core.userdetails.User;
3032
import org.springframework.security.core.userdetails.UserDetails;
3133

@@ -155,4 +157,22 @@ public void testToString() {
155157
assertThat(result.lastIndexOf("Credentials (Service/Proxy Ticket):") != -1).isTrue();
156158
}
157159

160+
@Test
161+
public void toBuilderWhenApplyThenCopies() {
162+
Assertion assertionOne = new AssertionImpl("test");
163+
CasAuthenticationToken factorOne = new CasAuthenticationToken("key", "alice", "pass",
164+
AuthorityUtils.createAuthorityList("FACTOR_ONE"), PasswordEncodedUser.user(), assertionOne);
165+
Assertion assertionTwo = new AssertionImpl("test");
166+
CasAuthenticationToken factorTwo = new CasAuthenticationToken("yek", "bob", "ssap",
167+
AuthorityUtils.createAuthorityList("FACTOR_TWO"), PasswordEncodedUser.admin(), assertionTwo);
168+
CasAuthenticationToken authentication = factorOne.toBuilder().apply(factorTwo).build();
169+
Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
170+
assertThat(authentication.getKeyHash()).isEqualTo(factorTwo.getKeyHash());
171+
assertThat(authentication.getPrincipal()).isEqualTo(factorTwo.getPrincipal());
172+
assertThat(authentication.getCredentials()).isEqualTo(factorTwo.getCredentials());
173+
assertThat(authentication.getUserDetails()).isEqualTo(factorTwo.getUserDetails());
174+
assertThat(authentication.getAssertion()).isEqualTo(factorTwo.getAssertion());
175+
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
176+
}
177+
158178
}

config/src/main/java/org/springframework/security/config/annotation/authentication/builders/AuthenticationManagerBuilder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
3838
import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsAwareConfigurer;
3939
import org.springframework.security.core.Authentication;
40+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
4041
import org.springframework.security.core.userdetails.UserDetailsService;
4142
import org.springframework.util.Assert;
4243

@@ -235,6 +236,11 @@ protected ProviderManager performBuild() throws Exception {
235236
if (this.eventPublisher != null) {
236237
providerManager.setAuthenticationEventPublisher(this.eventPublisher);
237238
}
239+
SecurityContextHolderStrategy securityContextHolderStrategy = getSharedObject(
240+
SecurityContextHolderStrategy.class);
241+
if (securityContextHolderStrategy != null) {
242+
providerManager.setSecurityContextHolderStrategy(securityContextHolderStrategy);
243+
}
238244
providerManager = postProcess(providerManager);
239245
return providerManager;
240246
}

config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
4848
import org.springframework.security.core.Authentication;
4949
import org.springframework.security.core.AuthenticationException;
50+
import org.springframework.security.core.context.SecurityContextHolder;
51+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
5052
import org.springframework.security.core.userdetails.UserDetailsService;
5153
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
5254
import org.springframework.security.crypto.password.PasswordEncoder;
@@ -83,6 +85,9 @@ public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProce
8385
AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context);
8486
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
8587
objectPostProcessor, defaultPasswordEncoder);
88+
result.setSharedObject(SecurityContextHolderStrategy.class,
89+
this.applicationContext.getBeanProvider(SecurityContextHolderStrategy.class)
90+
.getIfUnique(SecurityContextHolder::getContextHolderStrategy));
8691
if (authenticationEventPublisher != null) {
8792
result.authenticationEventPublisher(authenticationEventPublisher);
8893
}

config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,8 +316,12 @@ protected AuthenticationManager authenticationManager() throws Exception {
316316
if (this.authenticationManager == null) {
317317
DefaultAuthenticationEventPublisher eventPublisher = this.objectPostProcessor
318318
.postProcess(new DefaultAuthenticationEventPublisher());
319+
SecurityContextHolderStrategy securityContextHolderStrategy = this.context
320+
.getBeanProvider(SecurityContextHolderStrategy.class)
321+
.getIfUnique(SecurityContextHolder::getContextHolderStrategy);
319322
this.auth = new AuthenticationManagerBuilder(this.objectPostProcessor);
320323
this.auth.authenticationEventPublisher(eventPublisher);
324+
this.auth.setSharedObject(SecurityContextHolderStrategy.class, securityContextHolderStrategy);
321325
configure(this.auth);
322326
this.authenticationManager = (this.disableAuthenticationRegistry)
323327
? getAuthenticationConfiguration().getAuthenticationManager() : this.auth.build();

config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ HttpSecurity httpSecurity() throws Exception {
110110
LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
111111
AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
112112
this.objectPostProcessor, passwordEncoder);
113+
authenticationBuilder.setSharedObject(SecurityContextHolderStrategy.class,
114+
this.context.getBeanProvider(SecurityContextHolderStrategy.class)
115+
.getIfUnique(SecurityContextHolder::getContextHolderStrategy));
113116
authenticationBuilder.parentAuthenticationManager(authenticationManager());
114117
authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
115118
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());

config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,10 @@ public void configure(H http) throws Exception {
162162
WebAuthnRelyingPartyOperations rpOperations = webAuthnRelyingPartyOperations(userEntities, userCredentials);
163163
PublicKeyCredentialCreationOptionsRepository creationOptionsRepository = creationOptionsRepository();
164164
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
165-
webAuthnAuthnFilter.setAuthenticationManager(
166-
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
165+
ProviderManager manager = new ProviderManager(
166+
new WebAuthnAuthenticationProvider(rpOperations, userDetailsService));
167+
manager.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
168+
webAuthnAuthnFilter.setAuthenticationManager(manager);
167169
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
168170
rpOperations);
169171
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(

config/src/main/java/org/springframework/security/config/authentication/AuthenticationManagerFactoryBean.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import org.springframework.security.authentication.ProviderManager;
3131
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
3232
import org.springframework.security.config.BeanIds;
33+
import org.springframework.security.core.context.SecurityContextHolder;
34+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3335
import org.springframework.security.core.userdetails.UserDetailsService;
3436
import org.springframework.security.crypto.password.PasswordEncoder;
3537

@@ -72,6 +74,10 @@ public AuthenticationManager getObject() throws Exception {
7274
}
7375
provider.afterPropertiesSet();
7476
ProviderManager manager = new ProviderManager(Arrays.asList(provider));
77+
SecurityContextHolderStrategy securityContextHolderStrategy = this.bf
78+
.getBeanProvider(SecurityContextHolderStrategy.class)
79+
.getIfUnique(SecurityContextHolder::getContextHolderStrategy);
80+
manager.setSecurityContextHolderStrategy(securityContextHolderStrategy);
7581
if (this.observationRegistry.isNoop()) {
7682
return manager;
7783
}

core/src/main/java/org/springframework/security/authentication/AbstractAuthenticationToken.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.ArrayList;
2121
import java.util.Collection;
2222
import java.util.Collections;
23+
import java.util.HashSet;
24+
import java.util.function.Consumer;
2325

2426
import org.jspecify.annotations.Nullable;
2527

@@ -185,4 +187,36 @@ public String toString() {
185187
return sb.toString();
186188
}
187189

190+
protected abstract static class AbstractAuthenticationBuilder<A extends Authentication, B extends AbstractAuthenticationBuilder<A, B>>
191+
implements Builder<A, B> {
192+
193+
private final Collection<GrantedAuthority> authorities = new HashSet<>();
194+
195+
protected AbstractAuthenticationBuilder() {
196+
197+
}
198+
199+
@Override
200+
public B authorities(Consumer<Collection<GrantedAuthority>> authorities) {
201+
authorities.accept(this.authorities);
202+
return (B) this;
203+
}
204+
205+
@Override
206+
public A build() {
207+
return build(this.authorities);
208+
}
209+
210+
@Override
211+
public B apply(Authentication token) {
212+
Assert.isTrue(token.isAuthenticated(), "cannot mutate an unauthenticated token");
213+
Assert.notNull(token.getPrincipal(), "principal cannot be null");
214+
this.authorities.addAll(token.getAuthorities());
215+
return (B) this;
216+
}
217+
218+
protected abstract A build(Collection<GrantedAuthority> authorities);
219+
220+
}
221+
188222
}

core/src/main/java/org/springframework/security/authentication/ProviderManager.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import org.springframework.security.core.AuthenticationException;
3434
import org.springframework.security.core.CredentialsContainer;
3535
import org.springframework.security.core.SpringSecurityMessageSource;
36+
import org.springframework.security.core.context.SecurityContextHolder;
37+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3638
import org.springframework.util.Assert;
3739
import org.springframework.util.CollectionUtils;
3840

@@ -92,6 +94,9 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
9294

9395
private static final Log logger = LogFactory.getLog(ProviderManager.class);
9496

97+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
98+
.getContextHolderStrategy();
99+
95100
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
96101

97102
private List<AuthenticationProvider> providers = Collections.emptyList();
@@ -209,6 +214,7 @@ public Authentication authenticate(Authentication authentication) throws Authent
209214
lastException = ex;
210215
}
211216
}
217+
result = applyPreviousAuthentication(result);
212218
if (result == null && this.parent != null) {
213219
// Allow the parent to try.
214220
try {
@@ -265,6 +271,20 @@ public Authentication authenticate(Authentication authentication) throws Authent
265271
throw lastException;
266272
}
267273

274+
private @Nullable Authentication applyPreviousAuthentication(@Nullable Authentication result) {
275+
if (result == null) {
276+
return null;
277+
}
278+
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
279+
if (current == null) {
280+
return result;
281+
}
282+
if (!current.isAuthenticated()) {
283+
return result;
284+
}
285+
return result.toBuilder().apply(current).build();
286+
}
287+
268288
@SuppressWarnings("deprecation")
269289
private void prepareException(AuthenticationException ex, Authentication auth) {
270290
ex.setAuthenticationRequest(auth);
@@ -287,6 +307,11 @@ public List<AuthenticationProvider> getProviders() {
287307
return this.providers;
288308
}
289309

310+
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
311+
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
312+
this.securityContextHolderStrategy = securityContextHolderStrategy;
313+
}
314+
290315
@Override
291316
public void setMessageSource(MessageSource messageSource) {
292317
this.messages = new MessageSourceAccessor(messageSource);

0 commit comments

Comments
 (0)