Skip to content

Commit f86992a

Browse files
committed
Add SecurityContextHolderStrategy Test Support
Issue gh-11061 Issue gh-11444
1 parent fa0086d commit f86992a

6 files changed

+122
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.test.context;
18+
19+
import org.springframework.security.core.context.SecurityContext;
20+
import org.springframework.security.core.context.SecurityContextHolder;
21+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
22+
23+
public final class TestSecurityContextHolderStrategyAdapter implements SecurityContextHolderStrategy {
24+
25+
@Override
26+
public void clearContext() {
27+
TestSecurityContextHolder.clearContext();
28+
}
29+
30+
@Override
31+
public SecurityContext getContext() {
32+
return TestSecurityContextHolder.getContext();
33+
}
34+
35+
@Override
36+
public void setContext(SecurityContext context) {
37+
TestSecurityContextHolder.setContext(context);
38+
}
39+
40+
@Override
41+
public SecurityContext createEmptyContext() {
42+
return SecurityContextHolder.createEmptyContext();
43+
}
44+
45+
}

test/src/main/java/org/springframework/security/test/context/support/WithAnonymousUserSecurityContextFactory.java

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

1919
import java.util.List;
2020

21+
import org.springframework.beans.factory.annotation.Autowired;
2122
import org.springframework.security.authentication.AnonymousAuthenticationToken;
2223
import org.springframework.security.core.Authentication;
2324
import org.springframework.security.core.GrantedAuthority;
2425
import org.springframework.security.core.authority.AuthorityUtils;
2526
import org.springframework.security.core.context.SecurityContext;
2627
import org.springframework.security.core.context.SecurityContextHolder;
28+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
2729

2830
/**
2931
* A {@link WithAnonymousUserSecurityContextFactory} that runs with an
@@ -35,13 +37,21 @@
3537
*/
3638
final class WithAnonymousUserSecurityContextFactory implements WithSecurityContextFactory<WithAnonymousUser> {
3739

40+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
41+
.getContextHolderStrategy();
42+
3843
@Override
3944
public SecurityContext createSecurityContext(WithAnonymousUser withUser) {
4045
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS");
4146
Authentication authentication = new AnonymousAuthenticationToken("key", "anonymous", authorities);
42-
SecurityContext context = SecurityContextHolder.createEmptyContext();
47+
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
4348
context.setAuthentication(authentication);
4449
return context;
4550
}
4651

52+
@Autowired(required = false)
53+
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
54+
this.securityContextHolderStrategy = securityContextHolderStrategy;
55+
}
56+
4757
}

test/src/main/java/org/springframework/security/test/context/support/WithMockUserSecurityContextFactory.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
import java.util.Arrays;
2121
import java.util.List;
2222

23+
import org.springframework.beans.factory.annotation.Autowired;
2324
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
2425
import org.springframework.security.core.Authentication;
2526
import org.springframework.security.core.GrantedAuthority;
2627
import org.springframework.security.core.authority.SimpleGrantedAuthority;
2728
import org.springframework.security.core.context.SecurityContext;
2829
import org.springframework.security.core.context.SecurityContextHolder;
30+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
2931
import org.springframework.security.core.userdetails.User;
3032
import org.springframework.util.Assert;
3133
import org.springframework.util.StringUtils;
@@ -39,6 +41,9 @@
3941
*/
4042
final class WithMockUserSecurityContextFactory implements WithSecurityContextFactory<WithMockUser> {
4143

44+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
45+
.getContextHolderStrategy();
46+
4247
@Override
4348
public SecurityContext createSecurityContext(WithMockUser withUser) {
4449
String username = StringUtils.hasLength(withUser.username()) ? withUser.username() : withUser.value();
@@ -60,9 +65,14 @@ else if (!(withUser.roles().length == 1 && "USER".equals(withUser.roles()[0])))
6065
User principal = new User(username, withUser.password(), true, true, true, true, grantedAuthorities);
6166
Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(principal,
6267
principal.getPassword(), principal.getAuthorities());
63-
SecurityContext context = SecurityContextHolder.createEmptyContext();
68+
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
6469
context.setAuthentication(authentication);
6570
return context;
6671
}
6772

73+
@Autowired(required = false)
74+
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
75+
this.securityContextHolderStrategy = securityContextHolderStrategy;
76+
}
77+
6878
}

test/src/main/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListener.java

+21-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 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.
@@ -21,12 +21,16 @@
2121
import java.util.function.Supplier;
2222

2323
import org.springframework.beans.BeanUtils;
24+
import org.springframework.context.ApplicationContext;
2425
import org.springframework.core.GenericTypeResolver;
2526
import org.springframework.core.annotation.AnnotatedElementUtils;
2627
import org.springframework.core.annotation.AnnotationUtils;
28+
import org.springframework.core.convert.converter.Converter;
2729
import org.springframework.security.core.context.SecurityContext;
2830
import org.springframework.security.core.context.SecurityContextHolder;
31+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
2932
import org.springframework.security.test.context.TestSecurityContextHolder;
33+
import org.springframework.security.test.context.TestSecurityContextHolderStrategyAdapter;
3034
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
3135
import org.springframework.test.context.TestContext;
3236
import org.springframework.test.context.TestContextAnnotationUtils;
@@ -53,6 +57,19 @@ public class WithSecurityContextTestExecutionListener extends AbstractTestExecut
5357
static final String SECURITY_CONTEXT_ATTR_NAME = WithSecurityContextTestExecutionListener.class.getName()
5458
.concat(".SECURITY_CONTEXT");
5559

60+
static final SecurityContextHolderStrategy DEFAULT_SECURITY_CONTEXT_HOLDER_STRATEGY = new TestSecurityContextHolderStrategyAdapter();
61+
62+
Converter<TestContext, SecurityContextHolderStrategy> securityContextHolderStrategyConverter = (testContext) -> {
63+
if (!testContext.hasApplicationContext()) {
64+
return DEFAULT_SECURITY_CONTEXT_HOLDER_STRATEGY;
65+
}
66+
ApplicationContext context = testContext.getApplicationContext();
67+
if (context.getBeanNamesForType(SecurityContextHolderStrategy.class).length == 0) {
68+
return DEFAULT_SECURITY_CONTEXT_HOLDER_STRATEGY;
69+
}
70+
return context.getBean(SecurityContextHolderStrategy.class);
71+
};
72+
5673
/**
5774
* Sets up the {@link SecurityContext} for each test method. First the specific method
5875
* is inspected for a {@link WithSecurityContext} or {@link Annotation} that has
@@ -70,7 +87,7 @@ public void beforeTestMethod(TestContext testContext) {
7087
}
7188
Supplier<SecurityContext> supplier = testSecurityContext.getSecurityContextSupplier();
7289
if (testSecurityContext.getTestExecutionEvent() == TestExecutionEvent.TEST_METHOD) {
73-
TestSecurityContextHolder.setContext(supplier.get());
90+
this.securityContextHolderStrategyConverter.convert(testContext).setContext(supplier.get());
7491
}
7592
else {
7693
testContext.setAttribute(SECURITY_CONTEXT_ATTR_NAME, supplier);
@@ -86,7 +103,7 @@ public void beforeTestExecution(TestContext testContext) {
86103
Supplier<SecurityContext> supplier = (Supplier<SecurityContext>) testContext
87104
.removeAttribute(SECURITY_CONTEXT_ATTR_NAME);
88105
if (supplier != null) {
89-
TestSecurityContextHolder.setContext(supplier.get());
106+
this.securityContextHolderStrategyConverter.convert(testContext).setContext(supplier.get());
90107
}
91108
}
92109

@@ -166,7 +183,7 @@ private WithSecurityContextFactory<? extends Annotation> createFactory(WithSecur
166183
*/
167184
@Override
168185
public void afterTestMethod(TestContext testContext) {
169-
TestSecurityContextHolder.clearContext();
186+
this.securityContextHolderStrategyConverter.convert(testContext).clearContext();
170187
}
171188

172189
/**

test/src/main/java/org/springframework/security/test/context/support/WithUserDetailsSecurityContextFactory.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.security.core.Authentication;
2525
import org.springframework.security.core.context.SecurityContext;
2626
import org.springframework.security.core.context.SecurityContextHolder;
27+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
2728
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
2829
import org.springframework.security.core.userdetails.UserDetails;
2930
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -45,6 +46,9 @@ final class WithUserDetailsSecurityContextFactory implements WithSecurityContext
4546
private static final boolean reactorPresent = ClassUtils.isPresent("reactor.core.publisher.Mono",
4647
WithUserDetailsSecurityContextFactory.class.getClassLoader());
4748

49+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
50+
.getContextHolderStrategy();
51+
4852
private BeanFactory beans;
4953

5054
@Autowired
@@ -61,11 +65,16 @@ public SecurityContext createSecurityContext(WithUserDetails withUser) {
6165
UserDetails principal = userDetailsService.loadUserByUsername(username);
6266
Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(principal,
6367
principal.getPassword(), principal.getAuthorities());
64-
SecurityContext context = SecurityContextHolder.createEmptyContext();
68+
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
6569
context.setAuthentication(authentication);
6670
return context;
6771
}
6872

73+
@Autowired(required = false)
74+
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
75+
this.securityContextHolderStrategy = securityContextHolderStrategy;
76+
}
77+
6978
private UserDetailsService findUserDetailsService(String beanName) {
7079
if (reactorPresent) {
7180
UserDetailsService reactive = findAndAdaptReactiveUserDetailsService(beanName);

test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java

+23-7
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.springframework.security.core.authority.SimpleGrantedAuthority;
5656
import org.springframework.security.core.context.SecurityContext;
5757
import org.springframework.security.core.context.SecurityContextHolder;
58+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
5859
import org.springframework.security.core.userdetails.User;
5960
import org.springframework.security.core.userdetails.UserDetails;
6061
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
@@ -85,6 +86,7 @@
8586
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
8687
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
8788
import org.springframework.security.test.context.TestSecurityContextHolder;
89+
import org.springframework.security.test.context.TestSecurityContextHolderStrategyAdapter;
8890
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
8991
import org.springframework.security.test.web.support.WebTestUtils;
9092
import org.springframework.security.web.context.HttpRequestResponseHolder;
@@ -115,6 +117,8 @@
115117
*/
116118
public final class SecurityMockMvcRequestPostProcessors {
117119

120+
private static final SecurityContextHolderStrategy DEFAULT_SECURITY_CONTEXT_HOLDER_STRATEGY = new TestSecurityContextHolderStrategyAdapter();
121+
118122
private SecurityMockMvcRequestPostProcessors() {
119123
}
120124

@@ -455,6 +459,18 @@ public static OAuth2ClientRequestPostProcessor oauth2Client(String registrationI
455459
return new OAuth2ClientRequestPostProcessor(registrationId);
456460
}
457461

462+
private static SecurityContextHolderStrategy getSecurityContextHolderStrategy(HttpServletRequest request) {
463+
WebApplicationContext context = WebApplicationContextUtils
464+
.findWebApplicationContext(request.getServletContext());
465+
if (context == null) {
466+
return DEFAULT_SECURITY_CONTEXT_HOLDER_STRATEGY;
467+
}
468+
if (context.getBeanNamesForType(SecurityContextHolderStrategy.class).length == 0) {
469+
return DEFAULT_SECURITY_CONTEXT_HOLDER_STRATEGY;
470+
}
471+
return context.getBean(SecurityContextHolderStrategy.class);
472+
}
473+
458474
/**
459475
* Populates the X509Certificate instances onto the request
460476
*/
@@ -710,7 +726,7 @@ private abstract static class SecurityContextRequestPostProcessorSupport {
710726
* @param request the {@link HttpServletRequest} to use
711727
*/
712728
final void save(Authentication authentication, HttpServletRequest request) {
713-
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
729+
SecurityContext securityContext = getSecurityContextHolderStrategy(request).createEmptyContext();
714730
securityContext.setAuthentication(authentication);
715731
save(securityContext, request);
716732
}
@@ -790,17 +806,17 @@ private static SecurityContext getContext(HttpServletRequest request) {
790806
private static final class TestSecurityContextHolderPostProcessor extends SecurityContextRequestPostProcessorSupport
791807
implements RequestPostProcessor {
792808

793-
private SecurityContext EMPTY = SecurityContextHolder.createEmptyContext();
794-
795809
@Override
796810
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
797811
// TestSecurityContextHolder is only a default value
798812
SecurityContext existingContext = TestSecurityContextRepository.getContext(request);
799813
if (existingContext != null) {
800814
return request;
801815
}
802-
SecurityContext context = TestSecurityContextHolder.getContext();
803-
if (!this.EMPTY.equals(context)) {
816+
SecurityContextHolderStrategy strategy = getSecurityContextHolderStrategy(request);
817+
SecurityContext empty = strategy.createEmptyContext();
818+
SecurityContext context = strategy.getContext();
819+
if (!empty.equals(context)) {
804820
save(context, request);
805821
}
806822
return request;
@@ -851,7 +867,7 @@ private AuthenticationRequestPostProcessor(Authentication authentication) {
851867

852868
@Override
853869
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
854-
SecurityContext context = SecurityContextHolder.createEmptyContext();
870+
SecurityContext context = getSecurityContextHolderStrategy(request).createEmptyContext();
855871
context.setAuthentication(this.authentication);
856872
save(this.authentication, request);
857873
return request;
@@ -869,7 +885,7 @@ public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request)
869885
*/
870886
private static final class UserDetailsRequestPostProcessor implements RequestPostProcessor {
871887

872-
private final RequestPostProcessor delegate;
888+
private final AuthenticationRequestPostProcessor delegate;
873889

874890
UserDetailsRequestPostProcessor(UserDetails user) {
875891
Authentication token = UsernamePasswordAuthenticationToken.authenticated(user, user.getPassword(),

0 commit comments

Comments
 (0)