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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.w3c.dom.Element;
import org.springframework.aop.config.AopNamespaceUtils; import org.springframework.aop.config.AopNamespaceUtils;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition; 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.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionParser; 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.authorization.method.PreFilterAuthorizationMethodInterceptor;
import org.springframework.security.config.Elements; import org.springframework.security.config.Elements;
import org.springframework.security.config.core.GrantedAuthorityDefaults; 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; 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_REF = "ref";
private static final String ATT_SECURITY_CONTEXT_HOLDER_STRATEGY_REF = "security-context-holder-strategy-ref";
@Override @Override
public BeanDefinition parse(Element element, ParserContext pc) { public BeanDefinition parse(Element element, ParserContext pc) {
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(),
pc.extractSource(element)); pc.extractSource(element));
pc.pushContainingComponent(compositeDef); pc.pushContainingComponent(compositeDef);
BeanMetadataElement securityContextHolderStrategy = getSecurityContextHolderStrategy(element);
boolean prePostAnnotationsEnabled = !element.hasAttribute(ATT_USE_PREPOST) boolean prePostAnnotationsEnabled = !element.hasAttribute(ATT_USE_PREPOST)
|| "true".equals(element.getAttribute(ATT_USE_PREPOST)); || "true".equals(element.getAttribute(ATT_USE_PREPOST));
if (prePostAnnotationsEnabled) { if (prePostAnnotationsEnabled) {
BeanDefinitionBuilder preFilterInterceptor = BeanDefinitionBuilder BeanDefinitionBuilder preFilterInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(PreFilterAuthorizationMethodInterceptor.class) .rootBeanDefinition(PreFilterAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
BeanDefinitionBuilder preAuthorizeInterceptor = BeanDefinitionBuilder BeanDefinitionBuilder preAuthorizeInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(PreAuthorizeAuthorizationMethodInterceptor.class) .rootBeanDefinition(PreAuthorizeAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
BeanDefinitionBuilder postAuthorizeInterceptor = BeanDefinitionBuilder BeanDefinitionBuilder postAuthorizeInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(PostAuthorizeAuthorizationMethodInterceptor.class) .rootBeanDefinition(PostAuthorizeAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
BeanDefinitionBuilder postFilterInterceptor = BeanDefinitionBuilder BeanDefinitionBuilder postFilterInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(PostFilterAuthorizationMethodInterceptor.class) .rootBeanDefinition(PostFilterAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER); Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
if (expressionHandlerElt != null) { if (expressionHandlerElt != null) {
String expressionHandlerRef = expressionHandlerElt.getAttribute(ATT_REF); String expressionHandlerRef = expressionHandlerElt.getAttribute(ATT_REF);
@ -110,7 +122,9 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
if (securedEnabled) { if (securedEnabled) {
BeanDefinitionBuilder securedInterceptor = BeanDefinitionBuilder BeanDefinitionBuilder securedInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(AuthorizationManagerBeforeMethodInterceptor.class) .rootBeanDefinition(AuthorizationManagerBeforeMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).setFactoryMethod("secured"); .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
.setFactoryMethod("secured");
pc.getRegistry().registerBeanDefinition("securedAuthorizationMethodInterceptor", pc.getRegistry().registerBeanDefinition("securedAuthorizationMethodInterceptor",
securedInterceptor.getBeanDefinition()); securedInterceptor.getBeanDefinition());
} }
@ -118,7 +132,8 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
if (jsr250Enabled) { if (jsr250Enabled) {
BeanDefinitionBuilder jsr250Interceptor = BeanDefinitionBuilder BeanDefinitionBuilder jsr250Interceptor = BeanDefinitionBuilder
.rootBeanDefinition(Jsr250AuthorizationMethodInterceptor.class) .rootBeanDefinition(Jsr250AuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
pc.getRegistry().registerBeanDefinition("jsr250AuthorizationMethodInterceptor", pc.getRegistry().registerBeanDefinition("jsr250AuthorizationMethodInterceptor",
jsr250Interceptor.getBeanDefinition()); jsr250Interceptor.getBeanDefinition());
} }
@ -127,6 +142,14 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
return null; 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 public static final class MethodSecurityExpressionHandlerBean
implements FactoryBean<MethodSecurityExpressionHandler>, ApplicationContextAware { implements FactoryBean<MethodSecurityExpressionHandler>, ApplicationContextAware {
@ -158,11 +181,17 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
public static final class Jsr250AuthorizationMethodInterceptor public static final class Jsr250AuthorizationMethodInterceptor
implements FactoryBean<AuthorizationManagerBeforeMethodInterceptor>, ApplicationContextAware { implements FactoryBean<AuthorizationManagerBeforeMethodInterceptor>, ApplicationContextAware {
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private final Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager(); private final Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
@Override @Override
public AuthorizationManagerBeforeMethodInterceptor getObject() { public AuthorizationManagerBeforeMethodInterceptor getObject() {
return AuthorizationManagerBeforeMethodInterceptor.jsr250(this.manager); AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.jsr250(this.manager);
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
return interceptor;
} }
@Override @Override
@ -181,16 +210,26 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
} }
} }
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
} }
public static final class PreAuthorizeAuthorizationMethodInterceptor public static final class PreAuthorizeAuthorizationMethodInterceptor
implements FactoryBean<AuthorizationManagerBeforeMethodInterceptor> { implements FactoryBean<AuthorizationManagerBeforeMethodInterceptor> {
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private final PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager(); private final PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
@Override @Override
public AuthorizationManagerBeforeMethodInterceptor getObject() { public AuthorizationManagerBeforeMethodInterceptor getObject() {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(this.manager); AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.preAuthorize(this.manager);
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
return interceptor;
} }
@Override @Override
@ -198,6 +237,10 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
return AuthorizationManagerBeforeMethodInterceptor.class; return AuthorizationManagerBeforeMethodInterceptor.class;
} }
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.manager.setExpressionHandler(expressionHandler); this.manager.setExpressionHandler(expressionHandler);
} }
@ -207,11 +250,17 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
public static final class PostAuthorizeAuthorizationMethodInterceptor public static final class PostAuthorizeAuthorizationMethodInterceptor
implements FactoryBean<AuthorizationManagerAfterMethodInterceptor> { implements FactoryBean<AuthorizationManagerAfterMethodInterceptor> {
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private final PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager(); private final PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
@Override @Override
public AuthorizationManagerAfterMethodInterceptor getObject() { public AuthorizationManagerAfterMethodInterceptor getObject() {
return AuthorizationManagerAfterMethodInterceptor.postAuthorize(this.manager); AuthorizationManagerAfterMethodInterceptor interceptor = AuthorizationManagerAfterMethodInterceptor
.postAuthorize(this.manager);
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
return interceptor;
} }
@Override @Override
@ -219,10 +268,28 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
return AuthorizationManagerAfterMethodInterceptor.class; return AuthorizationManagerAfterMethodInterceptor.class;
} }
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.manager.setExpressionHandler(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 &= method-security.attlist &=
## If true, class-based proxying will be used instead of interface-based proxying. ## If true, class-based proxying will be used instead of interface-based proxying.
attribute proxy-target-class {xsd:boolean}? 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 = 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. ## 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:annotation>
<xs:complexType/> <xs:complexType/>
</xs:element> </xs:element>
<xs:attributeGroup name="password-encoder.attlist"> <xs:attributeGroup name="password-encoder.attlist">
<xs:attribute name="ref" type="xs:token"> <xs:attribute name="ref" type="xs:token">
<xs:annotation> <xs:annotation>
@ -408,7 +408,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="ldap-ap.attlist"> <xs:attributeGroup name="ldap-ap.attlist">
<xs:attribute name="server-ref" type="xs:token"> <xs:attribute name="server-ref" type="xs:token">
<xs:annotation> <xs:annotation>
@ -488,7 +488,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="password-compare.attlist"> <xs:attributeGroup name="password-compare.attlist">
<xs:attribute name="password-attribute" type="xs:token"> <xs:attribute name="password-attribute" type="xs:token">
<xs:annotation> <xs:annotation>
@ -541,7 +541,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="protect.attlist"> <xs:attributeGroup name="protect.attlist">
<xs:attribute name="method" use="required" type="xs:token"> <xs:attribute name="method" use="required" type="xs:token">
<xs:annotation> <xs:annotation>
@ -651,6 +651,13 @@
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </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:attributeGroup>
<xs:element name="global-method-security"> <xs:element name="global-method-security">
<xs:annotation> <xs:annotation>
@ -842,13 +849,13 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="protect-pointcut.attlist"> <xs:attributeGroup name="protect-pointcut.attlist">
<xs:attribute name="expression" use="required" type="xs:string"> <xs:attribute name="expression" use="required" type="xs:string">
<xs:annotation> <xs:annotation>
@ -1294,20 +1301,18 @@
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="use-authorization-manager" type="xs:boolean"> <xs:attribute name="use-authorization-manager" type="xs:boolean">
<xs:annotation> <xs:annotation>
<xs:documentation>Optional attribute specifying the ID of the AccessDecisionManager implementation which <xs:documentation>Use AuthorizationManager API instead of SecurityMetadataSource
should be used for authorizing HTTP requests. </xs:documentation>
</xs:documentation> </xs:annotation>
</xs:annotation> </xs:attribute>
</xs:attribute> <xs:attribute name="authorization-manager-ref" type="xs:token">
<xs:attribute name="authorization-manager-ref" type="xs:token"> <xs:annotation>
<xs:annotation> <xs:documentation>Use this AuthorizationManager instead of deriving one from &lt;intercept-url&gt; elements
<xs:documentation>Optional attribute specifying the ID of the AccessDecisionManager implementation which </xs:documentation>
should be used for authorizing HTTP requests. </xs:annotation>
</xs:documentation> </xs:attribute>
</xs:annotation>
</xs:attribute>
<xs:attribute name="access-decision-manager-ref" type="xs:token"> <xs:attribute name="access-decision-manager-ref" type="xs:token">
<xs:annotation> <xs:annotation>
<xs:documentation>Optional attribute specifying the ID of the AccessDecisionManager implementation which <xs:documentation>Optional attribute specifying the ID of the AccessDecisionManager implementation which
@ -1356,7 +1361,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="access-denied-handler.attlist"> <xs:attributeGroup name="access-denied-handler.attlist">
<xs:attribute name="ref" type="xs:token"> <xs:attribute name="ref" type="xs:token">
<xs:annotation> <xs:annotation>
@ -1381,7 +1386,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="intercept-url.attlist"> <xs:attributeGroup name="intercept-url.attlist">
<xs:attribute name="pattern" type="xs:token"> <xs:attribute name="pattern" type="xs:token">
<xs:annotation> <xs:annotation>
@ -1438,7 +1443,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="logout.attlist"> <xs:attributeGroup name="logout.attlist">
<xs:attribute name="logout-url" type="xs:token"> <xs:attribute name="logout-url" type="xs:token">
<xs:annotation> <xs:annotation>
@ -1485,7 +1490,7 @@
<xs:attributeGroup ref="security:ref"/> <xs:attributeGroup ref="security:ref"/>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:attributeGroup name="form-login.attlist"> <xs:attributeGroup name="form-login.attlist">
<xs:attribute name="login-processing-url" type="xs:token"> <xs:attribute name="login-processing-url" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2000,7 +2005,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:element name="attribute-exchange"> <xs:element name="attribute-exchange">
<xs:annotation> <xs:annotation>
<xs:documentation>Sets up an attribute exchange configuration to request specified attributes from the <xs:documentation>Sets up an attribute exchange configuration to request specified attributes from the
@ -2067,7 +2072,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="saml2-login.attlist"> <xs:attributeGroup name="saml2-login.attlist">
<xs:attribute name="relying-party-registration-repository-ref" type="xs:token"> <xs:attribute name="relying-party-registration-repository-ref" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2124,7 +2129,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="saml2-logout.attlist"> <xs:attributeGroup name="saml2-logout.attlist">
<xs:attribute name="logout-url" type="xs:token"> <xs:attribute name="logout-url" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2577,7 +2582,7 @@
</xs:simpleType> </xs:simpleType>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="http-basic.attlist"> <xs:attributeGroup name="http-basic.attlist">
<xs:attribute name="entry-point-ref" type="xs:token"> <xs:attribute name="entry-point-ref" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2610,7 +2615,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="session-management.attlist"> <xs:attributeGroup name="session-management.attlist">
<xs:attribute name="session-fixation-protection"> <xs:attribute name="session-fixation-protection">
<xs:annotation> <xs:annotation>
@ -2666,7 +2671,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="concurrency-control.attlist"> <xs:attributeGroup name="concurrency-control.attlist">
<xs:attribute name="max-sessions" type="xs:token"> <xs:attribute name="max-sessions" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2713,7 +2718,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="remember-me.attlist"> <xs:attributeGroup name="remember-me.attlist">
<xs:attribute name="key" type="xs:token"> <xs:attribute name="key" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2811,7 +2816,7 @@
<xs:attributeGroup name="remember-me-data-source-ref"> <xs:attributeGroup name="remember-me-data-source-ref">
<xs:attributeGroup ref="security:data-source-ref"/> <xs:attributeGroup ref="security:data-source-ref"/>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="anonymous.attlist"> <xs:attributeGroup name="anonymous.attlist">
<xs:attribute name="key" type="xs:token"> <xs:attribute name="key" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2844,8 +2849,8 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="http-port"> <xs:attributeGroup name="http-port">
<xs:attribute name="http" use="required" type="xs:token"> <xs:attribute name="http" use="required" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2862,7 +2867,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="x509.attlist"> <xs:attributeGroup name="x509.attlist">
<xs:attribute name="subject-principal-regex" type="xs:token"> <xs:attribute name="subject-principal-regex" type="xs:token">
<xs:annotation> <xs:annotation>
@ -2999,7 +3004,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="ap.attlist"> <xs:attributeGroup name="ap.attlist">
<xs:attribute name="ref" type="xs:token"> <xs:attribute name="ref" type="xs:token">
<xs:annotation> <xs:annotation>
@ -3051,7 +3056,7 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="user.attlist"> <xs:attributeGroup name="user.attlist">
<xs:attribute name="name" use="required" type="xs:token"> <xs:attribute name="name" use="required" type="xs:token">
<xs:annotation> <xs:annotation>
@ -3794,4 +3799,4 @@
<xs:enumeration value="LAST"/> <xs:enumeration value="LAST"/>
</xs:restriction> </xs:restriction>
</xs:simpleType> </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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.DenyAll;
import javax.annotation.security.PermitAll; import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PostAuthorize;
@ -49,6 +50,9 @@ public interface MethodSecurityService {
@PermitAll @PermitAll
String jsr250PermitAll(); String jsr250PermitAll();
@RolesAllowed("ADMIN")
String jsr250RolesAllowed();
@Secured({ "ROLE_USER", "RUN_AS_SUPER" }) @Secured({ "ROLE_USER", "RUN_AS_SUPER" })
Authentication runAs(); Authentication runAs();
@ -73,6 +77,12 @@ public interface MethodSecurityService {
@PostAuthorize("#o?.contains('grant')") @PostAuthorize("#o?.contains('grant')")
String postAnnotation(@P("o") String object); 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") @PreFilter("filterObject.length > 3")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@Secured("ROLE_USER") @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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; return null;
} }
@Override
public String jsr250RolesAllowed() {
return null;
}
@Override @Override
public Authentication runAs() { public Authentication runAs() {
return SecurityContextHolder.getContext().getAuthentication(); return SecurityContextHolder.getContext().getAuthentication();
@ -88,6 +93,16 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
return null; return null;
} }
@Override
public List<String> preFilterByUsername(List<String> array) {
return array;
}
@Override
public List<String> postFilterByUsername(List<String> array) {
return array;
}
@Override @Override
public List<String> manyAnnotations(List<String> object) { public List<String> manyAnnotations(List<String> object) {
return 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.AccessDeniedException;
import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.annotation.BusinessService; import org.springframework.security.access.annotation.BusinessService;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager; 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.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication; 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.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.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithAnonymousUser;
import org.springframework.security.test.context.support.WithMockUser; 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.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.verify;
/** /**
* @author Josh Cummings * @author Josh Cummings
@ -117,6 +122,17 @@ public class MethodSecurityBeanDefinitionParserTests {
assertThat(result).isNull(); 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") @WithMockUser(roles = "ADMIN")
@Test @Test
public void securedUserWhenRoleAdminThenAccessDeniedException() { public void securedUserWhenRoleAdminThenAccessDeniedException() {
@ -148,6 +164,17 @@ public class MethodSecurityBeanDefinitionParserTests {
this.methodSecurityService.preAuthorizeAdmin(); 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") @WithMockUser(authorities = "PREFIX_ADMIN")
@Test @Test
public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() { public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() {
@ -187,6 +214,30 @@ public class MethodSecurityBeanDefinitionParserTests {
assertThat(result).isNull(); 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") @WithMockUser("bob")
@Test @Test
public void methodReturningAListWhenPrePostFiltersConfiguredThenFiltersList() { public void methodReturningAListWhenPrePostFiltersConfiguredThenFiltersList() {
@ -253,6 +304,17 @@ public class MethodSecurityBeanDefinitionParserTests {
.withMessage("Access Denied"); .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 @WithAnonymousUser
@Test @Test
public void jsr250PermitAllWhenRoleAnonymousThenPasses() { 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. If true, class based proxying will be used instead of interface based proxying.
Defaults to "false". 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]] [[nsa-method-security-children]]
=== Child Elements of <method-security> === Child Elements of <method-security>