Skip to content

Commit 652c35d

Browse files
committed
Add SecurityContextHolderStrategy XML Configuration for OAuth2
Issue gh-11061
1 parent 1d22316 commit 652c35d

10 files changed

+195
-23
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java

+1
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ public void init(B http) throws Exception {
293293
OAuth2LoginAuthenticationFilter authenticationFilter = new OAuth2LoginAuthenticationFilter(
294294
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
295295
OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder()), this.loginProcessingUrl);
296+
authenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
296297
this.setAuthenticationFilter(authenticationFilter);
297298
super.loginProcessingUrl(this.loginProcessingUrl);
298299
if (this.loginPage != null) {

config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java

+20-11
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,12 @@ final class AuthenticationConfigBuilder {
251251
createAnonymousFilter(authenticationFilterSecurityContextHolderStrategyRef);
252252
createRememberMeFilter(authenticationManager);
253253
createBasicFilter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
254-
createBearerTokenAuthenticationFilter(authenticationManager);
254+
createBearerTokenAuthenticationFilter(authenticationManager,
255+
authenticationFilterSecurityContextHolderStrategyRef);
255256
createFormLoginFilter(sessionStrategy, authenticationManager,
256257
authenticationFilterSecurityContextHolderStrategyRef, authenticationFilterSecurityContextRepositoryRef);
257258
createOAuth2ClientFilters(sessionStrategy, requestCache, authenticationManager,
258-
authenticationFilterSecurityContextRepositoryRef);
259+
authenticationFilterSecurityContextRepositoryRef, authenticationFilterSecurityContextHolderStrategyRef);
259260
createOpenIDLoginFilter(sessionStrategy, authenticationManager,
260261
authenticationFilterSecurityContextRepositoryRef);
261262
createSaml2LoginFilter(authenticationManager, authenticationFilterSecurityContextRepositoryRef);
@@ -326,22 +327,26 @@ void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authMana
326327
}
327328

328329
void createOAuth2ClientFilters(BeanReference sessionStrategy, BeanReference requestCache,
329-
BeanReference authenticationManager, BeanReference authenticationFilterSecurityContextRepositoryRef) {
330+
BeanReference authenticationManager, BeanReference authenticationFilterSecurityContextRepositoryRef,
331+
BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
330332
createOAuth2LoginFilter(sessionStrategy, authenticationManager,
331-
authenticationFilterSecurityContextRepositoryRef);
332-
createOAuth2ClientFilter(requestCache, authenticationManager, authenticationFilterSecurityContextRepositoryRef);
333+
authenticationFilterSecurityContextRepositoryRef, authenticationFilterSecurityContextHolderStrategy);
334+
createOAuth2ClientFilter(requestCache, authenticationManager, authenticationFilterSecurityContextRepositoryRef,
335+
authenticationFilterSecurityContextHolderStrategy);
333336
registerOAuth2ClientPostProcessors();
334337
}
335338

336339
void createOAuth2LoginFilter(BeanReference sessionStrategy, BeanReference authManager,
337-
BeanReference authenticationFilterSecurityContextRepositoryRef) {
340+
BeanReference authenticationFilterSecurityContextRepositoryRef,
341+
BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
338342
Element oauth2LoginElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OAUTH2_LOGIN);
339343
if (oauth2LoginElt == null) {
340344
return;
341345
}
342346
this.oauth2LoginEnabled = true;
343347
OAuth2LoginBeanDefinitionParser parser = new OAuth2LoginBeanDefinitionParser(this.requestCache, this.portMapper,
344-
this.portResolver, sessionStrategy, this.allowSessionCreation);
348+
this.portResolver, sessionStrategy, this.allowSessionCreation,
349+
authenticationFilterSecurityContextHolderStrategy);
345350
BeanDefinition oauth2LoginFilterBean = parser.parse(oauth2LoginElt, this.pc);
346351
BeanDefinition defaultAuthorizedClientRepository = parser.getDefaultAuthorizedClientRepository();
347352
registerDefaultAuthorizedClientRepositoryIfNecessary(defaultAuthorizedClientRepository);
@@ -380,14 +385,16 @@ void createOAuth2LoginFilter(BeanReference sessionStrategy, BeanReference authMa
380385
}
381386

382387
void createOAuth2ClientFilter(BeanReference requestCache, BeanReference authenticationManager,
383-
BeanReference authenticationFilterSecurityContextRepositoryRef) {
388+
BeanReference authenticationFilterSecurityContextRepositoryRef,
389+
BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
384390
Element oauth2ClientElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OAUTH2_CLIENT);
385391
if (oauth2ClientElt == null) {
386392
return;
387393
}
388394
this.oauth2ClientEnabled = true;
389395
OAuth2ClientBeanDefinitionParser parser = new OAuth2ClientBeanDefinitionParser(requestCache,
390-
authenticationManager, authenticationFilterSecurityContextRepositoryRef);
396+
authenticationManager, authenticationFilterSecurityContextRepositoryRef,
397+
authenticationFilterSecurityContextHolderStrategy);
391398
parser.parse(oauth2ClientElt, this.pc);
392399
BeanDefinition defaultAuthorizedClientRepository = parser.getDefaultAuthorizedClientRepository();
393400
registerDefaultAuthorizedClientRepositoryIfNecessary(defaultAuthorizedClientRepository);
@@ -603,15 +610,17 @@ void createBasicFilter(BeanReference authManager,
603610
this.basicFilter = filterBuilder.getBeanDefinition();
604611
}
605612

606-
void createBearerTokenAuthenticationFilter(BeanReference authManager) {
613+
void createBearerTokenAuthenticationFilter(BeanReference authManager,
614+
BeanMetadataElement authenticationFilterSecurityContextHolderStrategyRef) {
607615
Element resourceServerElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OAUTH2_RESOURCE_SERVER);
608616
if (resourceServerElt == null) {
609617
// No resource server, do nothing
610618
return;
611619
}
612620
OAuth2ResourceServerBeanDefinitionParser resourceServerBuilder = new OAuth2ResourceServerBeanDefinitionParser(
613621
authManager, this.authenticationProviders, this.defaultEntryPointMappings,
614-
this.defaultDeniedHandlerMappings, this.csrfIgnoreRequestMatchers);
622+
this.defaultDeniedHandlerMappings, this.csrfIgnoreRequestMatchers,
623+
authenticationFilterSecurityContextHolderStrategyRef);
615624
this.bearerTokenAuthenticationFilter = resourceServerBuilder.parse(resourceServerElt, this.pc);
616625
}
617626

config/src/main/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParser.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -52,6 +52,8 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
5252

5353
private final BeanReference authenticationFilterSecurityContextRepositoryRef;
5454

55+
private final BeanMetadataElement authenticationFilterSecurityContextHolderStrategy;
56+
5557
private BeanDefinition defaultAuthorizedClientRepository;
5658

5759
private BeanDefinition authorizationRequestRedirectFilter;
@@ -61,10 +63,12 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
6163
private BeanDefinition authorizationCodeAuthenticationProvider;
6264

6365
OAuth2ClientBeanDefinitionParser(BeanReference requestCache, BeanReference authenticationManager,
64-
BeanReference authenticationFilterSecurityContextRepositoryRef) {
66+
BeanReference authenticationFilterSecurityContextRepositoryRef,
67+
BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
6568
this.requestCache = requestCache;
6669
this.authenticationManager = authenticationManager;
6770
this.authenticationFilterSecurityContextRepositoryRef = authenticationFilterSecurityContextRepositoryRef;
71+
this.authenticationFilterSecurityContextHolderStrategy = authenticationFilterSecurityContextHolderStrategy;
6872
}
6973

7074
@Override

config/src/main/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParser.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -115,6 +115,8 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
115115

116116
private final boolean allowSessionCreation;
117117

118+
private final BeanMetadataElement authenticationFilterSecurityContextHolderStrategy;
119+
118120
private BeanDefinition defaultAuthorizedClientRepository;
119121

120122
private BeanDefinition oauth2AuthorizationRequestRedirectFilter;
@@ -128,12 +130,14 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
128130
private BeanDefinition oauth2LoginLinks;
129131

130132
OAuth2LoginBeanDefinitionParser(BeanReference requestCache, BeanReference portMapper, BeanReference portResolver,
131-
BeanReference sessionStrategy, boolean allowSessionCreation) {
133+
BeanReference sessionStrategy, boolean allowSessionCreation,
134+
BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
132135
this.requestCache = requestCache;
133136
this.portMapper = portMapper;
134137
this.portResolver = portResolver;
135138
this.sessionStrategy = sessionStrategy;
136139
this.allowSessionCreation = allowSessionCreation;
140+
this.authenticationFilterSecurityContextHolderStrategy = authenticationFilterSecurityContextHolderStrategy;
137141
}
138142

139143
@Override
@@ -245,6 +249,8 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
245249
oauth2LoginAuthenticationFilterBuilder.addPropertyValue("authenticationFailureHandler",
246250
failureHandlerBuilder.getBeanDefinition());
247251
}
252+
oauth2LoginAuthenticationFilterBuilder.addPropertyValue("securityContextHolderStrategy",
253+
this.authenticationFilterSecurityContextHolderStrategy);
248254
// prepare loginlinks
249255
this.oauth2LoginLinks = BeanDefinitionBuilder.rootBeanDefinition(Map.class)
250256
.setFactoryMethodOnBean("getLoginLinks", oauth2LoginBeanConfigId).getBeanDefinition();

config/src/main/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParser.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -86,14 +86,18 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
8686

8787
private final BeanDefinition accessDeniedHandler = new RootBeanDefinition(BearerTokenAccessDeniedHandler.class);
8888

89+
private final BeanMetadataElement authenticationFilterSecurityContextHolderStrategy;
90+
8991
OAuth2ResourceServerBeanDefinitionParser(BeanReference authenticationManager,
9092
List<BeanReference> authenticationProviders, Map<BeanDefinition, BeanMetadataElement> entryPoints,
91-
Map<BeanDefinition, BeanMetadataElement> deniedHandlers, List<BeanDefinition> ignoreCsrfRequestMatchers) {
93+
Map<BeanDefinition, BeanMetadataElement> deniedHandlers, List<BeanDefinition> ignoreCsrfRequestMatchers,
94+
BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
9295
this.authenticationManager = authenticationManager;
9396
this.authenticationProviders = authenticationProviders;
9497
this.entryPoints = entryPoints;
9598
this.deniedHandlers = deniedHandlers;
9699
this.ignoreCsrfRequestMatchers = ignoreCsrfRequestMatchers;
100+
this.authenticationFilterSecurityContextHolderStrategy = authenticationFilterSecurityContextHolderStrategy;
97101
}
98102

99103
/**
@@ -135,6 +139,8 @@ public BeanDefinition parse(Element oauth2ResourceServer, ParserContext pc) {
135139
filterBuilder.addConstructorArgValue(authenticationManagerResolver);
136140
filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
137141
filterBuilder.addPropertyValue(AUTHENTICATION_ENTRY_POINT, authenticationEntryPoint);
142+
filterBuilder.addPropertyValue("securityContextHolderStrategy",
143+
this.authenticationFilterSecurityContextHolderStrategy);
138144
return filterBuilder.getBeanDefinition();
139145
}
140146

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ public void getWhenUsingDefaultsWithValidBearerTokenThenAcceptsRequest() throws
224224

225225
@Test
226226
public void getWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
227-
this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class, SecurityContextChangedListenerConfig.class).autowire();
227+
this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class,
228+
SecurityContextChangedListenerConfig.class).autowire();
228229
mockRestOperations(jwks("Default"));
229230
String token = this.token("ValidNoScopes");
230231
// @formatter:off

config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@
3535
import org.springframework.security.core.authority.AuthorityUtils;
3636
import org.springframework.security.core.authority.SimpleGrantedAuthority;
3737
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
38+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3839
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
3940
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
4041
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
@@ -78,6 +79,7 @@
7879
import static org.assertj.core.api.Assertions.assertThat;
7980
import static org.mockito.ArgumentMatchers.any;
8081
import static org.mockito.BDDMockito.given;
82+
import static org.mockito.Mockito.atLeastOnce;
8183
import static org.mockito.Mockito.times;
8284
import static org.mockito.Mockito.verify;
8385
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -137,6 +139,9 @@ public class OAuth2LoginBeanDefinitionParserTests {
137139
@Autowired(required = false)
138140
private RequestCache requestCache;
139141

142+
@Autowired(required = false)
143+
private SecurityContextHolderStrategy securityContextHolderStrategy;
144+
140145
@Autowired
141146
private MockMvc mvc;
142147

@@ -472,6 +477,28 @@ public void requestWhenCustomAuthorizedClientServiceThenCalled() throws Exceptio
472477
verify(this.authorizedClientService).saveAuthorizedClient(any(), any());
473478
}
474479

480+
@Test
481+
public void requestWhenCustomSecurityContextHolderStrategyThenCalled() throws Exception {
482+
this.spring.configLocations(this.xml("WithCustomSecurityContextHolderStrategy")).autowire();
483+
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
484+
given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(clientRegistration);
485+
Map<String, Object> attributes = new HashMap<>();
486+
attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
487+
OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request()
488+
.attributes(attributes).build();
489+
given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any()))
490+
.willReturn(authorizationRequest);
491+
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
492+
given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse);
493+
OAuth2User oauth2User = TestOAuth2Users.create();
494+
given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User);
495+
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
496+
params.add("code", "code123");
497+
params.add("state", authorizationRequest.getState());
498+
this.mvc.perform(get("/login/oauth2/code/" + clientRegistration.getRegistrationId()).params(params));
499+
verify(this.securityContextHolderStrategy, atLeastOnce()).getContext();
500+
}
501+
475502
@WithMockUser
476503
@Test
477504
public void requestWhenAuthorizedClientFoundThenMethodArgumentResolved() throws Exception {

config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -51,6 +51,7 @@
5151
import org.mockito.Mockito;
5252
import org.w3c.dom.Element;
5353

54+
import org.springframework.beans.BeanMetadataElement;
5455
import org.springframework.beans.factory.DisposableBean;
5556
import org.springframework.beans.factory.FactoryBean;
5657
import org.springframework.beans.factory.annotation.Autowired;
@@ -73,6 +74,7 @@
7374
import org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.OpaqueTokenBeanDefinitionParser;
7475
import org.springframework.security.config.test.SpringTestContext;
7576
import org.springframework.security.config.test.SpringTestContextExtension;
77+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
7678
import org.springframework.security.oauth2.core.OAuth2Error;
7779
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
7880
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
@@ -105,6 +107,7 @@
105107
import static org.mockito.ArgumentMatchers.anyString;
106108
import static org.mockito.ArgumentMatchers.eq;
107109
import static org.mockito.BDDMockito.given;
110+
import static org.mockito.Mockito.atLeastOnce;
108111
import static org.mockito.Mockito.mock;
109112
import static org.mockito.Mockito.reset;
110113
import static org.mockito.Mockito.times;
@@ -144,6 +147,20 @@ public void getWhenValidBearerTokenThenAcceptsRequest() throws Exception {
144147
// @formatter:on
145148
}
146149

150+
@Test
151+
public void getWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
152+
this.spring.configLocations(xml("JwtRestOperations"), xml("JwtCustomSecurityContextHolderStrategy")).autowire();
153+
mockRestOperations(jwks("Default"));
154+
String token = this.token("ValidNoScopes");
155+
// @formatter:off
156+
this.mvc.perform(get("/").header("Authorization", "Bearer " + token))
157+
.andExpect(status().isNotFound());
158+
// @formatter:on
159+
SecurityContextHolderStrategy securityContextHolderStrategy = this.spring.getContext()
160+
.getBean(SecurityContextHolderStrategy.class);
161+
verify(securityContextHolderStrategy, atLeastOnce()).getContext();
162+
}
163+
147164
@Test
148165
public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception {
149166
this.spring.configLocations(xml("WebServer"), xml("JwkSetUri")).autowire();
@@ -505,7 +522,8 @@ public void requestWhenBearerTokenResolverAllowsQueryParameterAndRequestContains
505522
@Test
506523
public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed() {
507524
OAuth2ResourceServerBeanDefinitionParser oauth2 = new OAuth2ResourceServerBeanDefinitionParser(
508-
mock(BeanReference.class), mock(List.class), mock(Map.class), mock(Map.class), mock(List.class));
525+
mock(BeanReference.class), mock(List.class), mock(Map.class), mock(Map.class), mock(List.class),
526+
mock(BeanMetadataElement.class));
509527
assertThat(oauth2.getBearerTokenResolver(mock(Element.class))).isInstanceOf(RootBeanDefinition.class);
510528
}
511529

@@ -800,7 +818,7 @@ public void configureWhenUsingBothAuthenticationManagerResolverAndJwtThenExcepti
800818
@Test
801819
public void validateConfigurationWhenMoreThanOneResourceServerModeThenError() {
802820
OAuth2ResourceServerBeanDefinitionParser parser = new OAuth2ResourceServerBeanDefinitionParser(null, null, null,
803-
null, null);
821+
null, null, null);
804822
Element element = mock(Element.class);
805823
given(element.hasAttribute(OAuth2ResourceServerBeanDefinitionParser.AUTHENTICATION_MANAGER_RESOLVER_REF))
806824
.willReturn(true);
@@ -816,7 +834,7 @@ public void validateConfigurationWhenMoreThanOneResourceServerModeThenError() {
816834
@Test
817835
public void validateConfigurationWhenNoResourceServerModeThenError() {
818836
OAuth2ResourceServerBeanDefinitionParser parser = new OAuth2ResourceServerBeanDefinitionParser(null, null, null,
819-
null, null);
837+
null, null, null);
820838
Element element = mock(Element.class);
821839
given(element.hasAttribute(OAuth2ResourceServerBeanDefinitionParser.AUTHENTICATION_MANAGER_RESOLVER_REF))
822840
.willReturn(false);

0 commit comments

Comments
 (0)