Skip to content

Commit 14584b0

Browse files
committed
Add SecurityContextHolderStrategy to OAuth2
Issue gh-11060
1 parent 72a46dd commit 14584b0

File tree

8 files changed

+131
-9
lines changed

8 files changed

+131
-9
lines changed

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

Lines changed: 17 additions & 2 deletions
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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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.OAuth2AuthorizeRequest;
3132
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
3233
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
@@ -67,6 +68,9 @@ public final class OAuth2AuthorizedClientArgumentResolver implements HandlerMeth
6768
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous",
6869
"anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
6970

71+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
72+
.getContextHolderStrategy();
73+
7074
private OAuth2AuthorizedClientManager authorizedClientManager;
7175

7276
/**
@@ -112,7 +116,7 @@ public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewC
112116
+ "It must be provided via @RegisteredOAuth2AuthorizedClient(\"client1\") or "
113117
+ "@RegisteredOAuth2AuthorizedClient(registrationId = \"client1\").");
114118
}
115-
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
119+
Authentication principal = this.securityContextHolderStrategy.getContext().getAuthentication();
116120
if (principal == null) {
117121
principal = ANONYMOUS_AUTHENTICATION;
118122
}
@@ -132,7 +136,7 @@ public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewC
132136
private String resolveClientRegistrationId(MethodParameter parameter) {
133137
RegisteredOAuth2AuthorizedClient authorizedClientAnnotation = AnnotatedElementUtils
134138
.findMergedAnnotation(parameter.getParameter(), RegisteredOAuth2AuthorizedClient.class);
135-
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
139+
Authentication principal = this.securityContextHolderStrategy.getContext().getAuthentication();
136140
if (!StringUtils.isEmpty(authorizedClientAnnotation.registrationId())) {
137141
return authorizedClientAnnotation.registrationId();
138142
}
@@ -145,4 +149,15 @@ private String resolveClientRegistrationId(MethodParameter parameter) {
145149
return null;
146150
}
147151

152+
/**
153+
* Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
154+
* the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
155+
*
156+
* @since 5.8
157+
*/
158+
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
159+
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
160+
this.securityContextHolderStrategy = securityContextHolderStrategy;
161+
}
162+
148163
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.security.core.Authentication;
3737
import org.springframework.security.core.authority.AuthorityUtils;
3838
import org.springframework.security.core.context.SecurityContextHolder;
39+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3940
import org.springframework.security.oauth2.client.ClientAuthorizationException;
4041
import org.springframework.security.oauth2.client.OAuth2AuthorizationFailureHandler;
4142
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
@@ -145,6 +146,9 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
145146
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous",
146147
"anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
147148

149+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
150+
.getContextHolderStrategy();
151+
148152
private OAuth2AuthorizedClientManager authorizedClientManager;
149153

150154
private boolean defaultOAuth2AuthorizedClient;
@@ -243,6 +247,17 @@ public void setDefaultClientRegistrationId(String clientRegistrationId) {
243247
this.defaultClientRegistrationId = clientRegistrationId;
244248
}
245249

250+
/**
251+
* Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
252+
* the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
253+
*
254+
* @since 5.8
255+
*/
256+
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
257+
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
258+
this.securityContextHolderStrategy = securityContextHolderStrategy;
259+
}
260+
246261
/**
247262
* Configures the builder with {@link #defaultRequest()} and adds this as a
248263
* {@link ExchangeFilterFunction}
@@ -431,7 +446,7 @@ private void populateDefaultAuthentication(Map<String, Object> attrs) {
431446
if (attrs.containsKey(AUTHENTICATION_ATTR_NAME)) {
432447
return;
433448
}
434-
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
449+
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
435450
attrs.putIfAbsent(AUTHENTICATION_ATTR_NAME, authentication);
436451
}
437452

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

Lines changed: 20 additions & 1 deletion
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,8 @@
3838
import org.springframework.security.core.authority.AuthorityUtils;
3939
import org.springframework.security.core.context.SecurityContext;
4040
import org.springframework.security.core.context.SecurityContextHolder;
41+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
42+
import org.springframework.security.core.context.SecurityContextImpl;
4143
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
4244
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
4345
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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import org.springframework.security.core.Authentication;
3434
import org.springframework.security.core.context.SecurityContext;
3535
import org.springframework.security.core.context.SecurityContextHolder;
36+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
37+
import org.springframework.security.core.context.SecurityContextImpl;
3638
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
3739
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
3840
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
@@ -67,6 +69,7 @@
6769
import static org.mockito.ArgumentMatchers.anyString;
6870
import static org.mockito.ArgumentMatchers.eq;
6971
import static org.mockito.BDDMockito.given;
72+
import static org.mockito.Mockito.atLeastOnce;
7073
import static org.mockito.Mockito.mock;
7174
import static org.mockito.Mockito.verify;
7275

@@ -233,6 +236,18 @@ public void resolveArgumentWhenAuthorizedClientFoundThenResolves() throws Except
233236
new ServletWebRequest(this.request, this.response), null)).isSameAs(this.authorizedClient1);
234237
}
235238

239+
@Test
240+
public void resolveArgumentWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
241+
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
242+
given(strategy.getContext()).willReturn(new SecurityContextImpl(this.authentication));
243+
this.argumentResolver.setSecurityContextHolderStrategy(strategy);
244+
MethodParameter methodParameter = this.getMethodParameter("paramTypeAuthorizedClient",
245+
OAuth2AuthorizedClient.class);
246+
assertThat(this.argumentResolver.resolveArgument(methodParameter, null,
247+
new ServletWebRequest(this.request, this.response), null)).isSameAs(this.authorizedClient1);
248+
verify(strategy, atLeastOnce()).getContext();
249+
}
250+
236251
@Test
237252
public void resolveArgumentWhenRegistrationIdInvalidThenThrowIllegalArgumentException() {
238253
MethodParameter methodParameter = this.getMethodParameter("registrationIdInvalid",

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464
import org.springframework.security.core.GrantedAuthority;
6565
import org.springframework.security.core.authority.AuthorityUtils;
6666
import org.springframework.security.core.context.SecurityContextHolder;
67+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
68+
import org.springframework.security.core.context.SecurityContextImpl;
6769
import org.springframework.security.oauth2.client.ClientAuthorizationException;
6870
import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
6971
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
@@ -250,6 +252,18 @@ public void defaultRequestAuthenticationWhenAuthenticationSetThenAuthenticationS
250252
verifyNoInteractions(this.authorizedClientRepository);
251253
}
252254

255+
@Test
256+
public void defaultRequestAuthenticationWhenCustomSecurityContextHolderStrategyThenAuthenticationSet() {
257+
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
258+
given(strategy.getContext()).willReturn(new SecurityContextImpl(this.authentication));
259+
this.function.setSecurityContextHolderStrategy(strategy);
260+
Map<String, Object> attrs = getDefaultRequestAttributes();
261+
assertThat(ServletOAuth2AuthorizedClientExchangeFilterFunction.getAuthentication(attrs))
262+
.isEqualTo(this.authentication);
263+
verify(strategy).getContext();
264+
verifyNoInteractions(this.authorizedClientRepository);
265+
}
266+
253267
private Map<String, Object> getDefaultRequestAttributes() {
254268
this.function.defaultRequest().accept(this.spec);
255269
verify(this.spec).attributes(this.attrs.capture());

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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.authentication.BearerTokenAuthenticationToken;
3738
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
@@ -67,6 +68,9 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter {
6768

6869
private final AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
6970

71+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
72+
.getContextHolderStrategy();
73+
7074
private AuthenticationEntryPoint authenticationEntryPoint = new BearerTokenAuthenticationEntryPoint();
7175

7276
private AuthenticationFailureHandler authenticationFailureHandler = (request, response, exception) -> {
@@ -135,22 +139,33 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
135139
try {
136140
AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
137141
Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);
138-
SecurityContext context = SecurityContextHolder.createEmptyContext();
142+
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
139143
context.setAuthentication(authenticationResult);
140-
SecurityContextHolder.setContext(context);
144+
this.securityContextHolderStrategy.setContext(context);
141145
this.securityContextRepository.saveContext(context, request, response);
142146
if (this.logger.isDebugEnabled()) {
143147
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authenticationResult));
144148
}
145149
filterChain.doFilter(request, response);
146150
}
147151
catch (AuthenticationException failed) {
148-
SecurityContextHolder.clearContext();
152+
this.securityContextHolderStrategy.clearContext();
149153
this.logger.trace("Failed to process authentication request", failed);
150154
this.authenticationFailureHandler.onAuthenticationFailure(request, response, failed);
151155
}
152156
}
153157

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

oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import org.springframework.security.authentication.AuthenticationServiceException;
3838
import org.springframework.security.authentication.TestingAuthenticationToken;
3939
import org.springframework.security.core.context.SecurityContext;
40+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
41+
import org.springframework.security.core.context.SecurityContextImpl;
4042
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
4143
import org.springframework.security.oauth2.server.resource.BearerTokenError;
4244
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
@@ -210,6 +212,18 @@ public void doFilterWhenCustomAuthenticationDetailsSourceThenUses() throws Servl
210212
verify(this.authenticationDetailsSource).buildDetails(this.request);
211213
}
212214

215+
@Test
216+
public void doFilterWhenCustomSecurityContextHolderStrategyThenUses() throws ServletException, IOException {
217+
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
218+
BearerTokenAuthenticationFilter filter = addMocks(
219+
new BearerTokenAuthenticationFilter(this.authenticationManager));
220+
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
221+
given(strategy.createEmptyContext()).willReturn(new SecurityContextImpl());
222+
filter.setSecurityContextHolderStrategy(strategy);
223+
filter.doFilter(this.request, this.response, this.filterChain);
224+
verify(strategy).setContext(any());
225+
}
226+
213227
@Test
214228
public void setAuthenticationEntryPointWhenNullThenThrowsException() {
215229
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager);

0 commit comments

Comments
 (0)