Skip to content

Commit 51475e2

Browse files
committed
Polish InterceptMethodsBeanDefinitionDecorator
Issue gh-11328
1 parent 68bdb63 commit 51475e2

File tree

4 files changed

+171
-139
lines changed

4 files changed

+171
-139
lines changed

config/src/main/java/org/springframework/security/config/method/InterceptMethodsBeanDefinitionDecorator.java

Lines changed: 5 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,14 @@
1616

1717
package org.springframework.security.config.method;
1818

19-
import java.lang.reflect.Method;
2019
import java.util.List;
2120
import java.util.Map;
22-
import java.util.function.Supplier;
2321

24-
import org.aopalliance.intercept.MethodInvocation;
2522
import org.w3c.dom.Element;
2623
import org.w3c.dom.Node;
2724

28-
import org.springframework.aop.ClassFilter;
29-
import org.springframework.aop.MethodMatcher;
3025
import org.springframework.aop.Pointcut;
3126
import org.springframework.aop.config.AbstractInterceptorDrivenBeanDefinitionDecorator;
32-
import org.springframework.aop.support.AopUtils;
33-
import org.springframework.aop.support.RootClassFilter;
3427
import org.springframework.beans.BeanMetadataElement;
3528
import org.springframework.beans.factory.config.BeanDefinition;
3629
import org.springframework.beans.factory.config.BeanDefinitionHolder;
@@ -41,24 +34,13 @@
4134
import org.springframework.beans.factory.support.RootBeanDefinition;
4235
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
4336
import org.springframework.beans.factory.xml.ParserContext;
44-
import org.springframework.expression.EvaluationContext;
45-
import org.springframework.expression.Expression;
46-
import org.springframework.expression.spel.standard.SpelExpressionParser;
4737
import org.springframework.security.access.SecurityConfig;
48-
import org.springframework.security.access.expression.ExpressionUtils;
49-
import org.springframework.security.access.expression.SecurityExpressionHandler;
50-
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
5138
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor;
5239
import org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource;
53-
import org.springframework.security.authorization.AuthorizationDecision;
54-
import org.springframework.security.authorization.AuthorizationManager;
5540
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
41+
import org.springframework.security.authorization.method.MethodExpressionAuthorizationManager;
5642
import org.springframework.security.config.BeanIds;
5743
import org.springframework.security.config.Elements;
58-
import org.springframework.security.core.Authentication;
59-
import org.springframework.security.web.access.expression.ExpressionAuthorizationDecision;
60-
import org.springframework.util.Assert;
61-
import org.springframework.util.ClassUtils;
6244
import org.springframework.util.StringUtils;
6345
import org.springframework.util.xml.DomUtils;
6446

@@ -93,8 +75,6 @@ static class InternalAuthorizationManagerInterceptMethodsBeanDefinitionDecorator
9375

9476
private static final String ATT_AUTHORIZATION_MGR = "authorization-manager-ref";
9577

96-
private final ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
97-
9878
@Override
9979
protected BeanDefinition createInterceptorDefinition(Node node) {
10080
Element interceptMethodsElt = (Element) node;
@@ -121,8 +101,8 @@ boolean supports(Node node) {
121101

122102
private Pointcut pointcut(Element interceptorElt, Element protectElt) {
123103
String method = protectElt.getAttribute(ATT_METHOD);
124-
Class<?> javaType = javaType(interceptorElt, method);
125-
return new PrefixBasedMethodMatcher(javaType, method);
104+
String parentBeanClass = ((Element) interceptorElt.getParentNode()).getAttribute("class");
105+
return PrefixBasedMethodMatcher.fromClass(parentBeanClass, method);
126106
}
127107

128108
private BeanMetadataElement authorizationManager(Element interceptMethodsElt, Element protectElt) {
@@ -131,129 +111,15 @@ private BeanMetadataElement authorizationManager(Element interceptMethodsElt, El
131111
return new RuntimeBeanReference(authorizationManager);
132112
}
133113
String access = protectElt.getAttribute(ATT_ACCESS);
134-
SpelExpressionParser parser = new SpelExpressionParser();
135-
Expression expression = parser.parseExpression(access);
136114
return BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionAuthorizationManager.class)
137-
.addConstructorArgValue(expression).getBeanDefinition();
115+
.addConstructorArgValue(access).getBeanDefinition();
138116
}
139117

140118
private BeanMetadataElement authorizationManager(Map<Pointcut, BeanMetadataElement> managers) {
141-
return BeanDefinitionBuilder.rootBeanDefinition(PointcutMatchingAuthorizationManager.class)
119+
return BeanDefinitionBuilder.rootBeanDefinition(PointcutDelegatingAuthorizationManager.class)
142120
.addConstructorArgValue(managers).getBeanDefinition();
143121
}
144122

145-
private Class<?> javaType(Element interceptMethodsElt, String method) {
146-
int lastDotIndex = method.lastIndexOf(".");
147-
String parentBeanClass = ((Element) interceptMethodsElt.getParentNode()).getAttribute("class");
148-
Assert.isTrue(lastDotIndex != -1 || StringUtils.hasText(parentBeanClass),
149-
() -> "'" + method + "' is not a valid method name: format is FQN.methodName");
150-
if (lastDotIndex == -1) {
151-
return ClassUtils.resolveClassName(parentBeanClass, this.beanClassLoader);
152-
}
153-
String methodName = method.substring(lastDotIndex + 1);
154-
Assert.hasText(methodName, () -> "Method not found for '" + method + "'");
155-
String typeName = method.substring(0, lastDotIndex);
156-
return ClassUtils.resolveClassName(typeName, this.beanClassLoader);
157-
}
158-
159-
private static class PrefixBasedMethodMatcher implements MethodMatcher, Pointcut {
160-
161-
private final ClassFilter classFilter;
162-
163-
private final String methodPrefix;
164-
165-
PrefixBasedMethodMatcher(Class<?> javaType, String methodPrefix) {
166-
this.classFilter = new RootClassFilter(javaType);
167-
this.methodPrefix = methodPrefix;
168-
}
169-
170-
@Override
171-
public ClassFilter getClassFilter() {
172-
return this.classFilter;
173-
}
174-
175-
@Override
176-
public MethodMatcher getMethodMatcher() {
177-
return this;
178-
}
179-
180-
@Override
181-
public boolean matches(Method method, Class<?> targetClass) {
182-
return matches(this.methodPrefix, method.getName());
183-
}
184-
185-
@Override
186-
public boolean isRuntime() {
187-
return false;
188-
}
189-
190-
@Override
191-
public boolean matches(Method method, Class<?> targetClass, Object... args) {
192-
return matches(this.methodPrefix, method.getName());
193-
}
194-
195-
private boolean matches(String mappedName, String methodName) {
196-
boolean equals = methodName.equals(mappedName);
197-
return equals || prefixMatches(mappedName, methodName) || suffixMatches(mappedName, methodName);
198-
}
199-
200-
private boolean prefixMatches(String mappedName, String methodName) {
201-
return mappedName.endsWith("*")
202-
&& methodName.startsWith(mappedName.substring(0, mappedName.length() - 1));
203-
}
204-
205-
private boolean suffixMatches(String mappedName, String methodName) {
206-
return mappedName.startsWith("*") && methodName.endsWith(mappedName.substring(1));
207-
}
208-
209-
}
210-
211-
private static class PointcutMatchingAuthorizationManager implements AuthorizationManager<MethodInvocation> {
212-
213-
private final Map<Pointcut, AuthorizationManager<MethodInvocation>> managers;
214-
215-
PointcutMatchingAuthorizationManager(Map<Pointcut, AuthorizationManager<MethodInvocation>> managers) {
216-
this.managers = managers;
217-
}
218-
219-
@Override
220-
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation object) {
221-
for (Map.Entry<Pointcut, AuthorizationManager<MethodInvocation>> entry : this.managers.entrySet()) {
222-
Class<?> targetClass = (object.getThis() != null) ? AopUtils.getTargetClass(object.getThis())
223-
: null;
224-
if (entry.getKey().getClassFilter().matches(targetClass)
225-
&& entry.getKey().getMethodMatcher().matches(object.getMethod(), targetClass)) {
226-
return entry.getValue().check(authentication, object);
227-
}
228-
}
229-
return new AuthorizationDecision(false);
230-
}
231-
232-
}
233-
234-
private static class MethodExpressionAuthorizationManager implements AuthorizationManager<MethodInvocation> {
235-
236-
private final Expression expression;
237-
238-
private SecurityExpressionHandler<MethodInvocation> expressionHandler = new DefaultMethodSecurityExpressionHandler();
239-
240-
MethodExpressionAuthorizationManager(Expression expression) {
241-
this.expression = expression;
242-
}
243-
244-
@Override
245-
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
246-
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, invocation);
247-
boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx);
248-
return new ExpressionAuthorizationDecision(granted, this.expression);
249-
}
250-
251-
void setExpressionHandler(SecurityExpressionHandler<MethodInvocation> expressionHandler) {
252-
this.expressionHandler = expressionHandler;
253-
}
254-
255-
}
256-
257123
}
258124

259125
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.method;
18+
19+
import java.util.Map;
20+
import java.util.function.Supplier;
21+
22+
import org.aopalliance.intercept.MethodInvocation;
23+
24+
import org.springframework.aop.Pointcut;
25+
import org.springframework.aop.support.AopUtils;
26+
import org.springframework.security.authorization.AuthorizationDecision;
27+
import org.springframework.security.authorization.AuthorizationManager;
28+
import org.springframework.security.core.Authentication;
29+
30+
class PointcutDelegatingAuthorizationManager implements AuthorizationManager<MethodInvocation> {
31+
32+
private final Map<Pointcut, AuthorizationManager<MethodInvocation>> managers;
33+
34+
PointcutDelegatingAuthorizationManager(Map<Pointcut, AuthorizationManager<MethodInvocation>> managers) {
35+
this.managers = managers;
36+
}
37+
38+
@Override
39+
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation object) {
40+
for (Map.Entry<Pointcut, AuthorizationManager<MethodInvocation>> entry : this.managers.entrySet()) {
41+
Class<?> targetClass = (object.getThis() != null) ? AopUtils.getTargetClass(object.getThis()) : null;
42+
if (entry.getKey().getClassFilter().matches(targetClass)
43+
&& entry.getKey().getMethodMatcher().matches(object.getMethod(), targetClass)) {
44+
return entry.getValue().check(authentication, object);
45+
}
46+
}
47+
return new AuthorizationDecision(false);
48+
}
49+
50+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.method;
18+
19+
import java.lang.reflect.Method;
20+
21+
import org.springframework.aop.ClassFilter;
22+
import org.springframework.aop.MethodMatcher;
23+
import org.springframework.aop.Pointcut;
24+
import org.springframework.aop.support.RootClassFilter;
25+
import org.springframework.util.Assert;
26+
import org.springframework.util.ClassUtils;
27+
import org.springframework.util.StringUtils;
28+
29+
class PrefixBasedMethodMatcher implements MethodMatcher, Pointcut {
30+
31+
private static final ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
32+
33+
private final ClassFilter classFilter;
34+
35+
private final String methodPrefix;
36+
37+
PrefixBasedMethodMatcher(Class<?> javaType, String methodPrefix) {
38+
this.classFilter = new RootClassFilter(javaType);
39+
this.methodPrefix = methodPrefix;
40+
}
41+
42+
static PrefixBasedMethodMatcher fromClass(String className, String method) {
43+
int lastDotIndex = method.lastIndexOf(".");
44+
Assert.isTrue(lastDotIndex != -1 || StringUtils.hasText(className),
45+
() -> "'" + method + "' is not a valid method name: format is FQN.methodName");
46+
if (lastDotIndex == -1) {
47+
Class<?> javaType = ClassUtils.resolveClassName(className, beanClassLoader);
48+
return new PrefixBasedMethodMatcher(javaType, method);
49+
}
50+
String methodName = method.substring(lastDotIndex + 1);
51+
Assert.hasText(methodName, () -> "Method not found for '" + method + "'");
52+
String typeName = method.substring(0, lastDotIndex);
53+
Class<?> javaType = ClassUtils.resolveClassName(typeName, beanClassLoader);
54+
return new PrefixBasedMethodMatcher(javaType, method);
55+
}
56+
57+
@Override
58+
public ClassFilter getClassFilter() {
59+
return this.classFilter;
60+
}
61+
62+
@Override
63+
public MethodMatcher getMethodMatcher() {
64+
return this;
65+
}
66+
67+
@Override
68+
public boolean matches(Method method, Class<?> targetClass) {
69+
return matches(this.methodPrefix, method.getName());
70+
}
71+
72+
@Override
73+
public boolean isRuntime() {
74+
return false;
75+
}
76+
77+
@Override
78+
public boolean matches(Method method, Class<?> targetClass, Object... args) {
79+
return matches(this.methodPrefix, method.getName());
80+
}
81+
82+
private boolean matches(String mappedName, String methodName) {
83+
boolean equals = methodName.equals(mappedName);
84+
return equals || prefixMatches(mappedName, methodName) || suffixMatches(mappedName, methodName);
85+
}
86+
87+
private boolean prefixMatches(String mappedName, String methodName) {
88+
return mappedName.endsWith("*") && methodName.startsWith(mappedName.substring(0, mappedName.length() - 1));
89+
}
90+
91+
private boolean suffixMatches(String mappedName, String methodName) {
92+
return mappedName.startsWith("*") && methodName.endsWith(mappedName.substring(1));
93+
}
94+
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.authorization.method;
18+
19+
public class MethodExpressionAuthorizationManager {
20+
21+
}

0 commit comments

Comments
 (0)