Skip to content

Commit 4f62b6b

Browse files
committed
Apply Existing Authorities
Signed-off-by: Josh Cummings <[email protected]>
1 parent 36f1de9 commit 4f62b6b

File tree

38 files changed

+1167
-4
lines changed

38 files changed

+1167
-4
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
@@ -116,6 +116,9 @@ HttpSecurity httpSecurity() throws Exception {
116116
LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
117117
AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
118118
this.objectPostProcessor, passwordEncoder);
119+
authenticationBuilder.setSharedObject(SecurityContextHolderStrategy.class,
120+
this.context.getBeanProvider(SecurityContextHolderStrategy.class)
121+
.getIfUnique(SecurityContextHolder::getContextHolderStrategy));
119122
authenticationBuilder.parentAuthenticationManager(authenticationManager());
120123
authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
121124
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/DelegatingReactiveAuthenticationManager.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import org.springframework.security.core.Authentication;
2929
import org.springframework.security.core.AuthenticationException;
30+
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
3031
import org.springframework.util.Assert;
3132

3233
/**
@@ -57,11 +58,24 @@ public DelegatingReactiveAuthenticationManager(List<ReactiveAuthenticationManage
5758

5859
@Override
5960
public Mono<Authentication> authenticate(Authentication authentication) {
61+
return ReactiveSecurityContextHolder.getContext().flatMap((context) -> {
62+
Mono<Authentication> result = doAuthenticate(authentication);
63+
Authentication current = context.getAuthentication();
64+
if (current == null) {
65+
return result;
66+
}
67+
if (!current.isAuthenticated()) {
68+
return result;
69+
}
70+
return doAuthenticate(current).map((r) -> r.toBuilder().apply(current).build());
71+
}).switchIfEmpty(doAuthenticate(authentication));
72+
}
73+
74+
private Mono<Authentication> doAuthenticate(Authentication authentication) {
6075
Flux<ReactiveAuthenticationManager> result = Flux.fromIterable(this.delegates);
6176
Function<ReactiveAuthenticationManager, Mono<Authentication>> logging = (m) -> m.authenticate(authentication)
6277
.doOnError(AuthenticationException.class, (ex) -> ex.setAuthenticationRequest(authentication))
6378
.doOnError(this.logger::debug);
64-
6579
return ((this.continueOnError) ? result.concatMapDelayError(logging) : result.concatMap(logging)).next();
6680
}
6781

0 commit comments

Comments
 (0)