Răsfoiți Sursa

Add SecurityContextHolderStrategy XML Configuration for OAuth2

Issue gh-11061
Josh Cummings 3 ani în urmă
părinte
comite
652c35db2f

+ 1 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java

@@ -293,6 +293,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 		OAuth2LoginAuthenticationFilter authenticationFilter = new OAuth2LoginAuthenticationFilter(
 				OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
 				OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder()), this.loginProcessingUrl);
+		authenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
 		this.setAuthenticationFilter(authenticationFilter);
 		super.loginProcessingUrl(this.loginProcessingUrl);
 		if (this.loginPage != null) {

+ 20 - 11
config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java

@@ -251,11 +251,12 @@ final class AuthenticationConfigBuilder {
 		createAnonymousFilter(authenticationFilterSecurityContextHolderStrategyRef);
 		createRememberMeFilter(authenticationManager);
 		createBasicFilter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
-		createBearerTokenAuthenticationFilter(authenticationManager);
+		createBearerTokenAuthenticationFilter(authenticationManager,
+				authenticationFilterSecurityContextHolderStrategyRef);
 		createFormLoginFilter(sessionStrategy, authenticationManager,
 				authenticationFilterSecurityContextHolderStrategyRef, authenticationFilterSecurityContextRepositoryRef);
 		createOAuth2ClientFilters(sessionStrategy, requestCache, authenticationManager,
-				authenticationFilterSecurityContextRepositoryRef);
+				authenticationFilterSecurityContextRepositoryRef, authenticationFilterSecurityContextHolderStrategyRef);
 		createOpenIDLoginFilter(sessionStrategy, authenticationManager,
 				authenticationFilterSecurityContextRepositoryRef);
 		createSaml2LoginFilter(authenticationManager, authenticationFilterSecurityContextRepositoryRef);
@@ -326,22 +327,26 @@ final class AuthenticationConfigBuilder {
 	}
 
 	void createOAuth2ClientFilters(BeanReference sessionStrategy, BeanReference requestCache,
-			BeanReference authenticationManager, BeanReference authenticationFilterSecurityContextRepositoryRef) {
+			BeanReference authenticationManager, BeanReference authenticationFilterSecurityContextRepositoryRef,
+			BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
 		createOAuth2LoginFilter(sessionStrategy, authenticationManager,
-				authenticationFilterSecurityContextRepositoryRef);
-		createOAuth2ClientFilter(requestCache, authenticationManager, authenticationFilterSecurityContextRepositoryRef);
+				authenticationFilterSecurityContextRepositoryRef, authenticationFilterSecurityContextHolderStrategy);
+		createOAuth2ClientFilter(requestCache, authenticationManager, authenticationFilterSecurityContextRepositoryRef,
+				authenticationFilterSecurityContextHolderStrategy);
 		registerOAuth2ClientPostProcessors();
 	}
 
 	void createOAuth2LoginFilter(BeanReference sessionStrategy, BeanReference authManager,
-			BeanReference authenticationFilterSecurityContextRepositoryRef) {
+			BeanReference authenticationFilterSecurityContextRepositoryRef,
+			BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
 		Element oauth2LoginElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OAUTH2_LOGIN);
 		if (oauth2LoginElt == null) {
 			return;
 		}
 		this.oauth2LoginEnabled = true;
 		OAuth2LoginBeanDefinitionParser parser = new OAuth2LoginBeanDefinitionParser(this.requestCache, this.portMapper,
-				this.portResolver, sessionStrategy, this.allowSessionCreation);
+				this.portResolver, sessionStrategy, this.allowSessionCreation,
+				authenticationFilterSecurityContextHolderStrategy);
 		BeanDefinition oauth2LoginFilterBean = parser.parse(oauth2LoginElt, this.pc);
 		BeanDefinition defaultAuthorizedClientRepository = parser.getDefaultAuthorizedClientRepository();
 		registerDefaultAuthorizedClientRepositoryIfNecessary(defaultAuthorizedClientRepository);
@@ -380,14 +385,16 @@ final class AuthenticationConfigBuilder {
 	}
 
 	void createOAuth2ClientFilter(BeanReference requestCache, BeanReference authenticationManager,
-			BeanReference authenticationFilterSecurityContextRepositoryRef) {
+			BeanReference authenticationFilterSecurityContextRepositoryRef,
+			BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
 		Element oauth2ClientElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OAUTH2_CLIENT);
 		if (oauth2ClientElt == null) {
 			return;
 		}
 		this.oauth2ClientEnabled = true;
 		OAuth2ClientBeanDefinitionParser parser = new OAuth2ClientBeanDefinitionParser(requestCache,
-				authenticationManager, authenticationFilterSecurityContextRepositoryRef);
+				authenticationManager, authenticationFilterSecurityContextRepositoryRef,
+				authenticationFilterSecurityContextHolderStrategy);
 		parser.parse(oauth2ClientElt, this.pc);
 		BeanDefinition defaultAuthorizedClientRepository = parser.getDefaultAuthorizedClientRepository();
 		registerDefaultAuthorizedClientRepositoryIfNecessary(defaultAuthorizedClientRepository);
@@ -603,7 +610,8 @@ final class AuthenticationConfigBuilder {
 		this.basicFilter = filterBuilder.getBeanDefinition();
 	}
 
-	void createBearerTokenAuthenticationFilter(BeanReference authManager) {
+	void createBearerTokenAuthenticationFilter(BeanReference authManager,
+			BeanMetadataElement authenticationFilterSecurityContextHolderStrategyRef) {
 		Element resourceServerElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OAUTH2_RESOURCE_SERVER);
 		if (resourceServerElt == null) {
 			// No resource server, do nothing
@@ -611,7 +619,8 @@ final class AuthenticationConfigBuilder {
 		}
 		OAuth2ResourceServerBeanDefinitionParser resourceServerBuilder = new OAuth2ResourceServerBeanDefinitionParser(
 				authManager, this.authenticationProviders, this.defaultEntryPointMappings,
-				this.defaultDeniedHandlerMappings, this.csrfIgnoreRequestMatchers);
+				this.defaultDeniedHandlerMappings, this.csrfIgnoreRequestMatchers,
+				authenticationFilterSecurityContextHolderStrategyRef);
 		this.bearerTokenAuthenticationFilter = resourceServerBuilder.parse(resourceServerElt, this.pc);
 	}
 

+ 6 - 2
config/src/main/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParser.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -52,6 +52,8 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
 
 	private final BeanReference authenticationFilterSecurityContextRepositoryRef;
 
+	private final BeanMetadataElement authenticationFilterSecurityContextHolderStrategy;
+
 	private BeanDefinition defaultAuthorizedClientRepository;
 
 	private BeanDefinition authorizationRequestRedirectFilter;
@@ -61,10 +63,12 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
 	private BeanDefinition authorizationCodeAuthenticationProvider;
 
 	OAuth2ClientBeanDefinitionParser(BeanReference requestCache, BeanReference authenticationManager,
-			BeanReference authenticationFilterSecurityContextRepositoryRef) {
+			BeanReference authenticationFilterSecurityContextRepositoryRef,
+			BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
 		this.requestCache = requestCache;
 		this.authenticationManager = authenticationManager;
 		this.authenticationFilterSecurityContextRepositoryRef = authenticationFilterSecurityContextRepositoryRef;
+		this.authenticationFilterSecurityContextHolderStrategy = authenticationFilterSecurityContextHolderStrategy;
 	}
 
 	@Override

+ 8 - 2
config/src/main/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParser.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -115,6 +115,8 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
 
 	private final boolean allowSessionCreation;
 
+	private final BeanMetadataElement authenticationFilterSecurityContextHolderStrategy;
+
 	private BeanDefinition defaultAuthorizedClientRepository;
 
 	private BeanDefinition oauth2AuthorizationRequestRedirectFilter;
@@ -128,12 +130,14 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
 	private BeanDefinition oauth2LoginLinks;
 
 	OAuth2LoginBeanDefinitionParser(BeanReference requestCache, BeanReference portMapper, BeanReference portResolver,
-			BeanReference sessionStrategy, boolean allowSessionCreation) {
+			BeanReference sessionStrategy, boolean allowSessionCreation,
+			BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
 		this.requestCache = requestCache;
 		this.portMapper = portMapper;
 		this.portResolver = portResolver;
 		this.sessionStrategy = sessionStrategy;
 		this.allowSessionCreation = allowSessionCreation;
+		this.authenticationFilterSecurityContextHolderStrategy = authenticationFilterSecurityContextHolderStrategy;
 	}
 
 	@Override
@@ -245,6 +249,8 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
 			oauth2LoginAuthenticationFilterBuilder.addPropertyValue("authenticationFailureHandler",
 					failureHandlerBuilder.getBeanDefinition());
 		}
+		oauth2LoginAuthenticationFilterBuilder.addPropertyValue("securityContextHolderStrategy",
+				this.authenticationFilterSecurityContextHolderStrategy);
 		// prepare loginlinks
 		this.oauth2LoginLinks = BeanDefinitionBuilder.rootBeanDefinition(Map.class)
 				.setFactoryMethodOnBean("getLoginLinks", oauth2LoginBeanConfigId).getBeanDefinition();

+ 8 - 2
config/src/main/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParser.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -86,14 +86,18 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
 
 	private final BeanDefinition accessDeniedHandler = new RootBeanDefinition(BearerTokenAccessDeniedHandler.class);
 
+	private final BeanMetadataElement authenticationFilterSecurityContextHolderStrategy;
+
 	OAuth2ResourceServerBeanDefinitionParser(BeanReference authenticationManager,
 			List<BeanReference> authenticationProviders, Map<BeanDefinition, BeanMetadataElement> entryPoints,
-			Map<BeanDefinition, BeanMetadataElement> deniedHandlers, List<BeanDefinition> ignoreCsrfRequestMatchers) {
+			Map<BeanDefinition, BeanMetadataElement> deniedHandlers, List<BeanDefinition> ignoreCsrfRequestMatchers,
+			BeanMetadataElement authenticationFilterSecurityContextHolderStrategy) {
 		this.authenticationManager = authenticationManager;
 		this.authenticationProviders = authenticationProviders;
 		this.entryPoints = entryPoints;
 		this.deniedHandlers = deniedHandlers;
 		this.ignoreCsrfRequestMatchers = ignoreCsrfRequestMatchers;
+		this.authenticationFilterSecurityContextHolderStrategy = authenticationFilterSecurityContextHolderStrategy;
 	}
 
 	/**
@@ -135,6 +139,8 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
 		filterBuilder.addConstructorArgValue(authenticationManagerResolver);
 		filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
 		filterBuilder.addPropertyValue(AUTHENTICATION_ENTRY_POINT, authenticationEntryPoint);
+		filterBuilder.addPropertyValue("securityContextHolderStrategy",
+				this.authenticationFilterSecurityContextHolderStrategy);
 		return filterBuilder.getBeanDefinition();
 	}
 

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

@@ -224,7 +224,8 @@ public class OAuth2ResourceServerConfigurerTests {
 
 	@Test
 	public void getWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
-		this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class, SecurityContextChangedListenerConfig.class).autowire();
+		this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class,
+				SecurityContextChangedListenerConfig.class).autowire();
 		mockRestOperations(jwks("Default"));
 		String token = this.token("ValidNoScopes");
 		// @formatter:off

+ 28 - 1
config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -35,6 +35,7 @@ import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
 import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
 import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
 import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
@@ -78,6 +79,7 @@ import org.springframework.web.bind.annotation.RestController;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -137,6 +139,9 @@ public class OAuth2LoginBeanDefinitionParserTests {
 	@Autowired(required = false)
 	private RequestCache requestCache;
 
+	@Autowired(required = false)
+	private SecurityContextHolderStrategy securityContextHolderStrategy;
+
 	@Autowired
 	private MockMvc mvc;
 
@@ -472,6 +477,28 @@ public class OAuth2LoginBeanDefinitionParserTests {
 		verify(this.authorizedClientService).saveAuthorizedClient(any(), any());
 	}
 
+	@Test
+	public void requestWhenCustomSecurityContextHolderStrategyThenCalled() throws Exception {
+		this.spring.configLocations(this.xml("WithCustomSecurityContextHolderStrategy")).autowire();
+		ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
+		given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(clientRegistration);
+		Map<String, Object> attributes = new HashMap<>();
+		attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
+		OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request()
+				.attributes(attributes).build();
+		given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any()))
+				.willReturn(authorizationRequest);
+		OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
+		given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse);
+		OAuth2User oauth2User = TestOAuth2Users.create();
+		given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User);
+		MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
+		params.add("code", "code123");
+		params.add("state", authorizationRequest.getState());
+		this.mvc.perform(get("/login/oauth2/code/" + clientRegistration.getRegistrationId()).params(params));
+		verify(this.securityContextHolderStrategy, atLeastOnce()).getContext();
+	}
+
 	@WithMockUser
 	@Test
 	public void requestWhenAuthorizedClientFoundThenMethodArgumentResolved() throws Exception {

+ 22 - 4
config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -51,6 +51,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mockito;
 import org.w3c.dom.Element;
 
+import org.springframework.beans.BeanMetadataElement;
 import org.springframework.beans.factory.DisposableBean;
 import org.springframework.beans.factory.FactoryBean;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -73,6 +74,7 @@ import org.springframework.security.config.http.OAuth2ResourceServerBeanDefiniti
 import org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.OpaqueTokenBeanDefinitionParser;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2TokenValidator;
 import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
@@ -105,6 +107,7 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
@@ -144,6 +147,20 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
 		// @formatter:on
 	}
 
+	@Test
+	public void getWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
+		this.spring.configLocations(xml("JwtRestOperations"), xml("JwtCustomSecurityContextHolderStrategy")).autowire();
+		mockRestOperations(jwks("Default"));
+		String token = this.token("ValidNoScopes");
+		// @formatter:off
+		this.mvc.perform(get("/").header("Authorization", "Bearer " + token))
+				.andExpect(status().isNotFound());
+		// @formatter:on
+		SecurityContextHolderStrategy securityContextHolderStrategy = this.spring.getContext()
+				.getBean(SecurityContextHolderStrategy.class);
+		verify(securityContextHolderStrategy, atLeastOnce()).getContext();
+	}
+
 	@Test
 	public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception {
 		this.spring.configLocations(xml("WebServer"), xml("JwkSetUri")).autowire();
@@ -505,7 +522,8 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
 	@Test
 	public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed() {
 		OAuth2ResourceServerBeanDefinitionParser oauth2 = new OAuth2ResourceServerBeanDefinitionParser(
-				mock(BeanReference.class), mock(List.class), mock(Map.class), mock(Map.class), mock(List.class));
+				mock(BeanReference.class), mock(List.class), mock(Map.class), mock(Map.class), mock(List.class),
+				mock(BeanMetadataElement.class));
 		assertThat(oauth2.getBearerTokenResolver(mock(Element.class))).isInstanceOf(RootBeanDefinition.class);
 	}
 
@@ -800,7 +818,7 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
 	@Test
 	public void validateConfigurationWhenMoreThanOneResourceServerModeThenError() {
 		OAuth2ResourceServerBeanDefinitionParser parser = new OAuth2ResourceServerBeanDefinitionParser(null, null, null,
-				null, null);
+				null, null, null);
 		Element element = mock(Element.class);
 		given(element.hasAttribute(OAuth2ResourceServerBeanDefinitionParser.AUTHENTICATION_MANAGER_RESOLVER_REF))
 				.willReturn(true);
@@ -816,7 +834,7 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
 	@Test
 	public void validateConfigurationWhenNoResourceServerModeThenError() {
 		OAuth2ResourceServerBeanDefinitionParser parser = new OAuth2ResourceServerBeanDefinitionParser(null, null, null,
-				null, null);
+				null, null, null);
 		Element element = mock(Element.class);
 		given(element.hasAttribute(OAuth2ResourceServerBeanDefinitionParser.AUTHENTICATION_MANAGER_RESOLVER_REF))
 				.willReturn(false);

+ 59 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests-WithCustomSecurityContextHolderStrategy.xml

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2022 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xmlns="http://www.springframework.org/schema/security"
+		xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			https://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http auto-config="true" security-context-holder-strategy-ref="ref">
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-login client-registration-repository-ref="clientRegistrationRepository"
+						access-token-response-client-ref="accessTokenResponseClient"
+						user-service-ref="oauth2UserService"
+						authorization-request-repository-ref="authorizationRequestRepository"
+						authorized-client-service-ref="authorizedClientService"/>
+	</http>
+
+	<b:bean id="ref" class="org.mockito.Mockito" factory-method="spy">
+		<b:constructor-arg>
+			<b:bean class="org.springframework.security.config.MockSecurityContextHolderStrategy"/>
+		</b:constructor-arg>
+	</b:bean>
+
+	<b:bean id="accessTokenResponseClient" class="org.mockito.Mockito" factory-method="mock">
+			<b:constructor-arg value="org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient"/>
+	</b:bean>
+	<b:bean id="oauth2UserService" class="org.mockito.Mockito" factory-method="mock">
+			<b:constructor-arg value="org.springframework.security.oauth2.client.userinfo.OAuth2UserService"/>
+	</b:bean>
+	<b:bean id="authorizationRequestRepository" class="org.mockito.Mockito" factory-method="mock">
+			<b:constructor-arg value="org.springframework.security.oauth2.client.web.AuthorizationRequestRepository"/>
+	</b:bean>
+	<b:bean id="clientRegistrationRepository" class="org.mockito.Mockito" factory-method="mock">
+			<b:constructor-arg value="org.springframework.security.oauth2.client.registration.ClientRegistrationRepository"/>
+	</b:bean>
+	<b:bean id="authorizedClientService" class="org.mockito.Mockito" factory-method="mock">
+			<b:constructor-arg value="org.springframework.security.oauth2.client.OAuth2AuthorizedClientService"/>
+	</b:bean>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 41 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests-JwtCustomSecurityContextHolderStrategy.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2022 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+
+
+	<http security-context-holder-strategy-ref="ref">
+		<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
+		<intercept-url pattern="/**" access="authenticated"/>
+		<oauth2-resource-server>
+			<jwt decoder-ref="decoder"/>
+		</oauth2-resource-server>
+	</http>
+
+	<b:bean id="ref" class="org.mockito.Mockito" factory-method="spy">
+		<b:constructor-arg>
+			<b:bean class="org.springframework.security.config.MockSecurityContextHolderStrategy"/>
+		</b:constructor-arg>
+	</b:bean>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>