Skip to content

Commit 74d646f

Browse files
committed
Add SecurityContextHolderStrategy Java Configuration for Method Security
Issue gh-11061
1 parent 7a9c873 commit 74d646f

File tree

7 files changed

+142
-13
lines changed

7 files changed

+142
-13
lines changed

config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java

+13-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.
@@ -73,6 +73,8 @@
7373
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
7474
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
7575
import org.springframework.security.config.core.GrantedAuthorityDefaults;
76+
import org.springframework.security.core.context.SecurityContextHolder;
77+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
7678
import org.springframework.util.Assert;
7779

7880
/**
@@ -101,6 +103,9 @@ public <T> T postProcess(T object) {
101103

102104
};
103105

106+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
107+
.getContextHolderStrategy();
108+
104109
private DefaultMethodSecurityExpressionHandler defaultMethodExpressionHandler = new DefaultMethodSecurityExpressionHandler();
105110

106111
private AuthenticationManager authenticationManager;
@@ -143,6 +148,7 @@ public MethodInterceptor methodSecurityInterceptor(MethodSecurityMetadataSource
143148
this.methodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
144149
this.methodSecurityInterceptor.setAfterInvocationManager(afterInvocationManager());
145150
this.methodSecurityInterceptor.setSecurityMetadataSource(methodSecurityMetadataSource);
151+
this.methodSecurityInterceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
146152
RunAsManager runAsManager = runAsManager();
147153
if (runAsManager != null) {
148154
this.methodSecurityInterceptor.setRunAsManager(runAsManager);
@@ -411,6 +417,12 @@ public void setMethodSecurityExpressionHandler(List<MethodSecurityExpressionHand
411417
this.expressionHandler = handlers.get(0);
412418
}
413419

420+
@Autowired(required = false)
421+
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
422+
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
423+
this.securityContextHolderStrategy = securityContextHolderStrategy;
424+
}
425+
414426
@Override
415427
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
416428
this.context = beanFactory;

config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java

+15-2
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.
@@ -25,6 +25,8 @@
2525
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
2626
import org.springframework.security.authorization.method.Jsr250AuthorizationManager;
2727
import org.springframework.security.config.core.GrantedAuthorityDefaults;
28+
import org.springframework.security.core.context.SecurityContextHolder;
29+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
2830

2931
/**
3032
* {@link Configuration} for enabling JSR-250 Spring Security Method Security.
@@ -40,15 +42,26 @@ final class Jsr250MethodSecurityConfiguration {
4042

4143
private final Jsr250AuthorizationManager jsr250AuthorizationManager = new Jsr250AuthorizationManager();
4244

45+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
46+
.getContextHolderStrategy();
47+
4348
@Bean
4449
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
4550
Advisor jsr250AuthorizationMethodInterceptor() {
46-
return AuthorizationManagerBeforeMethodInterceptor.jsr250(this.jsr250AuthorizationManager);
51+
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
52+
.jsr250(this.jsr250AuthorizationManager);
53+
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
54+
return interceptor;
4755
}
4856

4957
@Autowired(required = false)
5058
void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) {
5159
this.jsr250AuthorizationManager.setRolePrefix(grantedAuthorityDefaults.getRolePrefix());
5260
}
5361

62+
@Autowired(required = false)
63+
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
64+
this.securityContextHolderStrategy = securityContextHolderStrategy;
65+
}
66+
5467
}

config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java

+9
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
3535
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
3636
import org.springframework.security.config.core.GrantedAuthorityDefaults;
37+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3738

3839
/**
3940
* Base {@link Configuration} for enabling Spring Security Method Security.
@@ -109,6 +110,14 @@ void setMethodSecurityExpressionHandler(MethodSecurityExpressionHandler methodSe
109110
this.postFilterAuthorizationMethodInterceptor.setExpressionHandler(methodSecurityExpressionHandler);
110111
}
111112

113+
@Autowired(required = false)
114+
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy strategy) {
115+
this.preFilterAuthorizationMethodInterceptor.setSecurityContextHolderStrategy(strategy);
116+
this.preAuthorizeAuthorizationMethodInterceptor.setSecurityContextHolderStrategy(strategy);
117+
this.postAuthorizeAuthorizaitonMethodInterceptor.setSecurityContextHolderStrategy(strategy);
118+
this.postFilterAuthorizationMethodInterceptor.setSecurityContextHolderStrategy(strategy);
119+
}
120+
112121
@Autowired(required = false)
113122
void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) {
114123
this.expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());

config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java

+15-2
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.
@@ -17,12 +17,15 @@
1717
package org.springframework.security.config.annotation.method.configuration;
1818

1919
import org.springframework.aop.Advisor;
20+
import org.springframework.beans.factory.annotation.Autowired;
2021
import org.springframework.beans.factory.config.BeanDefinition;
2122
import org.springframework.context.annotation.Bean;
2223
import org.springframework.context.annotation.Configuration;
2324
import org.springframework.context.annotation.Role;
2425
import org.springframework.security.access.annotation.Secured;
2526
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
27+
import org.springframework.security.core.context.SecurityContextHolder;
28+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
2629

2730
/**
2831
* {@link Configuration} for enabling {@link Secured} Spring Security Method Security.
@@ -36,10 +39,20 @@
3639
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
3740
final class SecuredMethodSecurityConfiguration {
3841

42+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
43+
.getContextHolderStrategy();
44+
3945
@Bean
4046
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
4147
Advisor securedAuthorizationMethodInterceptor() {
42-
return AuthorizationManagerBeforeMethodInterceptor.secured();
48+
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor.secured();
49+
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
50+
return interceptor;
51+
}
52+
53+
@Autowired(required = false)
54+
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
55+
this.securityContextHolderStrategy = securityContextHolderStrategy;
4356
}
4457

4558
}

config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java

+30-7
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,24 @@
5050
import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder;
5151
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
5252
import org.springframework.security.authorization.method.MethodInvocationResult;
53+
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
5354
import org.springframework.security.config.core.GrantedAuthorityDefaults;
5455
import org.springframework.security.config.test.SpringTestContext;
5556
import org.springframework.security.config.test.SpringTestContextExtension;
57+
import org.springframework.security.config.test.SpringTestParentApplicationContextExecutionListener;
5658
import org.springframework.security.core.Authentication;
57-
import org.springframework.security.core.context.SecurityContextHolder;
58-
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
59+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
5960
import org.springframework.security.test.context.support.WithAnonymousUser;
6061
import org.springframework.security.test.context.support.WithMockUser;
62+
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
63+
import org.springframework.test.context.ContextConfiguration;
64+
import org.springframework.test.context.TestExecutionListeners;
6165
import org.springframework.test.context.junit.jupiter.SpringExtension;
6266

6367
import static org.assertj.core.api.Assertions.assertThat;
6468
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
6569
import static org.mockito.ArgumentMatchers.any;
70+
import static org.mockito.Mockito.atLeastOnce;
6671
import static org.mockito.Mockito.mock;
6772
import static org.mockito.Mockito.verify;
6873

@@ -73,7 +78,9 @@
7378
* @author Josh Cummings
7479
*/
7580
@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class })
76-
@SecurityTestExecutionListeners
81+
@ContextConfiguration(classes = SecurityContextChangedListenerConfig.class)
82+
@TestExecutionListeners(listeners = { WithSecurityContextTestExecutionListener.class,
83+
SpringTestParentApplicationContextExecutionListener.class })
7784
public class PrePostMethodSecurityConfigurationTests {
7885

7986
public final SpringTestContext spring = new SpringTestContext(this);
@@ -137,6 +144,8 @@ public void securedUserWhenRoleAdminThenAccessDeniedException() {
137144
this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire();
138145
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser)
139146
.withMessage("Access Denied");
147+
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
148+
verify(strategy, atLeastOnce()).getContext();
140149
}
141150

142151
@WithMockUser
@@ -162,6 +171,15 @@ public void preAuthorizeAdminWhenRoleAdminThenPasses() {
162171
this.methodSecurityService.preAuthorizeAdmin();
163172
}
164173

174+
@WithMockUser(roles = "ADMIN")
175+
@Test
176+
public void preAuthorizeAdminWhenSecurityContextHolderStrategyThenUses() {
177+
this.spring.register(MethodSecurityServiceConfig.class).autowire();
178+
this.methodSecurityService.preAuthorizeAdmin();
179+
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
180+
verify(strategy, atLeastOnce()).getContext();
181+
}
182+
165183
@WithMockUser(authorities = "PREFIX_ADMIN")
166184
@Test
167185
public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() {
@@ -285,6 +303,8 @@ public void rolesAllowedUserWhenRoleAdminThenAccessDeniedException() {
285303
this.spring.register(BusinessServiceConfig.class).autowire();
286304
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.businessService::rolesAllowedUser)
287305
.withMessage("Access Denied");
306+
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
307+
verify(strategy, atLeastOnce()).getContext();
288308
}
289309

290310
@WithMockUser
@@ -480,12 +500,15 @@ static class CustomAuthorizationManagerBeforeAdviceConfig {
480500

481501
@Bean
482502
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
483-
Advisor customBeforeAdvice() {
503+
Advisor customBeforeAdvice(SecurityContextHolderStrategy strategy) {
484504
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
485505
pointcut.setPattern(".*MethodSecurityServiceImpl.*securedUser");
486506
AuthorizationManager<MethodInvocation> authorizationManager = (a,
487507
o) -> new AuthorizationDecision("bob".equals(a.get().getName()));
488-
return new AuthorizationManagerBeforeMethodInterceptor(pointcut, authorizationManager);
508+
AuthorizationManagerBeforeMethodInterceptor before = new AuthorizationManagerBeforeMethodInterceptor(
509+
pointcut, authorizationManager);
510+
before.setSecurityContextHolderStrategy(strategy);
511+
return before;
489512
}
490513

491514
}
@@ -495,11 +518,11 @@ static class CustomAuthorizationManagerAfterAdviceConfig {
495518

496519
@Bean
497520
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
498-
Advisor customAfterAdvice() {
521+
Advisor customAfterAdvice(SecurityContextHolderStrategy strategy) {
499522
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
500523
pointcut.setPattern(".*MethodSecurityServiceImpl.*securedUser");
501524
MethodInterceptor interceptor = (mi) -> {
502-
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
525+
Authentication auth = strategy.getContext().getAuthentication();
503526
if ("bob".equals(auth.getName())) {
504527
return "granted";
505528
}

config/src/test/java/org/springframework/security/config/test/SpringTestContext.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 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.
@@ -19,6 +19,7 @@
1919
import java.io.Closeable;
2020
import java.util.ArrayList;
2121
import java.util.List;
22+
import java.util.function.Consumer;
2223

2324
import jakarta.servlet.Filter;
2425
import jakarta.servlet.FilterChain;
@@ -56,6 +57,8 @@ public class SpringTestContext implements Closeable {
5657

5758
private List<Filter> filters = new ArrayList<>();
5859

60+
private List<Consumer<ConfigurableWebApplicationContext>> postProcessors = new ArrayList<>();
61+
5962
public SpringTestContext(Object test) {
6063
setTest(test);
6164
}
@@ -104,6 +107,11 @@ public SpringTestContext context(String configuration) {
104107
return this;
105108
}
106109

110+
public SpringTestContext postProcessor(Consumer<ConfigurableWebApplicationContext> contextConsumer) {
111+
this.postProcessors.add(contextConsumer);
112+
return this;
113+
}
114+
107115
public SpringTestContext mockMvcAfterSpringSecurityOk() {
108116
return addFilter(new OncePerRequestFilter() {
109117
@Override
@@ -131,6 +139,9 @@ public ConfigurableWebApplicationContext getContext() {
131139
public void autowire() {
132140
this.context.setServletContext(new MockServletContext());
133141
this.context.setServletConfig(new MockServletConfig());
142+
for (Consumer<ConfigurableWebApplicationContext> postProcessor : this.postProcessors) {
143+
postProcessor.accept(this.context);
144+
}
134145
this.context.refresh();
135146
if (this.context.containsBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN)) {
136147
// @formatter:off
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.config.test;
18+
19+
import java.lang.reflect.Field;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
23+
import org.springframework.context.ApplicationContext;
24+
import org.springframework.test.context.TestContext;
25+
import org.springframework.test.context.TestExecutionListener;
26+
27+
public class SpringTestParentApplicationContextExecutionListener implements TestExecutionListener {
28+
29+
@Override
30+
public void beforeTestMethod(TestContext testContext) throws Exception {
31+
ApplicationContext parent = testContext.getApplicationContext();
32+
Object testInstance = testContext.getTestInstance();
33+
getContexts(testInstance).forEach((springTestContext) -> springTestContext
34+
.postProcessor((applicationContext) -> applicationContext.setParent(parent)));
35+
}
36+
37+
private static List<SpringTestContext> getContexts(Object test) throws IllegalAccessException {
38+
Field[] declaredFields = test.getClass().getDeclaredFields();
39+
List<SpringTestContext> result = new ArrayList<>();
40+
for (Field field : declaredFields) {
41+
if (SpringTestContext.class.isAssignableFrom(field.getType())) {
42+
result.add((SpringTestContext) field.get(test));
43+
}
44+
}
45+
return result;
46+
}
47+
48+
}

0 commit comments

Comments
 (0)