Add SecurityContextHolderStrategy XML Configuration for Method Security

Issue gh-11061
This commit is contained in:
Josh Cummings 2022-06-23 16:26:40 -06:00
parent da57bac061
commit 9cd7c7b046
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
8 changed files with 256 additions and 55 deletions

View File

@ -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;
}
}
}

View File

@ -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.

View File

@ -124,7 +124,7 @@
</xs:annotation>
<xs:complexType/>
</xs:element>
<xs:attributeGroup name="password-encoder.attlist">
<xs:attribute name="ref" type="xs:token">
<xs:annotation>
@ -408,7 +408,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="ldap-ap.attlist">
<xs:attribute name="server-ref" type="xs:token">
<xs:annotation>
@ -488,7 +488,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="password-compare.attlist">
<xs:attribute name="password-attribute" type="xs:token">
<xs:annotation>
@ -541,7 +541,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="protect.attlist">
<xs:attribute name="method" use="required" type="xs:token">
<xs:annotation>
@ -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>
@ -842,13 +849,13 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="protect-pointcut.attlist">
<xs:attribute name="expression" use="required" type="xs:string">
<xs:annotation>
@ -1294,20 +1301,18 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="use-authorization-manager" type="xs:boolean">
<xs:annotation>
<xs:documentation>Optional attribute specifying the ID of the AccessDecisionManager implementation which
should be used for authorizing HTTP requests.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="authorization-manager-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Optional attribute specifying the ID of the AccessDecisionManager implementation which
should be used for authorizing HTTP requests.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="use-authorization-manager" type="xs:boolean">
<xs:annotation>
<xs:documentation>Use AuthorizationManager API instead of SecurityMetadataSource
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="authorization-manager-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Use this AuthorizationManager instead of deriving one from &lt;intercept-url&gt; elements
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="access-decision-manager-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Optional attribute specifying the ID of the AccessDecisionManager implementation which
@ -1356,7 +1361,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="access-denied-handler.attlist">
<xs:attribute name="ref" type="xs:token">
<xs:annotation>
@ -1381,7 +1386,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="intercept-url.attlist">
<xs:attribute name="pattern" type="xs:token">
<xs:annotation>
@ -1438,7 +1443,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="logout.attlist">
<xs:attribute name="logout-url" type="xs:token">
<xs:annotation>
@ -1485,7 +1490,7 @@
<xs:attributeGroup ref="security:ref"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="form-login.attlist">
<xs:attribute name="login-processing-url" type="xs:token">
<xs:annotation>
@ -2000,7 +2005,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="attribute-exchange">
<xs:annotation>
<xs:documentation>Sets up an attribute exchange configuration to request specified attributes from the
@ -2067,7 +2072,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="saml2-login.attlist">
<xs:attribute name="relying-party-registration-repository-ref" type="xs:token">
<xs:annotation>
@ -2124,7 +2129,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="saml2-logout.attlist">
<xs:attribute name="logout-url" type="xs:token">
<xs:annotation>
@ -2577,7 +2582,7 @@
</xs:simpleType>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="http-basic.attlist">
<xs:attribute name="entry-point-ref" type="xs:token">
<xs:annotation>
@ -2610,7 +2615,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="session-management.attlist">
<xs:attribute name="session-fixation-protection">
<xs:annotation>
@ -2666,7 +2671,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="concurrency-control.attlist">
<xs:attribute name="max-sessions" type="xs:token">
<xs:annotation>
@ -2713,7 +2718,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="remember-me.attlist">
<xs:attribute name="key" type="xs:token">
<xs:annotation>
@ -2811,7 +2816,7 @@
<xs:attributeGroup name="remember-me-data-source-ref">
<xs:attributeGroup ref="security:data-source-ref"/>
</xs:attributeGroup>
<xs:attributeGroup name="anonymous.attlist">
<xs:attribute name="key" type="xs:token">
<xs:annotation>
@ -2844,8 +2849,8 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="http-port">
<xs:attribute name="http" use="required" type="xs:token">
<xs:annotation>
@ -2862,7 +2867,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="x509.attlist">
<xs:attribute name="subject-principal-regex" type="xs:token">
<xs:annotation>
@ -2999,7 +3004,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="ap.attlist">
<xs:attribute name="ref" type="xs:token">
<xs:annotation>
@ -3051,7 +3056,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="user.attlist">
<xs:attribute name="name" use="required" type="xs:token">
<xs:annotation>
@ -3794,4 +3799,4 @@
<xs:enumeration value="LAST"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
</xs:schema>

View File

@ -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 javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.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")

View File

@ -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;

View File

@ -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() {

View File

@ -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>

View File

@ -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>