Skip to content

Commit 1d22316

Browse files
committed
Add SecurityContextHolderStrategy Java Configuration for OAuth2
Issue gh-11061
1 parent 1d72a05 commit 1d22316

File tree

6 files changed

+82
-6
lines changed

6 files changed

+82
-6
lines changed

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

+15-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.
@@ -23,6 +23,7 @@
2323
import org.springframework.context.annotation.Import;
2424
import org.springframework.context.annotation.ImportSelector;
2525
import org.springframework.core.type.AnnotationMetadata;
26+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
2627
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
2728
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
2829
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
@@ -75,11 +76,18 @@ static class OAuth2ClientWebMvcSecurityConfiguration implements WebMvcConfigurer
7576

7677
private OAuth2AuthorizedClientManager authorizedClientManager;
7778

79+
private SecurityContextHolderStrategy securityContextHolderStrategy;
80+
7881
@Override
7982
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
8083
OAuth2AuthorizedClientManager authorizedClientManager = getAuthorizedClientManager();
8184
if (authorizedClientManager != null) {
82-
argumentResolvers.add(new OAuth2AuthorizedClientArgumentResolver(authorizedClientManager));
85+
OAuth2AuthorizedClientArgumentResolver resolver = new OAuth2AuthorizedClientArgumentResolver(
86+
authorizedClientManager);
87+
if (this.securityContextHolderStrategy != null) {
88+
resolver.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
89+
}
90+
argumentResolvers.add(resolver);
8391
}
8492
}
8593

@@ -110,6 +118,11 @@ void setAuthorizedClientManager(List<OAuth2AuthorizedClientManager> authorizedCl
110118
}
111119
}
112120

121+
@Autowired(required = false)
122+
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy strategy) {
123+
this.securityContextHolderStrategy = strategy;
124+
}
125+
113126
private OAuth2AuthorizedClientManager getAuthorizedClientManager() {
114127
if (this.authorizedClientManager != null) {
115128
return this.authorizedClientManager;

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

+2-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.
@@ -272,6 +272,7 @@ private OAuth2AuthorizationCodeGrantFilter createAuthorizationCodeGrantFilter(B
272272
if (this.authorizationRequestRepository != null) {
273273
authorizationCodeGrantFilter.setAuthorizationRequestRepository(this.authorizationRequestRepository);
274274
}
275+
authorizationCodeGrantFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
275276
RequestCache requestCache = builder.getSharedObject(RequestCache.class);
276277
if (requestCache != null) {
277278
authorizationCodeGrantFilter.setRequestCache(requestCache);

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java

+2-1
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.
@@ -270,6 +270,7 @@ public void configure(H http) {
270270
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
271271
filter.setBearerTokenResolver(bearerTokenResolver);
272272
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
273+
filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
273274
filter = postProcess(filter);
274275
http.addFilter(filter);
275276
}

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java

+25
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.mock.web.MockHttpServletRequest;
4343
import org.springframework.mock.web.MockHttpServletResponse;
4444
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
45+
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
4546
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4647
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
4748
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -53,6 +54,8 @@
5354
import org.springframework.security.core.authority.AuthorityUtils;
5455
import org.springframework.security.core.authority.SimpleGrantedAuthority;
5556
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
57+
import org.springframework.security.core.context.SecurityContextChangedListener;
58+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
5659
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
5760
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
5861
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
@@ -99,7 +102,10 @@
99102
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
100103
import static org.mockito.ArgumentMatchers.any;
101104
import static org.mockito.BDDMockito.given;
105+
import static org.mockito.Mockito.atLeastOnce;
102106
import static org.mockito.Mockito.mock;
107+
import static org.mockito.Mockito.verify;
108+
import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
103109
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
104110
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
105111
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -193,6 +199,25 @@ public void oauth2Login() throws Exception {
193199
.hasToString("ROLE_USER");
194200
}
195201

202+
@Test
203+
public void requestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
204+
loadConfig(OAuth2LoginConfig.class, SecurityContextChangedListenerConfig.class);
205+
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest();
206+
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response);
207+
this.request.setParameter("code", "code123");
208+
this.request.setParameter("state", authorizationRequest.getState());
209+
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
210+
Authentication authentication = this.securityContextRepository
211+
.loadContext(new HttpRequestResponseHolder(this.request, this.response)).getAuthentication();
212+
assertThat(authentication.getAuthorities()).hasSize(1);
213+
assertThat(authentication.getAuthorities()).first().isInstanceOf(OAuth2UserAuthority.class)
214+
.hasToString("ROLE_USER");
215+
SecurityContextHolderStrategy strategy = this.context.getBean(SecurityContextHolderStrategy.class);
216+
verify(strategy, atLeastOnce()).getContext();
217+
SecurityContextChangedListener listener = this.context.getBean(SecurityContextChangedListener.class);
218+
verify(listener).securityContextChanged(setAuthentication(OAuth2AuthenticationToken.class));
219+
}
220+
196221
@Test
197222
public void requestWhenOauth2LoginInLambdaThenAuthenticationContainsOauth2UserAuthority() throws Exception {
198223
loadConfig(OAuth2LoginInLambdaConfig.class);

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

+37-1
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.hamcrest.core.StringStartsWith;
5252
import org.junit.jupiter.api.Test;
5353
import org.junit.jupiter.api.extension.ExtendWith;
54+
import org.mockito.verification.VerificationMode;
5455

5556
import org.springframework.beans.factory.BeanCreationException;
5657
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
@@ -82,6 +83,7 @@
8283
import org.springframework.security.authentication.AuthenticationProvider;
8384
import org.springframework.security.authentication.AuthenticationServiceException;
8485
import org.springframework.security.config.annotation.ObjectPostProcessor;
86+
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
8587
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
8688
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
8789
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -93,6 +95,8 @@
9395
import org.springframework.security.core.Authentication;
9496
import org.springframework.security.core.GrantedAuthority;
9597
import org.springframework.security.core.authority.SimpleGrantedAuthority;
98+
import org.springframework.security.core.context.SecurityContextChangedListener;
99+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
96100
import org.springframework.security.core.userdetails.UserDetailsService;
97101
import org.springframework.security.oauth2.client.registration.ClientRegistration;
98102
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
@@ -152,6 +156,7 @@
152156
import static org.mockito.ArgumentMatchers.anyString;
153157
import static org.mockito.ArgumentMatchers.eq;
154158
import static org.mockito.BDDMockito.given;
159+
import static org.mockito.Mockito.atLeastOnce;
155160
import static org.mockito.Mockito.mock;
156161
import static org.mockito.Mockito.never;
157162
import static org.mockito.Mockito.verify;
@@ -217,6 +222,33 @@ public void getWhenUsingDefaultsWithValidBearerTokenThenAcceptsRequest() throws
217222
// @formatter:on
218223
}
219224

225+
@Test
226+
public void getWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
227+
this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class, SecurityContextChangedListenerConfig.class).autowire();
228+
mockRestOperations(jwks("Default"));
229+
String token = this.token("ValidNoScopes");
230+
// @formatter:off
231+
this.mvc.perform(get("/").with(bearerToken(token)))
232+
.andExpect(status().isOk())
233+
.andExpect(content().string("ok"));
234+
// @formatter:on
235+
verifyBean(SecurityContextHolderStrategy.class, atLeastOnce()).getContext();
236+
}
237+
238+
@Test
239+
public void getWhenSecurityContextHolderStrategyThenUses() throws Exception {
240+
this.spring.register(RestOperationsConfig.class, DefaultConfig.class,
241+
SecurityContextChangedListenerConfig.class, BasicController.class).autowire();
242+
mockRestOperations(jwks("Default"));
243+
String token = this.token("ValidNoScopes");
244+
// @formatter:off
245+
this.mvc.perform(get("/").with(bearerToken(token)))
246+
.andExpect(status().isOk())
247+
.andExpect(content().string("ok"));
248+
// @formatter:on
249+
verifyBean(SecurityContextChangedListener.class, atLeastOnce()).securityContextChanged(any());
250+
}
251+
220252
@Test
221253
public void getWhenUsingDefaultsInLambdaWithValidBearerTokenThenAcceptsRequest() throws Exception {
222254
this.spring.register(RestOperationsConfig.class, DefaultInLambdaConfig.class, BasicController.class).autowire();
@@ -1418,6 +1450,10 @@ private <T> T verifyBean(Class<T> beanClass) {
14181450
return verify(this.spring.getContext().getBean(beanClass));
14191451
}
14201452

1453+
private <T> T verifyBean(Class<T> beanClass, VerificationMode mode) {
1454+
return verify(this.spring.getContext().getBean(beanClass), mode);
1455+
}
1456+
14211457
private String json(String name) throws IOException {
14221458
return resource(name + ".json");
14231459
}

config/src/test/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurerTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 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.

0 commit comments

Comments
 (0)