Skip to content

Commit 89423ec

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

13 files changed

+304
-37
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 (this.variables != null) {
84+
setVariables(this.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.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.security.access.expression.method;
1818

19+
import java.util.Map;
20+
1921
import org.aopalliance.intercept.MethodInvocation;
2022

2123
import org.springframework.expression.EvaluationContext;
@@ -27,6 +29,7 @@
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

+12-9
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,10 +18,10 @@
1818

1919
import java.lang.reflect.Method;
2020

21-
import reactor.util.annotation.NonNull;
22-
2321
import org.springframework.aop.support.AopUtils;
22+
import org.springframework.core.annotation.MergedAnnotation;
2423
import org.springframework.expression.Expression;
24+
import org.springframework.lang.NonNull;
2525
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
2626
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
2727
import org.springframework.security.access.prepost.PostAuthorize;
@@ -31,6 +31,7 @@
3131
* For internal use only, as this contract is likely to change.
3232
*
3333
* @author Evgeniy Cheban
34+
* @author DingHao
3435
* @since 5.8
3536
*/
3637
final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
@@ -54,19 +55,21 @@ MethodSecurityExpressionHandler getExpressionHandler() {
5455
@Override
5556
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
5657
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
57-
PostAuthorize postAuthorize = findPostAuthorizeAnnotation(specificMethod);
58+
MergedAnnotation<PostAuthorize> postAuthorize = findPostAuthorizeAnnotation(specificMethod);
5859
if (postAuthorize == null) {
5960
return ExpressionAttribute.NULL_ATTRIBUTE;
6061
}
6162
Expression postAuthorizeExpression = this.expressionHandler.getExpressionParser()
62-
.parseExpression(postAuthorize.value());
63+
.parseExpression(postAuthorize.getString(MergedAnnotation.VALUE));
64+
this.expressionHandler.setVariables(getMetaAnnotationAttribute(postAuthorize));
6365
return new ExpressionAttribute(postAuthorizeExpression);
6466
}
6567

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);
68+
private MergedAnnotation<PostAuthorize> findPostAuthorizeAnnotation(Method method) {
69+
MergedAnnotation<PostAuthorize> postAuthorize = AuthorizationAnnotationUtils.findUniqueMergedAnnotation(method,
70+
PostAuthorize.class);
71+
return (postAuthorize != null) ? postAuthorize : AuthorizationAnnotationUtils
72+
.findUniqueMergedAnnotation(method.getDeclaringClass(), PostAuthorize.class);
7073
}
7174

7275
}

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-9
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,10 +18,10 @@
1818

1919
import java.lang.reflect.Method;
2020

21-
import reactor.util.annotation.NonNull;
22-
2321
import org.springframework.aop.support.AopUtils;
22+
import org.springframework.core.annotation.MergedAnnotation;
2423
import org.springframework.expression.Expression;
24+
import org.springframework.lang.NonNull;
2525
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
2626
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
2727
import org.springframework.security.access.prepost.PreAuthorize;
@@ -31,6 +31,7 @@
3131
* For internal use only, as this contract is likely to change.
3232
*
3333
* @author Evgeniy Cheban
34+
* @author DingHao
3435
* @since 5.8
3536
*/
3637
final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
@@ -58,19 +59,21 @@ MethodSecurityExpressionHandler getExpressionHandler() {
5859
@Override
5960
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
6061
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
61-
PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod);
62+
MergedAnnotation<PreAuthorize> preAuthorize = findPreAuthorizeAnnotation(specificMethod);
6263
if (preAuthorize == null) {
6364
return ExpressionAttribute.NULL_ATTRIBUTE;
6465
}
6566
Expression preAuthorizeExpression = this.expressionHandler.getExpressionParser()
66-
.parseExpression(preAuthorize.value());
67+
.parseExpression(preAuthorize.getString(MergedAnnotation.VALUE));
68+
this.expressionHandler.setVariables(getMetaAnnotationAttribute(preAuthorize));
6769
return new ExpressionAttribute(preAuthorizeExpression);
6870
}
6971

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);
72+
private MergedAnnotation<PreAuthorize> findPreAuthorizeAnnotation(Method method) {
73+
MergedAnnotation<PreAuthorize> preAuthorize = AuthorizationAnnotationUtils.findUniqueMergedAnnotation(method,
74+
PreAuthorize.class);
75+
return (preAuthorize != null) ? preAuthorize : AuthorizationAnnotationUtils
76+
.findUniqueMergedAnnotation(method.getDeclaringClass(), PreAuthorize.class);
7477
}
7578

7679
}

0 commit comments

Comments
 (0)