Add SecurityContextHolderStrategy XML Configuration for OAuth2

Issue gh-11061
This commit is contained in:
Josh Cummings 2022-06-24 13:24:24 -06:00
parent 7543effe89
commit 2079309c5a
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
10 changed files with 194 additions and 23 deletions

View File

@ -290,6 +290,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) {

View File

@ -230,11 +230,12 @@ final class AuthenticationConfigBuilder {
createAnonymousFilter(authenticationFilterSecurityContextHolderStrategyRef);
createRememberMeFilter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
createBasicFilter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
createBearerTokenAuthenticationFilter(authenticationManager);
createBearerTokenAuthenticationFilter(authenticationManager,
authenticationFilterSecurityContextHolderStrategyRef);
createFormLoginFilter(sessionStrategy, authenticationManager,
authenticationFilterSecurityContextHolderStrategyRef, authenticationFilterSecurityContextRepositoryRef);
createOAuth2ClientFilters(sessionStrategy, requestCache, authenticationManager,
authenticationFilterSecurityContextRepositoryRef);
authenticationFilterSecurityContextRepositoryRef, authenticationFilterSecurityContextHolderStrategyRef);
createSaml2LoginFilter(authenticationManager, authenticationFilterSecurityContextRepositoryRef);
createX509Filter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
createJeeFilter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
@ -304,22 +305,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);
@ -358,14 +363,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);
@ -476,7 +483,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
@ -484,7 +492,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);
}

View File

@ -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.
@ -54,6 +54,8 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
private final BeanReference authenticationFilterSecurityContextRepositoryRef;
private final BeanMetadataElement authenticationFilterSecurityContextHolderStrategy;
private BeanDefinition defaultAuthorizedClientRepository;
private BeanDefinition authorizationRequestRedirectFilter;
@ -63,10 +65,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

View File

@ -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.
@ -117,6 +117,8 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
private final boolean allowSessionCreation;
private final BeanMetadataElement authenticationFilterSecurityContextHolderStrategy;
private BeanDefinition defaultAuthorizedClientRepository;
private BeanDefinition oauth2AuthorizationRequestRedirectFilter;
@ -130,12 +132,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
@ -248,6 +252,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();

View File

@ -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.
@ -85,14 +85,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;
}
/**
@ -134,6 +138,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();
}

View File

@ -225,7 +225,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

View File

@ -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;
@ -80,6 +81,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
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;
@ -142,6 +144,9 @@ public class OAuth2LoginBeanDefinitionParserTests {
@Autowired(required = false)
private RequestCache requestCache;
@Autowired(required = false)
private SecurityContextHolderStrategy securityContextHolderStrategy;
@Autowired
private MockMvc mvc;
@ -488,6 +493,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 {

View File

@ -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.
@ -50,6 +50,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;
@ -107,6 +108,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;
@ -146,6 +148,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();
@ -507,7 +523,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);
}
@ -816,7 +833,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);
@ -832,7 +849,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);

View File

@ -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>

View File

@ -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>