Skip to content

Commit eda7acb

Browse files
committed
security meta-annotation support allowed for parameters.
Closes gh-14480
1 parent bdc0bd6 commit eda7acb

13 files changed

+307
-33
lines changed

core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -52,6 +52,7 @@
5252
*
5353
* @author Luke Taylor
5454
* @author Evgeniy Cheban
55+
* @author DingHao
5556
* @since 3.0
5657
*/
5758
public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation>
@@ -65,6 +66,8 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
6566

6667
private PermissionCacheOptimizer permissionCacheOptimizer = null;
6768

69+
private Map<String, Object> variables = null;
70+
6871
private String defaultRolePrefix = "ROLE_";
6972

7073
public DefaultMethodSecurityExpressionHandler() {
@@ -76,14 +79,14 @@ public DefaultMethodSecurityExpressionHandler() {
7679
*/
7780
@Override
7881
public StandardEvaluationContext createEvaluationContextInternal(Authentication auth, MethodInvocation mi) {
79-
return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer());
82+
return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer(), this.variables);
8083
}
8184

8285
@Override
8386
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
8487
MethodSecurityExpressionOperations root = createSecurityExpressionRoot(authentication, mi);
8588
MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(root, mi,
86-
getParameterNameDiscoverer());
89+
getParameterNameDiscoverer(), this.variables);
8790
ctx.setBeanResolver(getBeanResolver());
8891
return ctx;
8992
}
@@ -245,6 +248,11 @@ public void setReturnObject(Object returnObject, EvaluationContext ctx) {
245248
((MethodSecurityExpressionOperations) ctx.getRootObject().getValue()).setReturnObject(returnObject);
246249
}
247250

251+
@Override
252+
public void setVariables(Map<String, Object> variables) {
253+
this.variables = variables;
254+
}
255+
248256
/**
249257
* <p>
250258
* Sets the default prefix to be added to

core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContext.java

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.security.access.expression.method;
1818

1919
import java.lang.reflect.Method;
20+
import java.util.Map;
2021

2122
import org.aopalliance.intercept.MethodInvocation;
2223

@@ -35,10 +36,13 @@
3536
* @author Luke Taylor
3637
* @author Daniel Bustamante
3738
* @author Evgeniy Cheban
39+
* @author DingHao
3840
* @since 3.0
3941
*/
4042
class MethodSecurityEvaluationContext extends MethodBasedEvaluationContext {
4143

44+
private final Map<String, Object> variables;
45+
4246
/**
4347
* Intended for testing. Don't use in practice as it creates a new parameter resolver
4448
* for each instance. Use the constructor which takes the resolver, as an argument
@@ -50,16 +54,36 @@ class MethodSecurityEvaluationContext extends MethodBasedEvaluationContext {
5054

5155
MethodSecurityEvaluationContext(Authentication user, MethodInvocation mi,
5256
ParameterNameDiscoverer parameterNameDiscoverer) {
57+
this(user, mi, parameterNameDiscoverer, null);
58+
}
59+
60+
MethodSecurityEvaluationContext(Authentication user, MethodInvocation mi,
61+
ParameterNameDiscoverer parameterNameDiscoverer, Map<String, Object> variables) {
5362
super(mi.getThis(), getSpecificMethod(mi), mi.getArguments(), parameterNameDiscoverer);
63+
this.variables = variables;
5464
}
5565

5666
MethodSecurityEvaluationContext(MethodSecurityExpressionOperations root, MethodInvocation mi,
5767
ParameterNameDiscoverer parameterNameDiscoverer) {
68+
this(root, mi, parameterNameDiscoverer, null);
69+
}
70+
71+
MethodSecurityEvaluationContext(MethodSecurityExpressionOperations root, MethodInvocation mi,
72+
ParameterNameDiscoverer parameterNameDiscoverer, Map<String, Object> variables) {
5873
super(root, getSpecificMethod(mi), mi.getArguments(), parameterNameDiscoverer);
74+
this.variables = variables;
5975
}
6076

6177
private static Method getSpecificMethod(MethodInvocation mi) {
6278
return AopUtils.getMostSpecificMethod(mi.getMethod(), AopProxyUtils.ultimateTargetClass(mi.getThis()));
6379
}
6480

81+
@Override
82+
protected void lazyLoadArguments() {
83+
if (variables != null) {
84+
setVariables(variables);
85+
}
86+
super.lazyLoadArguments();
87+
}
88+
6589
}

core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionHandler.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,11 +22,14 @@
2222
import org.springframework.expression.Expression;
2323
import org.springframework.security.access.expression.SecurityExpressionHandler;
2424

25+
import java.util.Map;
26+
2527
/**
2628
* Extended expression-handler facade which adds methods which are specific to securing
2729
* method invocations.
2830
*
2931
* @author Luke Taylor
32+
* @author DingHao
3033
* @since 3.0
3134
*/
3235
public interface MethodSecurityExpressionHandler extends SecurityExpressionHandler<MethodInvocation> {
@@ -53,4 +56,12 @@ public interface MethodSecurityExpressionHandler extends SecurityExpressionHandl
5356
*/
5457
void setReturnObject(Object returnObject, EvaluationContext ctx);
5558

59+
/**
60+
* Set multiple named variables in this evaluation context to given values.
61+
* <p>
62+
* Note: the variables has a lower priority than the method parameter priority
63+
* @param variables the names and values of the variables to set
64+
*/
65+
void setVariables(Map<String, Object> variables);
66+
5667
}

core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,12 +23,14 @@
2323
import org.aopalliance.intercept.MethodInvocation;
2424

2525
import org.springframework.core.MethodClassKey;
26+
import org.springframework.core.annotation.MergedAnnotation;
2627
import org.springframework.lang.NonNull;
2728

2829
/**
2930
* For internal use only, as this contract is likely to change
3031
*
3132
* @author Evgeniy Cheban
33+
* @author DingHao
3234
*/
3335
abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute> {
3436

@@ -67,4 +69,12 @@ final T getAttribute(Method method, Class<?> targetClass) {
6769
@NonNull
6870
abstract T resolveAttribute(Method method, Class<?> targetClass);
6971

72+
Map<String, Object> getMetaAnnotationAttribute(MergedAnnotation<?> mergedAnnotation) {
73+
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
74+
if (metaSource != null) {
75+
return metaSource.asMap();
76+
}
77+
return null;
78+
}
79+
7080
}

core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java

+21
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
*
4848
* @author Josh Cummings
4949
* @author Sam Brannen
50+
* @author DingHao
5051
*/
5152
final class AuthorizationAnnotationUtils {
5253

@@ -107,6 +108,26 @@ private static <A extends Annotation> A findDistinctAnnotation(AnnotatedElement
107108
};
108109
}
109110

111+
static <A extends Annotation> MergedAnnotation<A> findUniqueMergedAnnotation(AnnotatedElement annotatedElement,
112+
Class<A> annotationType) {
113+
MergedAnnotations mergedAnnotations = MergedAnnotations.from(annotatedElement,
114+
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
115+
116+
List<MergedAnnotation<A>> annotations = mergedAnnotations.stream(annotationType)
117+
.map(MergedAnnotation::withNonMergedAttributes)
118+
.distinct()
119+
.toList();
120+
121+
return switch (annotations.size()) {
122+
case 0 -> null;
123+
case 1 -> annotations.get(0);
124+
default -> throw new AnnotationConfigurationException("""
125+
Please ensure there is one unique annotation of type @%s attributed to %s. \
126+
Found %d competing annotations: %s""".formatted(annotationType.getName(), annotatedElement,
127+
annotations.size(), annotations));
128+
};
129+
}
130+
110131
private AuthorizationAnnotationUtils() {
111132

112133
}

core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import java.lang.reflect.Method;
2020

21+
import org.springframework.core.annotation.MergedAnnotation;
2122
import reactor.util.annotation.NonNull;
2223

2324
import org.springframework.aop.support.AopUtils;
@@ -31,6 +32,7 @@
3132
* For internal use only, as this contract is likely to change.
3233
*
3334
* @author Evgeniy Cheban
35+
* @author DingHao
3436
* @since 5.8
3537
*/
3638
final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
@@ -54,19 +56,21 @@ MethodSecurityExpressionHandler getExpressionHandler() {
5456
@Override
5557
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
5658
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
57-
PostAuthorize postAuthorize = findPostAuthorizeAnnotation(specificMethod);
59+
MergedAnnotation<PostAuthorize> postAuthorize = findPostAuthorizeAnnotation(specificMethod);
5860
if (postAuthorize == null) {
5961
return ExpressionAttribute.NULL_ATTRIBUTE;
6062
}
6163
Expression postAuthorizeExpression = this.expressionHandler.getExpressionParser()
62-
.parseExpression(postAuthorize.value());
64+
.parseExpression(postAuthorize.getString(MergedAnnotation.VALUE));
65+
this.expressionHandler.setVariables(getMetaAnnotationAttribute(postAuthorize));
6366
return new ExpressionAttribute(postAuthorizeExpression);
6467
}
6568

66-
private PostAuthorize findPostAuthorizeAnnotation(Method method) {
67-
PostAuthorize postAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostAuthorize.class);
68-
return (postAuthorize != null) ? postAuthorize
69-
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostAuthorize.class);
69+
private MergedAnnotation<PostAuthorize> findPostAuthorizeAnnotation(Method method) {
70+
MergedAnnotation<PostAuthorize> postAuthorize = AuthorizationAnnotationUtils.findUniqueMergedAnnotation(method,
71+
PostAuthorize.class);
72+
return (postAuthorize != null) ? postAuthorize : AuthorizationAnnotationUtils
73+
.findUniqueMergedAnnotation(method.getDeclaringClass(), PostAuthorize.class);
7074
}
7175

7276
}

core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.lang.reflect.Method;
2020

2121
import org.springframework.aop.support.AopUtils;
22+
import org.springframework.core.annotation.MergedAnnotation;
2223
import org.springframework.expression.Expression;
2324
import org.springframework.lang.NonNull;
2425
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
@@ -30,6 +31,7 @@
3031
* For internal use only, as this contract is likely to change.
3132
*
3233
* @author Evgeniy Cheban
34+
* @author DingHao
3335
* @since 5.8
3436
*/
3537
final class PostFilterExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
@@ -53,19 +55,21 @@ MethodSecurityExpressionHandler getExpressionHandler() {
5355
@Override
5456
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
5557
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
56-
PostFilter postFilter = findPostFilterAnnotation(specificMethod);
58+
MergedAnnotation<PostFilter> postFilter = findPostFilterAnnotation(specificMethod);
5759
if (postFilter == null) {
5860
return ExpressionAttribute.NULL_ATTRIBUTE;
5961
}
6062
Expression postFilterExpression = this.expressionHandler.getExpressionParser()
61-
.parseExpression(postFilter.value());
63+
.parseExpression(postFilter.getString(MergedAnnotation.VALUE));
64+
this.expressionHandler.setVariables(getMetaAnnotationAttribute(postFilter));
6265
return new ExpressionAttribute(postFilterExpression);
6366
}
6467

65-
private PostFilter findPostFilterAnnotation(Method method) {
66-
PostFilter postFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostFilter.class);
68+
private MergedAnnotation<PostFilter> findPostFilterAnnotation(Method method) {
69+
MergedAnnotation<PostFilter> postFilter = AuthorizationAnnotationUtils.findUniqueMergedAnnotation(method,
70+
PostFilter.class);
6771
return (postFilter != null) ? postFilter
68-
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostFilter.class);
72+
: AuthorizationAnnotationUtils.findUniqueMergedAnnotation(method.getDeclaringClass(), PostFilter.class);
6973
}
7074

7175
}

core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java

+12-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,7 +17,9 @@
1717
package org.springframework.security.authorization.method;
1818

1919
import java.lang.reflect.Method;
20+
import java.util.Map;
2021

22+
import org.springframework.core.annotation.MergedAnnotation;
2123
import reactor.util.annotation.NonNull;
2224

2325
import org.springframework.aop.support.AopUtils;
@@ -31,6 +33,7 @@
3133
* For internal use only, as this contract is likely to change.
3234
*
3335
* @author Evgeniy Cheban
36+
* @author DingHao
3437
* @since 5.8
3538
*/
3639
final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
@@ -58,19 +61,21 @@ MethodSecurityExpressionHandler getExpressionHandler() {
5861
@Override
5962
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
6063
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
61-
PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod);
64+
MergedAnnotation<PreAuthorize> preAuthorize = findPreAuthorizeAnnotation(specificMethod);
6265
if (preAuthorize == null) {
6366
return ExpressionAttribute.NULL_ATTRIBUTE;
6467
}
6568
Expression preAuthorizeExpression = this.expressionHandler.getExpressionParser()
66-
.parseExpression(preAuthorize.value());
69+
.parseExpression(preAuthorize.getString(MergedAnnotation.VALUE));
70+
this.expressionHandler.setVariables(getMetaAnnotationAttribute(preAuthorize));
6771
return new ExpressionAttribute(preAuthorizeExpression);
6872
}
6973

70-
private PreAuthorize findPreAuthorizeAnnotation(Method method) {
71-
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
72-
return (preAuthorize != null) ? preAuthorize
73-
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreAuthorize.class);
74+
private MergedAnnotation<PreAuthorize> findPreAuthorizeAnnotation(Method method) {
75+
MergedAnnotation<PreAuthorize> preAuthorize = AuthorizationAnnotationUtils.findUniqueMergedAnnotation(method,
76+
PreAuthorize.class);
77+
return (preAuthorize != null) ? preAuthorize : AuthorizationAnnotationUtils
78+
.findUniqueMergedAnnotation(method.getDeclaringClass(), PreAuthorize.class);
7479
}
7580

7681
}

0 commit comments

Comments
 (0)