|
@@ -1,5 +1,5 @@
|
|
|
/*
|
|
|
- * Copyright 2002-2016 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.
|
|
@@ -16,13 +16,22 @@
|
|
|
|
|
|
package org.springframework.security.config.method;
|
|
|
|
|
|
+import java.lang.reflect.Method;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.function.Supplier;
|
|
|
|
|
|
+import org.aopalliance.intercept.MethodInvocation;
|
|
|
import org.w3c.dom.Element;
|
|
|
import org.w3c.dom.Node;
|
|
|
|
|
|
+import org.springframework.aop.ClassFilter;
|
|
|
+import org.springframework.aop.MethodMatcher;
|
|
|
+import org.springframework.aop.Pointcut;
|
|
|
import org.springframework.aop.config.AbstractInterceptorDrivenBeanDefinitionDecorator;
|
|
|
+import org.springframework.aop.support.AopUtils;
|
|
|
+import org.springframework.aop.support.RootClassFilter;
|
|
|
+import org.springframework.beans.BeanMetadataElement;
|
|
|
import org.springframework.beans.factory.config.BeanDefinition;
|
|
|
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
|
|
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
|
@@ -32,11 +41,24 @@ import org.springframework.beans.factory.support.ManagedMap;
|
|
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
|
|
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
|
|
|
import org.springframework.beans.factory.xml.ParserContext;
|
|
|
+import org.springframework.expression.EvaluationContext;
|
|
|
+import org.springframework.expression.Expression;
|
|
|
+import org.springframework.expression.spel.standard.SpelExpressionParser;
|
|
|
import org.springframework.security.access.SecurityConfig;
|
|
|
+import org.springframework.security.access.expression.ExpressionUtils;
|
|
|
+import org.springframework.security.access.expression.SecurityExpressionHandler;
|
|
|
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
|
|
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor;
|
|
|
import org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource;
|
|
|
+import org.springframework.security.authorization.AuthorizationDecision;
|
|
|
+import org.springframework.security.authorization.AuthorizationManager;
|
|
|
+import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
|
|
import org.springframework.security.config.BeanIds;
|
|
|
import org.springframework.security.config.Elements;
|
|
|
+import org.springframework.security.core.Authentication;
|
|
|
+import org.springframework.security.web.access.expression.ExpressionAuthorizationDecision;
|
|
|
+import org.springframework.util.Assert;
|
|
|
+import org.springframework.util.ClassUtils;
|
|
|
import org.springframework.util.StringUtils;
|
|
|
import org.springframework.util.xml.DomUtils;
|
|
|
|
|
@@ -68,9 +90,76 @@ public class InterceptMethodsBeanDefinitionDecorator implements BeanDefinitionDe
|
|
|
|
|
|
private static final String ATT_ACCESS_MGR = "access-decision-manager-ref";
|
|
|
|
|
|
+ private static final String ATT_USE_AUTHORIZATION_MGR = "use-authorization-manager";
|
|
|
+
|
|
|
+ private static final String ATT_AUTHORIZATION_MGR = "authorization-manager-ref";
|
|
|
+
|
|
|
+ private final ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
|
|
|
+
|
|
|
@Override
|
|
|
protected BeanDefinition createInterceptorDefinition(Node node) {
|
|
|
Element interceptMethodsElt = (Element) node;
|
|
|
+ if (Boolean.parseBoolean(interceptMethodsElt.getAttribute(ATT_USE_AUTHORIZATION_MGR))) {
|
|
|
+ return createAuthorizationManagerInterceptorDefinition(interceptMethodsElt);
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(interceptMethodsElt.getAttribute(ATT_AUTHORIZATION_MGR))) {
|
|
|
+ return createAuthorizationManagerInterceptorDefinition(interceptMethodsElt);
|
|
|
+ }
|
|
|
+ return createMethodSecurityInterceptorDefinition(interceptMethodsElt);
|
|
|
+ }
|
|
|
+
|
|
|
+ private BeanDefinition createAuthorizationManagerInterceptorDefinition(Element interceptMethodsElt) {
|
|
|
+ BeanDefinitionBuilder interceptor = BeanDefinitionBuilder
|
|
|
+ .rootBeanDefinition(AuthorizationManagerBeforeMethodInterceptor.class);
|
|
|
+ interceptor.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
|
|
|
+ Map<Pointcut, BeanMetadataElement> managers = new ManagedMap<>();
|
|
|
+ List<Element> methods = DomUtils.getChildElementsByTagName(interceptMethodsElt, Elements.PROTECT);
|
|
|
+ for (Element protectElt : methods) {
|
|
|
+ managers.put(pointcut(interceptMethodsElt, protectElt),
|
|
|
+ authorizationManager(interceptMethodsElt, protectElt));
|
|
|
+ }
|
|
|
+ return interceptor.addConstructorArgValue(Pointcut.TRUE)
|
|
|
+ .addConstructorArgValue(authorizationManager(managers)).getBeanDefinition();
|
|
|
+ }
|
|
|
+
|
|
|
+ private Pointcut pointcut(Element interceptorElt, Element protectElt) {
|
|
|
+ String method = protectElt.getAttribute(ATT_METHOD);
|
|
|
+ Class<?> javaType = javaType(interceptorElt, method);
|
|
|
+ return new PrefixBasedMethodMatcher(javaType, method);
|
|
|
+ }
|
|
|
+
|
|
|
+ private BeanMetadataElement authorizationManager(Element interceptMethodsElt, Element protectElt) {
|
|
|
+ String authorizationManager = interceptMethodsElt.getAttribute(ATT_AUTHORIZATION_MGR);
|
|
|
+ if (StringUtils.hasText(authorizationManager)) {
|
|
|
+ return new RuntimeBeanReference(authorizationManager);
|
|
|
+ }
|
|
|
+ String access = protectElt.getAttribute(ATT_ACCESS);
|
|
|
+ SpelExpressionParser parser = new SpelExpressionParser();
|
|
|
+ Expression expression = parser.parseExpression(access);
|
|
|
+ return BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionAuthorizationManager.class)
|
|
|
+ .addConstructorArgValue(expression).getBeanDefinition();
|
|
|
+ }
|
|
|
+
|
|
|
+ private BeanMetadataElement authorizationManager(Map<Pointcut, BeanMetadataElement> managers) {
|
|
|
+ return BeanDefinitionBuilder.rootBeanDefinition(PointcutMatchingAuthorizationManager.class)
|
|
|
+ .addConstructorArgValue(managers).getBeanDefinition();
|
|
|
+ }
|
|
|
+
|
|
|
+ private Class<?> javaType(Element interceptMethodsElt, String method) {
|
|
|
+ int lastDotIndex = method.lastIndexOf(".");
|
|
|
+ String parentBeanClass = ((Element) interceptMethodsElt.getParentNode()).getAttribute("class");
|
|
|
+ Assert.isTrue(lastDotIndex != -1 || StringUtils.hasText(parentBeanClass),
|
|
|
+ () -> "'" + method + "' is not a valid method name: format is FQN.methodName");
|
|
|
+ if (lastDotIndex == -1) {
|
|
|
+ return ClassUtils.resolveClassName(parentBeanClass, this.beanClassLoader);
|
|
|
+ }
|
|
|
+ String methodName = method.substring(lastDotIndex + 1);
|
|
|
+ Assert.hasText(methodName, () -> "Method not found for '" + method + "'");
|
|
|
+ String typeName = method.substring(0, lastDotIndex);
|
|
|
+ return ClassUtils.resolveClassName(typeName, this.beanClassLoader);
|
|
|
+ }
|
|
|
+
|
|
|
+ private BeanDefinition createMethodSecurityInterceptorDefinition(Element interceptMethodsElt) {
|
|
|
BeanDefinitionBuilder interceptor = BeanDefinitionBuilder
|
|
|
.rootBeanDefinition(MethodSecurityInterceptor.class);
|
|
|
// Default to autowiring to pick up after invocation mgr
|
|
@@ -83,7 +172,7 @@ public class InterceptMethodsBeanDefinitionDecorator implements BeanDefinitionDe
|
|
|
interceptor.addPropertyValue("authenticationManager",
|
|
|
new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER));
|
|
|
// Lookup parent bean information
|
|
|
- String parentBeanClass = ((Element) node.getParentNode()).getAttribute("class");
|
|
|
+ String parentBeanClass = ((Element) interceptMethodsElt.getParentNode()).getAttribute("class");
|
|
|
// Parse the included methods
|
|
|
List<Element> methods = DomUtils.getChildElementsByTagName(interceptMethodsElt, Elements.PROTECT);
|
|
|
Map<String, BeanDefinition> mappings = new ManagedMap<>();
|
|
@@ -108,4 +197,103 @@ public class InterceptMethodsBeanDefinitionDecorator implements BeanDefinitionDe
|
|
|
|
|
|
}
|
|
|
|
|
|
+ private static class PrefixBasedMethodMatcher implements MethodMatcher, Pointcut {
|
|
|
+
|
|
|
+ private final ClassFilter classFilter;
|
|
|
+
|
|
|
+ private final Class<?> javaType;
|
|
|
+
|
|
|
+ private final String methodPrefix;
|
|
|
+
|
|
|
+ PrefixBasedMethodMatcher(Class<?> javaType, String methodPrefix) {
|
|
|
+ this.classFilter = new RootClassFilter(javaType);
|
|
|
+ this.javaType = javaType;
|
|
|
+ this.methodPrefix = methodPrefix;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ClassFilter getClassFilter() {
|
|
|
+ return this.classFilter;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public MethodMatcher getMethodMatcher() {
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean matches(Method method, Class<?> targetClass) {
|
|
|
+ return matches(this.methodPrefix, method.getName());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isRuntime() {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean matches(Method method, Class<?> targetClass, Object... args) {
|
|
|
+ return matches(this.methodPrefix, method.getName());
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean matches(String mappedName, String methodName) {
|
|
|
+ boolean equals = methodName.equals(mappedName);
|
|
|
+ return equals || prefixMatches(mappedName, methodName) || suffixMatches(mappedName, methodName);
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean prefixMatches(String mappedName, String methodName) {
|
|
|
+ return mappedName.endsWith("*") && methodName.startsWith(mappedName.substring(0, mappedName.length() - 1));
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean suffixMatches(String mappedName, String methodName) {
|
|
|
+ return mappedName.startsWith("*") && methodName.endsWith(mappedName.substring(1));
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class PointcutMatchingAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
|
|
+
|
|
|
+ private final Map<Pointcut, AuthorizationManager<MethodInvocation>> managers;
|
|
|
+
|
|
|
+ PointcutMatchingAuthorizationManager(Map<Pointcut, AuthorizationManager<MethodInvocation>> managers) {
|
|
|
+ this.managers = managers;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation object) {
|
|
|
+ for (Map.Entry<Pointcut, AuthorizationManager<MethodInvocation>> entry : this.managers.entrySet()) {
|
|
|
+ Class<?> targetClass = (object.getThis() != null) ? AopUtils.getTargetClass(object.getThis()) : null;
|
|
|
+ if (entry.getKey().getClassFilter().matches(targetClass)
|
|
|
+ && entry.getKey().getMethodMatcher().matches(object.getMethod(), targetClass)) {
|
|
|
+ return entry.getValue().check(authentication, object);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return new AuthorizationDecision(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class MethodExpressionAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
|
|
+
|
|
|
+ private final Expression expression;
|
|
|
+
|
|
|
+ private SecurityExpressionHandler<MethodInvocation> expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
|
|
+
|
|
|
+ MethodExpressionAuthorizationManager(Expression expression) {
|
|
|
+ this.expression = expression;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
|
|
|
+ EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, invocation);
|
|
|
+ boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx);
|
|
|
+ return new ExpressionAuthorizationDecision(granted, this.expression);
|
|
|
+ }
|
|
|
+
|
|
|
+ void setExpressionHandler(SecurityExpressionHandler<MethodInvocation> expressionHandler) {
|
|
|
+ this.expressionHandler = expressionHandler;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
}
|