Skip to content

Commit 1d72a05

Browse files
committed
Add SecurityContextHolderStrategy to OAuth2
Issue gh-11060
1 parent 6c16ac1 commit 1d72a05

File tree

9 files changed

+138
-16
lines changed

9 files changed

+138
-16
lines changed

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java

+17-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.
@@ -32,6 +32,7 @@
3232
import org.springframework.security.authentication.AuthenticationManager;
3333
import org.springframework.security.core.Authentication;
3434
import org.springframework.security.core.context.SecurityContextHolder;
35+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3536
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
3637
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
3738
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
@@ -103,6 +104,9 @@
103104
*/
104105
public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
105106

107+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
108+
.getContextHolderStrategy();
109+
106110
private final ClientRegistrationRepository clientRegistrationRepository;
107111

108112
private final OAuth2AuthorizedClientRepository authorizedClientRepository;
@@ -158,6 +162,17 @@ public final void setRequestCache(RequestCache requestCache) {
158162
this.requestCache = requestCache;
159163
}
160164

165+
/**
166+
* Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
167+
* the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
168+
*
169+
* @since 5.8
170+
*/
171+
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
172+
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
173+
this.securityContextHolderStrategy = securityContextHolderStrategy;
174+
}
175+
161176
@Override
162177
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
163178
throws ServletException, IOException {
@@ -232,7 +247,7 @@ private void processAuthorizationResponse(HttpServletRequest request, HttpServle
232247
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build().encode().toString());
233248
return;
234249
}
235-
Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication();
250+
Authentication currentAuthentication = this.securityContextHolderStrategy.getContext().getAuthentication();
236251
String principalName = (currentAuthentication != null) ? currentAuthentication.getName() : "anonymousUser";
237252
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
238253
authenticationResult.getClientRegistration(), principalName, authenticationResult.getAccessToken(),

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolver.java

+18-3
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.
@@ -27,6 +27,7 @@
2727
import org.springframework.security.core.Authentication;
2828
import org.springframework.security.core.authority.AuthorityUtils;
2929
import org.springframework.security.core.context.SecurityContextHolder;
30+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3031
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
3132
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
3233
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
@@ -72,6 +73,9 @@ public final class OAuth2AuthorizedClientArgumentResolver implements HandlerMeth
7273
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous",
7374
"anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
7475

76+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
77+
.getContextHolderStrategy();
78+
7579
private OAuth2AuthorizedClientManager authorizedClientManager;
7680

7781
private boolean defaultAuthorizedClientManager;
@@ -120,7 +124,7 @@ public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewC
120124
+ "It must be provided via @RegisteredOAuth2AuthorizedClient(\"client1\") or "
121125
+ "@RegisteredOAuth2AuthorizedClient(registrationId = \"client1\").");
122126
}
123-
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
127+
Authentication principal = this.securityContextHolderStrategy.getContext().getAuthentication();
124128
if (principal == null) {
125129
principal = ANONYMOUS_AUTHENTICATION;
126130
}
@@ -140,7 +144,7 @@ public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewC
140144
private String resolveClientRegistrationId(MethodParameter parameter) {
141145
RegisteredOAuth2AuthorizedClient authorizedClientAnnotation = AnnotatedElementUtils
142146
.findMergedAnnotation(parameter.getParameter(), RegisteredOAuth2AuthorizedClient.class);
143-
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
147+
Authentication principal = this.securityContextHolderStrategy.getContext().getAuthentication();
144148
if (!StringUtils.isEmpty(authorizedClientAnnotation.registrationId())) {
145149
return authorizedClientAnnotation.registrationId();
146150
}
@@ -179,6 +183,17 @@ public void setClientCredentialsTokenResponseClient(
179183
updateDefaultAuthorizedClientManager(clientCredentialsTokenResponseClient);
180184
}
181185

186+
/**
187+
* Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
188+
* the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
189+
*
190+
* @since 5.8
191+
*/
192+
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
193+
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
194+
this.securityContextHolderStrategy = securityContextHolderStrategy;
195+
}
196+
182197
private void updateDefaultAuthorizedClientManager(
183198
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient) {
184199
// @formatter:off

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java

+1-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.

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java

+17-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.
@@ -38,6 +38,7 @@
3838
import org.springframework.security.core.Authentication;
3939
import org.springframework.security.core.authority.AuthorityUtils;
4040
import org.springframework.security.core.context.SecurityContextHolder;
41+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
4142
import org.springframework.security.oauth2.client.ClientAuthorizationException;
4243
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
4344
import org.springframework.security.oauth2.client.OAuth2AuthorizationFailureHandler;
@@ -151,6 +152,9 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
151152
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous",
152153
"anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
153154

155+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
156+
.getContextHolderStrategy();
157+
154158
@Deprecated
155159
private Duration accessTokenExpiresSkew = Duration.ofMinutes(1);
156160

@@ -304,6 +308,17 @@ public void setDefaultClientRegistrationId(String clientRegistrationId) {
304308
this.defaultClientRegistrationId = clientRegistrationId;
305309
}
306310

311+
/**
312+
* Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
313+
* the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
314+
*
315+
* @since 5.8
316+
*/
317+
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
318+
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
319+
this.securityContextHolderStrategy = securityContextHolderStrategy;
320+
}
321+
307322
/**
308323
* Configures the builder with {@link #defaultRequest()} and adds this as a
309324
* {@link ExchangeFilterFunction}
@@ -513,7 +528,7 @@ private void populateDefaultAuthentication(Map<String, Object> attrs) {
513528
if (attrs.containsKey(AUTHENTICATION_ATTR_NAME)) {
514529
return;
515530
}
516-
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
531+
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
517532
attrs.putIfAbsent(AUTHENTICATION_ATTR_NAME, authentication);
518533
}
519534

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java

+20-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.
@@ -39,6 +39,8 @@
3939
import org.springframework.security.core.authority.AuthorityUtils;
4040
import org.springframework.security.core.context.SecurityContext;
4141
import org.springframework.security.core.context.SecurityContextHolder;
42+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
43+
import org.springframework.security.core.context.SecurityContextImpl;
4244
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
4345
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
4446
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
@@ -306,6 +308,23 @@ public void doFilterWhenAuthorizationSucceedsThenRedirected() throws Exception {
306308
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/callback/client-1");
307309
}
308310

311+
@Test
312+
public void doFilterWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
313+
MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1");
314+
MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
315+
MockHttpServletResponse response = new MockHttpServletResponse();
316+
FilterChain filterChain = mock(FilterChain.class);
317+
this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1);
318+
this.setUpAuthenticationResult(this.registration1);
319+
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
320+
given(strategy.getContext())
321+
.willReturn(new SecurityContextImpl(new TestingAuthenticationToken("user", "password")));
322+
this.filter.setSecurityContextHolderStrategy(strategy);
323+
this.filter.doFilter(authorizationResponse, response, filterChain);
324+
verify(strategy).getContext();
325+
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/callback/client-1");
326+
}
327+
309328
@Test
310329
public void doFilterWhenAuthorizationSucceedsAndHasSavedRequestThenRedirectToSavedRequest() throws Exception {
311330
String requestUri = "/saved-request";

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolverTests.java

+16-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.
@@ -34,6 +34,8 @@
3434
import org.springframework.security.core.Authentication;
3535
import org.springframework.security.core.context.SecurityContext;
3636
import org.springframework.security.core.context.SecurityContextHolder;
37+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
38+
import org.springframework.security.core.context.SecurityContextImpl;
3739
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
3840
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
3941
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
@@ -70,6 +72,7 @@
7072
import static org.mockito.ArgumentMatchers.anyString;
7173
import static org.mockito.ArgumentMatchers.eq;
7274
import static org.mockito.BDDMockito.given;
75+
import static org.mockito.Mockito.atLeastOnce;
7376
import static org.mockito.Mockito.mock;
7477
import static org.mockito.Mockito.verify;
7578

@@ -254,6 +257,18 @@ public void resolveArgumentWhenAuthorizedClientFoundThenResolves() throws Except
254257
new ServletWebRequest(this.request, this.response), null)).isSameAs(this.authorizedClient1);
255258
}
256259

260+
@Test
261+
public void resolveArgumentWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
262+
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
263+
given(strategy.getContext()).willReturn(new SecurityContextImpl(this.authentication));
264+
this.argumentResolver.setSecurityContextHolderStrategy(strategy);
265+
MethodParameter methodParameter = this.getMethodParameter("paramTypeAuthorizedClient",
266+
OAuth2AuthorizedClient.class);
267+
assertThat(this.argumentResolver.resolveArgument(methodParameter, null,
268+
new ServletWebRequest(this.request, this.response), null)).isSameAs(this.authorizedClient1);
269+
verify(strategy, atLeastOnce()).getContext();
270+
}
271+
257272
@Test
258273
public void resolveArgumentWhenRegistrationIdInvalidThenThrowIllegalArgumentException() {
259274
MethodParameter methodParameter = this.getMethodParameter("registrationIdInvalid",

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunctionTests.java

+15-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.
@@ -65,6 +65,8 @@
6565
import org.springframework.security.core.GrantedAuthority;
6666
import org.springframework.security.core.authority.AuthorityUtils;
6767
import org.springframework.security.core.context.SecurityContextHolder;
68+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
69+
import org.springframework.security.core.context.SecurityContextImpl;
6870
import org.springframework.security.oauth2.client.ClientAuthorizationException;
6971
import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
7072
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
@@ -282,6 +284,18 @@ public void defaultRequestAuthenticationWhenAuthenticationSetThenAuthenticationS
282284
verifyNoInteractions(this.authorizedClientRepository);
283285
}
284286

287+
@Test
288+
public void defaultRequestAuthenticationWhenCustomSecurityContextHolderStrategyThenAuthenticationSet() {
289+
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
290+
given(strategy.getContext()).willReturn(new SecurityContextImpl(this.authentication));
291+
this.function.setSecurityContextHolderStrategy(strategy);
292+
Map<String, Object> attrs = getDefaultRequestAttributes();
293+
assertThat(ServletOAuth2AuthorizedClientExchangeFilterFunction.getAuthentication(attrs))
294+
.isEqualTo(this.authentication);
295+
verify(strategy).getContext();
296+
verifyNoInteractions(this.authorizedClientRepository);
297+
}
298+
285299
private Map<String, Object> getDefaultRequestAttributes() {
286300
this.function.defaultRequest().accept(this.spec);
287301
verify(this.spec).attributes(this.attrs.capture());

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenAuthenticationFilter.java

+19-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.
@@ -32,6 +32,7 @@
3232
import org.springframework.security.core.AuthenticationException;
3333
import org.springframework.security.core.context.SecurityContext;
3434
import org.springframework.security.core.context.SecurityContextHolder;
35+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3536
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3637
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
3738
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
@@ -64,6 +65,9 @@ public final class BearerTokenAuthenticationFilter extends OncePerRequestFilter
6465

6566
private final AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
6667

68+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
69+
.getContextHolderStrategy();
70+
6771
private AuthenticationEntryPoint authenticationEntryPoint = new BearerTokenAuthenticationEntryPoint();
6872

6973
private AuthenticationFailureHandler authenticationFailureHandler = (request, response, exception) -> {
@@ -132,22 +136,33 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
132136
try {
133137
AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
134138
Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);
135-
SecurityContext context = SecurityContextHolder.createEmptyContext();
139+
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
136140
context.setAuthentication(authenticationResult);
137-
SecurityContextHolder.setContext(context);
141+
this.securityContextHolderStrategy.setContext(context);
138142
this.securityContextRepository.saveContext(context, request, response);
139143
if (this.logger.isDebugEnabled()) {
140144
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authenticationResult));
141145
}
142146
filterChain.doFilter(request, response);
143147
}
144148
catch (AuthenticationException failed) {
145-
SecurityContextHolder.clearContext();
149+
this.securityContextHolderStrategy.clearContext();
146150
this.logger.trace("Failed to process authentication request", failed);
147151
this.authenticationFailureHandler.onAuthenticationFailure(request, response, failed);
148152
}
149153
}
150154

155+
/**
156+
* Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
157+
* the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
158+
*
159+
* @since 5.8
160+
*/
161+
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
162+
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
163+
this.securityContextHolderStrategy = securityContextHolderStrategy;
164+
}
165+
151166
/**
152167
* Sets the {@link SecurityContextRepository} to save the {@link SecurityContext} on
153168
* authentication success. The default action is not to save the

0 commit comments

Comments
 (0)