Browse Source

Add SecurityContextHolderStrategy XML Configuration for Method Security

Issue gh-11061
Josh Cummings 3 years ago
parent
commit
5e4e7abf15

+ 77 - 10
config/src/main/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParser.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,9 +21,11 @@ import org.apache.commons.logging.LogFactory;
 import org.w3c.dom.Element;
 
 import org.springframework.aop.config.AopNamespaceUtils;
+import org.springframework.beans.BeanMetadataElement;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.FactoryBean;
 import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.RuntimeBeanReference;
 import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.xml.BeanDefinitionParser;
@@ -41,6 +43,9 @@ import org.springframework.security.authorization.method.PreAuthorizeAuthorizati
 import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
 import org.springframework.security.config.Elements;
 import org.springframework.security.config.core.GrantedAuthorityDefaults;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
+import org.springframework.util.StringUtils;
 import org.springframework.util.xml.DomUtils;
 
 /**
@@ -61,26 +66,33 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
 
 	private static final String ATT_REF = "ref";
 
+	private static final String ATT_SECURITY_CONTEXT_HOLDER_STRATEGY_REF = "security-context-holder-strategy-ref";
+
 	@Override
 	public BeanDefinition parse(Element element, ParserContext pc) {
 		CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(),
 				pc.extractSource(element));
 		pc.pushContainingComponent(compositeDef);
+		BeanMetadataElement securityContextHolderStrategy = getSecurityContextHolderStrategy(element);
 		boolean prePostAnnotationsEnabled = !element.hasAttribute(ATT_USE_PREPOST)
 				|| "true".equals(element.getAttribute(ATT_USE_PREPOST));
 		if (prePostAnnotationsEnabled) {
 			BeanDefinitionBuilder preFilterInterceptor = BeanDefinitionBuilder
 					.rootBeanDefinition(PreFilterAuthorizationMethodInterceptor.class)
-					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
+					.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
 			BeanDefinitionBuilder preAuthorizeInterceptor = BeanDefinitionBuilder
 					.rootBeanDefinition(PreAuthorizeAuthorizationMethodInterceptor.class)
-					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
+					.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
 			BeanDefinitionBuilder postAuthorizeInterceptor = BeanDefinitionBuilder
 					.rootBeanDefinition(PostAuthorizeAuthorizationMethodInterceptor.class)
-					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
+					.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
 			BeanDefinitionBuilder postFilterInterceptor = BeanDefinitionBuilder
 					.rootBeanDefinition(PostFilterAuthorizationMethodInterceptor.class)
-					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
+					.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
 			Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
 			if (expressionHandlerElt != null) {
 				String expressionHandlerRef = expressionHandlerElt.getAttribute(ATT_REF);
@@ -110,7 +122,9 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
 		if (securedEnabled) {
 			BeanDefinitionBuilder securedInterceptor = BeanDefinitionBuilder
 					.rootBeanDefinition(AuthorizationManagerBeforeMethodInterceptor.class)
-					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).setFactoryMethod("secured");
+					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
+					.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
+					.setFactoryMethod("secured");
 			pc.getRegistry().registerBeanDefinition("securedAuthorizationMethodInterceptor",
 					securedInterceptor.getBeanDefinition());
 		}
@@ -118,7 +132,8 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
 		if (jsr250Enabled) {
 			BeanDefinitionBuilder jsr250Interceptor = BeanDefinitionBuilder
 					.rootBeanDefinition(Jsr250AuthorizationMethodInterceptor.class)
-					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+					.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
+					.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
 			pc.getRegistry().registerBeanDefinition("jsr250AuthorizationMethodInterceptor",
 					jsr250Interceptor.getBeanDefinition());
 		}
@@ -127,6 +142,14 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
 		return null;
 	}
 
+	private BeanMetadataElement getSecurityContextHolderStrategy(Element methodSecurityElmt) {
+		String holderStrategyRef = methodSecurityElmt.getAttribute(ATT_SECURITY_CONTEXT_HOLDER_STRATEGY_REF);
+		if (StringUtils.hasText(holderStrategyRef)) {
+			return new RuntimeBeanReference(holderStrategyRef);
+		}
+		return BeanDefinitionBuilder.rootBeanDefinition(SecurityContextHolderStrategyFactory.class).getBeanDefinition();
+	}
+
 	public static final class MethodSecurityExpressionHandlerBean
 			implements FactoryBean<MethodSecurityExpressionHandler>, ApplicationContextAware {
 
@@ -158,11 +181,17 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
 	public static final class Jsr250AuthorizationMethodInterceptor
 			implements FactoryBean<AuthorizationManagerBeforeMethodInterceptor>, ApplicationContextAware {
 
+		private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
+				.getContextHolderStrategy();
+
 		private final Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
 
 		@Override
 		public AuthorizationManagerBeforeMethodInterceptor getObject() {
-			return AuthorizationManagerBeforeMethodInterceptor.jsr250(this.manager);
+			AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
+					.jsr250(this.manager);
+			interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
+			return interceptor;
 		}
 
 		@Override
@@ -181,16 +210,26 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
 			}
 		}
 
+		public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
+			this.securityContextHolderStrategy = securityContextHolderStrategy;
+		}
+
 	}
 
 	public static final class PreAuthorizeAuthorizationMethodInterceptor
 			implements FactoryBean<AuthorizationManagerBeforeMethodInterceptor> {
 
+		private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
+				.getContextHolderStrategy();
+
 		private final PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
 
 		@Override
 		public AuthorizationManagerBeforeMethodInterceptor getObject() {
-			return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(this.manager);
+			AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
+					.preAuthorize(this.manager);
+			interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
+			return interceptor;
 		}
 
 		@Override
@@ -198,6 +237,10 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
 			return AuthorizationManagerBeforeMethodInterceptor.class;
 		}
 
+		public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
+			this.securityContextHolderStrategy = securityContextHolderStrategy;
+		}
+
 		public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
 			this.manager.setExpressionHandler(expressionHandler);
 		}
@@ -207,11 +250,17 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
 	public static final class PostAuthorizeAuthorizationMethodInterceptor
 			implements FactoryBean<AuthorizationManagerAfterMethodInterceptor> {
 
+		private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
+				.getContextHolderStrategy();
+
 		private final PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
 
 		@Override
 		public AuthorizationManagerAfterMethodInterceptor getObject() {
-			return AuthorizationManagerAfterMethodInterceptor.postAuthorize(this.manager);
+			AuthorizationManagerAfterMethodInterceptor interceptor = AuthorizationManagerAfterMethodInterceptor
+					.postAuthorize(this.manager);
+			interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
+			return interceptor;
 		}
 
 		@Override
@@ -219,10 +268,28 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
 			return AuthorizationManagerAfterMethodInterceptor.class;
 		}
 
+		public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
+			this.securityContextHolderStrategy = securityContextHolderStrategy;
+		}
+
 		public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
 			this.manager.setExpressionHandler(expressionHandler);
 		}
 
 	}
 
+	static class SecurityContextHolderStrategyFactory implements FactoryBean<SecurityContextHolderStrategy> {
+
+		@Override
+		public SecurityContextHolderStrategy getObject() throws Exception {
+			return SecurityContextHolder.getContextHolderStrategy();
+		}
+
+		@Override
+		public Class<?> getObjectType() {
+			return SecurityContextHolderStrategy.class;
+		}
+
+	}
+
 }

+ 3 - 0
config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc

@@ -211,6 +211,9 @@ method-security.attlist &=
 method-security.attlist &=
 	## If true, class-based proxying will be used instead of interface-based proxying.
 	attribute proxy-target-class {xsd:boolean}?
+method-security.attlist &=
+	## Specifies the security context holder strategy to use, by default uses a ThreadLocal-based strategy
+	attribute security-context-holder-strategy-ref {xsd:string}?
 
 global-method-security =
 	## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with the ordered list of "protect-pointcut" sub-elements, Spring Security annotations and/or. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. If you use and enable all four sources of method security metadata (ie "protect-pointcut" declarations, expression annotations, @Secured and also JSR250 security annotations), the metadata sources will be queried in that order. In practical terms, this enables you to use XML to override method security metadata expressed in annotations. If using annotations, the order of precedence is EL-based (@PreAuthorize etc.), @Secured and finally JSR-250.

+ 7 - 0
config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd

@@ -651,6 +651,13 @@
                 </xs:documentation>
          </xs:annotation>
       </xs:attribute>
+      <xs:attribute name="security-context-holder-strategy-ref" type="xs:string">
+         <xs:annotation>
+            <xs:documentation>Specifies the security context holder strategy to use, by default uses a ThreadLocal-based
+                strategy
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
   </xs:attributeGroup>
   <xs:element name="global-method-security">
       <xs:annotation>

+ 3 - 0
config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc

@@ -211,6 +211,9 @@ method-security.attlist &=
 method-security.attlist &=
 	## If true, class-based proxying will be used instead of interface-based proxying.
 	attribute proxy-target-class {xsd:boolean}?
+method-security.attlist &=
+	## Specifies the security context holder strategy to use, by default uses a ThreadLocal-based strategy
+	attribute security-context-holder-strategy-ref {xsd:string}?
 
 global-method-security =
 	## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with the ordered list of "protect-pointcut" sub-elements, Spring Security annotations and/or. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. If you use and enable all four sources of method security metadata (ie "protect-pointcut" declarations, expression annotations, @Secured and also JSR250 security annotations), the metadata sources will be queried in that order. In practical terms, this enables you to use XML to override method security metadata expressed in annotations. If using annotations, the order of precedence is EL-based (@PreAuthorize etc.), @Secured and finally JSR-250.

+ 7 - 0
config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd

@@ -651,6 +651,13 @@
                 </xs:documentation>
          </xs:annotation>
       </xs:attribute>
+      <xs:attribute name="security-context-holder-strategy-ref" type="xs:string">
+         <xs:annotation>
+            <xs:documentation>Specifies the security context holder strategy to use, by default uses a ThreadLocal-based
+                strategy
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
   </xs:attributeGroup>
   <xs:element name="global-method-security">
       <xs:annotation>

+ 11 - 1
config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.util.List;
 
 import jakarta.annotation.security.DenyAll;
 import jakarta.annotation.security.PermitAll;
+import jakarta.annotation.security.RolesAllowed;
 
 import org.springframework.security.access.annotation.Secured;
 import org.springframework.security.access.prepost.PostAuthorize;
@@ -49,6 +50,9 @@ public interface MethodSecurityService {
 	@PermitAll
 	String jsr250PermitAll();
 
+	@RolesAllowed("ADMIN")
+	String jsr250RolesAllowed();
+
 	@Secured({ "ROLE_USER", "RUN_AS_SUPER" })
 	Authentication runAs();
 
@@ -73,6 +77,12 @@ public interface MethodSecurityService {
 	@PostAuthorize("#o?.contains('grant')")
 	String postAnnotation(@P("o") String object);
 
+	@PreFilter("filterObject == authentication.name")
+	List<String> preFilterByUsername(List<String> array);
+
+	@PostFilter("filterObject == authentication.name")
+	List<String> postFilterByUsername(List<String> array);
+
 	@PreFilter("filterObject.length > 3")
 	@PreAuthorize("hasRole('ADMIN')")
 	@Secured("ROLE_USER")

+ 16 - 1
config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2022 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -51,6 +51,11 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
 		return null;
 	}
 
+	@Override
+	public String jsr250RolesAllowed() {
+		return null;
+	}
+
 	@Override
 	public Authentication runAs() {
 		return SecurityContextHolder.getContext().getAuthentication();
@@ -88,6 +93,16 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
 		return null;
 	}
 
+	@Override
+	public List<String> preFilterByUsername(List<String> array) {
+		return array;
+	}
+
+	@Override
+	public List<String> postFilterByUsername(List<String> array) {
+		return array;
+	}
+
 	@Override
 	public List<String> manyAnnotations(List<String> object) {
 		return object;

+ 62 - 0
config/src/test/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests.java

@@ -34,6 +34,7 @@ import org.springframework.lang.Nullable;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.access.PermissionEvaluator;
 import org.springframework.security.access.annotation.BusinessService;
+import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationManager;
@@ -41,7 +42,10 @@ import org.springframework.security.config.annotation.method.configuration.Metho
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
+import org.springframework.security.core.context.SecurityContextImpl;
 import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
 import org.springframework.security.test.context.support.WithAnonymousUser;
 import org.springframework.security.test.context.support.WithMockUser;
@@ -49,6 +53,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.Mockito.verify;
 
 /**
  * @author Josh Cummings
@@ -117,6 +122,17 @@ public class MethodSecurityBeanDefinitionParserTests {
 		assertThat(result).isNull();
 	}
 
+	@Test
+	public void securedWhenCustomSecurityContextHolderStrategyThenUses() {
+		this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire();
+		SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
+		SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass"));
+		strategy.setContext(context);
+		assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::secured)
+				.withMessage("Access Denied");
+		verify(strategy).getContext();
+	}
+
 	@WithMockUser(roles = "ADMIN")
 	@Test
 	public void securedUserWhenRoleAdminThenAccessDeniedException() {
@@ -148,6 +164,17 @@ public class MethodSecurityBeanDefinitionParserTests {
 		this.methodSecurityService.preAuthorizeAdmin();
 	}
 
+	@Test
+	public void preAuthorizeWhenCustomSecurityContextHolderStrategyThenUses() {
+		this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire();
+		SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
+		SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass"));
+		strategy.setContext(context);
+		assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorizeAdmin)
+				.withMessage("Access Denied");
+		verify(strategy).getContext();
+	}
+
 	@WithMockUser(authorities = "PREFIX_ADMIN")
 	@Test
 	public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() {
@@ -187,6 +214,30 @@ public class MethodSecurityBeanDefinitionParserTests {
 		assertThat(result).isNull();
 	}
 
+	@Test
+	public void preFilterWhenCustomSecurityContextHolderStrategyThenUses() {
+		this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire();
+		SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
+		SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass"));
+		strategy.setContext(context);
+		List<String> result = this.methodSecurityService
+				.preFilterByUsername(new ArrayList<>(Arrays.asList("user", "bob", "joe")));
+		assertThat(result).containsExactly("user");
+		verify(strategy).getContext();
+	}
+
+	@Test
+	public void postFilterWhenCustomSecurityContextHolderStrategyThenUses() {
+		this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire();
+		SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
+		SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass"));
+		strategy.setContext(context);
+		List<String> result = this.methodSecurityService
+				.postFilterByUsername(new ArrayList<>(Arrays.asList("user", "bob", "joe")));
+		assertThat(result).containsExactly("user");
+		verify(strategy).getContext();
+	}
+
 	@WithMockUser("bob")
 	@Test
 	public void methodReturningAListWhenPrePostFiltersConfiguredThenFiltersList() {
@@ -253,6 +304,17 @@ public class MethodSecurityBeanDefinitionParserTests {
 				.withMessage("Access Denied");
 	}
 
+	@Test
+	public void jsr250WhenCustomSecurityContextHolderStrategyThenUses() {
+		this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire();
+		SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
+		SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass"));
+		strategy.setContext(context);
+		assertThatExceptionOfType(AccessDeniedException.class)
+				.isThrownBy(this.methodSecurityService::jsr250RolesAllowed).withMessage("Access Denied");
+		verify(strategy).getContext();
+	}
+
 	@WithAnonymousUser
 	@Test
 	public void jsr250PermitAllWhenRoleAnonymousThenPasses() {

+ 34 - 0
config/src/test/resources/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests-MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2022 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      https://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<method-security secured-enabled="true" jsr250-enabled="true" security-context-holder-strategy-ref="ref"/>
+
+	<b:bean id="ref" class="org.mockito.Mockito" factory-method="spy">
+		<b:constructor-arg>
+			<b:bean class="org.springframework.security.config.MockSecurityContextHolderStrategy"/>
+		</b:constructor-arg>
+	</b:bean>
+
+	<b:bean class="org.springframework.security.config.annotation.method.configuration.MethodSecurityServiceImpl"/>
+
+</b:beans>

+ 5 - 0
docs/modules/ROOT/pages/servlet/appendix/namespace/method-security.adoc

@@ -28,6 +28,11 @@ Defaults to "false".
 If true, class based proxying will be used instead of interface based proxying.
 Defaults to "false".
 
+[[nsa-method-security-security-context-holder-strategy-ref]]
+* **security-context-holder-strategy-ref**
+Specifies a SecurityContextHolderStrategy to use when retrieving the SecurityContext.
+Defaults to the value returned by SecurityContextHolder.getContextHolderStrategy().
+
 [[nsa-method-security-children]]
 === Child Elements of <method-security>