Skip to content

Commit 8d914ef

Browse files
Add @AuthorizationDeniedHandler for Method Authorization Denied Handling
Issue gh-14601
1 parent 75197ca commit 8d914ef

File tree

9 files changed

+185
-99
lines changed

9 files changed

+185
-99
lines changed

config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.security.access.prepost.PreAuthorize;
4040
import org.springframework.security.access.prepost.PreFilter;
4141
import org.springframework.security.authorization.AuthorizationResult;
42+
import org.springframework.security.authorization.method.AuthorizationDeniedHandler;
4243
import org.springframework.security.authorization.method.AuthorizeReturnObject;
4344
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
4445
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
@@ -127,54 +128,67 @@ public interface MethodSecurityService {
127128
@RequireAdminRole
128129
void repeatedAnnotations();
129130

130-
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
131+
@PreAuthorize("hasRole('ADMIN')")
132+
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
131133
String preAuthorizeGetCardNumberIfAdmin(String cardNumber);
132134

133-
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StartMaskingHandlerChild.class)
135+
@PreAuthorize("hasRole('ADMIN')")
136+
@AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class)
134137
String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
135138

136-
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
139+
@PreAuthorize("hasRole('ADMIN')")
140+
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
137141
String preAuthorizeThrowAccessDeniedManually();
138142

139-
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = CardNumberMaskingPostProcessor.class)
143+
@PostAuthorize("hasRole('ADMIN')")
144+
@AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class)
140145
String postAuthorizeGetCardNumberIfAdmin(String cardNumber);
141146

142-
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = PostMaskingPostProcessor.class)
147+
@PostAuthorize("hasRole('ADMIN')")
148+
@AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class)
143149
String postAuthorizeThrowAccessDeniedManually();
144150

145-
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
151+
@PreAuthorize("denyAll()")
146152
@Mask("methodmask")
153+
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
147154
String preAuthorizeDeniedMethodWithMaskAnnotation();
148155

149-
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
156+
@PreAuthorize("denyAll()")
157+
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
150158
String preAuthorizeDeniedMethodWithNoMaskAnnotation();
151159

152160
@NullDenied(role = "ADMIN")
153161
String postAuthorizeDeniedWithNullDenied();
154162

155-
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
163+
@PostAuthorize("denyAll()")
156164
@Mask("methodmask")
165+
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
157166
String postAuthorizeDeniedMethodWithMaskAnnotation();
158167

159-
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
168+
@PostAuthorize("denyAll()")
169+
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
160170
String postAuthorizeDeniedMethodWithNoMaskAnnotation();
161171

162-
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = MaskAnnotationHandler.class)
172+
@PreAuthorize("hasRole('ADMIN')")
163173
@Mask(expression = "@myMasker.getMask()")
174+
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
164175
String preAuthorizeWithMaskAnnotationUsingBean();
165176

166-
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = MaskAnnotationPostProcessor.class)
177+
@PostAuthorize("hasRole('ADMIN')")
167178
@Mask(expression = "@myMasker.getMask(returnObject)")
179+
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
168180
String postAuthorizeWithMaskAnnotationUsingBean();
169181

170182
@AuthorizeReturnObject
171183
UserRecordWithEmailProtected getUserRecordWithEmailProtected();
172184

173-
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = UserFallbackDeniedHandler.class)
185+
@PreAuthorize("hasRole('ADMIN')")
186+
@AuthorizationDeniedHandler(handlerClass = UserFallbackDeniedHandler.class)
174187
UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized();
175188

176-
@PreAuthorize(value = "@authz.checkResult(#result)", handlerClass = MethodAuthorizationDeniedHandler.class)
177-
@PostAuthorize(value = "@authz.checkResult(!#result)",
189+
@PreAuthorize("@authz.checkResult(#result)")
190+
@PostAuthorize("@authz.checkResult(!#result)")
191+
@AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class,
178192
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
179193
String checkCustomResult(boolean result);
180194

@@ -305,7 +319,8 @@ public Object postProcessResult(MethodInvocationResult methodInvocationResult,
305319
@Target({ ElementType.METHOD, ElementType.TYPE })
306320
@Retention(RetentionPolicy.RUNTIME)
307321
@Inherited
308-
@PostAuthorize(value = "hasRole('{role}')", postProcessorClass = NullPostProcessor.class)
322+
@PostAuthorize("hasRole('{role}')")
323+
@AuthorizationDeniedHandler(postProcessorClass = NullPostProcessor.class)
309324
@interface NullDenied {
310325

311326
String role();

config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.security.access.prepost.PostAuthorize;
3434
import org.springframework.security.access.prepost.PreAuthorize;
3535
import org.springframework.security.authorization.AuthorizationResult;
36+
import org.springframework.security.authorization.method.AuthorizationDeniedHandler;
3637
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
3738
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
3839
import org.springframework.security.authorization.method.MethodInvocationResult;
@@ -45,48 +46,60 @@
4546
@ReactiveMethodSecurityService.Mask("classmask")
4647
public interface ReactiveMethodSecurityService {
4748

48-
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
49+
@PreAuthorize("hasRole('ADMIN')")
50+
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
4951
Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber);
5052

51-
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StartMaskingHandlerChild.class)
53+
@PreAuthorize("hasRole('ADMIN')")
54+
@AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class)
5255
Mono<String> preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
5356

54-
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
57+
@PreAuthorize("hasRole('ADMIN')")
58+
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
5559
Mono<String> preAuthorizeThrowAccessDeniedManually();
5660

57-
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = CardNumberMaskingPostProcessor.class)
61+
@PostAuthorize("hasRole('ADMIN')")
62+
@AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class)
5863
Mono<String> postAuthorizeGetCardNumberIfAdmin(String cardNumber);
5964

60-
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = PostMaskingPostProcessor.class)
65+
@PostAuthorize("hasRole('ADMIN')")
66+
@AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class)
6167
Mono<String> postAuthorizeThrowAccessDeniedManually();
6268

63-
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
69+
@PreAuthorize("denyAll()")
6470
@Mask("methodmask")
71+
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
6572
Mono<String> preAuthorizeDeniedMethodWithMaskAnnotation();
6673

67-
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
74+
@PreAuthorize("denyAll()")
75+
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
6876
Mono<String> preAuthorizeDeniedMethodWithNoMaskAnnotation();
6977

7078
@NullDenied(role = "ADMIN")
7179
Mono<String> postAuthorizeDeniedWithNullDenied();
7280

73-
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
81+
@PostAuthorize("denyAll()")
7482
@Mask("methodmask")
83+
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
7584
Mono<String> postAuthorizeDeniedMethodWithMaskAnnotation();
7685

77-
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
86+
@PostAuthorize("denyAll()")
87+
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
7888
Mono<String> postAuthorizeDeniedMethodWithNoMaskAnnotation();
7989

80-
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = MaskAnnotationHandler.class)
90+
@PreAuthorize("hasRole('ADMIN')")
8191
@Mask(expression = "@myMasker.getMask()")
92+
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
8293
Mono<String> preAuthorizeWithMaskAnnotationUsingBean();
8394

84-
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = MaskAnnotationPostProcessor.class)
95+
@PostAuthorize("hasRole('ADMIN')")
8596
@Mask(expression = "@myMasker.getMask(returnObject)")
97+
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
8698
Mono<String> postAuthorizeWithMaskAnnotationUsingBean();
8799

88-
@PreAuthorize(value = "@authz.checkReactiveResult(#result)", handlerClass = MethodAuthorizationDeniedHandler.class)
89-
@PostAuthorize(value = "@authz.checkReactiveResult(!#result)",
100+
@PreAuthorize("@authz.checkReactiveResult(#result)")
101+
@PostAuthorize("@authz.checkReactiveResult(!#result)")
102+
@AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class,
90103
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
91104
Mono<String> checkCustomResult(boolean result);
92105

@@ -217,7 +230,8 @@ public Object postProcessResult(MethodInvocationResult methodInvocationResult,
217230
@Target({ ElementType.METHOD, ElementType.TYPE })
218231
@Retention(RetentionPolicy.RUNTIME)
219232
@Inherited
220-
@PostAuthorize(value = "hasRole('{value}')", postProcessorClass = NullPostProcessor.class)
233+
@PostAuthorize("hasRole('{value}')")
234+
@AuthorizationDeniedHandler(postProcessorClass = NullPostProcessor.class)
221235
@interface NullDenied {
222236

223237
String role();

config/src/test/java/org/springframework/security/config/annotation/method/configuration/UserRecordWithEmailProtected.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.springframework.security.access.prepost.PostAuthorize;
2020
import org.springframework.security.authorization.AuthorizationResult;
21+
import org.springframework.security.authorization.method.AuthorizationDeniedHandler;
2122
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
2223
import org.springframework.security.authorization.method.MethodInvocationResult;
2324

@@ -36,7 +37,8 @@ public String name() {
3637
return this.name;
3738
}
3839

39-
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = EmailMaskingPostProcessor.class)
40+
@PostAuthorize("hasRole('ADMIN')")
41+
@AuthorizationDeniedHandler(postProcessorClass = EmailMaskingPostProcessor.class)
4042
public String email() {
4143
return this.email;
4244
}

core/src/main/java/org/springframework/security/access/prepost/PostAuthorize.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@
2323
import java.lang.annotation.RetentionPolicy;
2424
import java.lang.annotation.Target;
2525

26-
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
27-
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
28-
2926
/**
3027
* Annotation for specifying a method access-control expression which will be evaluated
3128
* after a method has been invoked.
@@ -45,10 +42,4 @@
4542
*/
4643
String value();
4744

48-
/**
49-
* @return the {@link MethodAuthorizationDeniedPostProcessor} class used to
50-
* post-process access denied
51-
*/
52-
Class<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass() default ThrowingMethodAuthorizationDeniedPostProcessor.class;
53-
5445
}

core/src/main/java/org/springframework/security/access/prepost/PreAuthorize.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@
2323
import java.lang.annotation.RetentionPolicy;
2424
import java.lang.annotation.Target;
2525

26-
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
27-
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
28-
2926
/**
3027
* Annotation for specifying a method access-control expression which will be evaluated to
3128
* decide whether a method invocation is allowed or not.
@@ -45,10 +42,4 @@
4542
*/
4643
String value();
4744

48-
/**
49-
* @return the {@link MethodAuthorizationDeniedHandler} class used to handle access
50-
* denied
51-
*/
52-
Class<? extends MethodAuthorizationDeniedHandler> handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class;
53-
5445
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2002-2024 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+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Inherited;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
/**
27+
* Annotation for specifying handling behavior when an authorization denied happens in
28+
* method security
29+
*
30+
* @author Marcus da Coregio
31+
* @since 6.3
32+
* @see org.springframework.security.access.prepost.PreAuthorize
33+
* @see org.springframework.security.access.prepost.PostAuthorize
34+
*/
35+
@Target({ ElementType.METHOD, ElementType.TYPE })
36+
@Retention(RetentionPolicy.RUNTIME)
37+
@Inherited
38+
@Documented
39+
public @interface AuthorizationDeniedHandler {
40+
41+
/**
42+
* The {@link MethodAuthorizationDeniedHandler} used to handle denied authorizations
43+
* from {@link org.springframework.security.access.prepost.PreAuthorize}
44+
* @return
45+
*/
46+
Class<? extends MethodAuthorizationDeniedHandler> handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class;
47+
48+
/**
49+
* The {@link MethodAuthorizationDeniedPostProcessor} used to post process denied
50+
* authorizations from
51+
* {@link org.springframework.security.access.prepost.PostAuthorize}
52+
* @return
53+
*/
54+
Class<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass() default ThrowingMethodAuthorizationDeniedPostProcessor.class;
55+
56+
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,24 @@ ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
5555
return ExpressionAttribute.NULL_ATTRIBUTE;
5656
}
5757
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value());
58-
MethodAuthorizationDeniedPostProcessor postProcessor = this.postProcessorResolver
59-
.apply(postAuthorize.postProcessorClass());
58+
MethodAuthorizationDeniedPostProcessor postProcessor = resolvePostProcessor(method, targetClass);
6059
return new PostAuthorizeExpressionAttribute(expression, postProcessor);
6160
}
6261

62+
private MethodAuthorizationDeniedPostProcessor resolvePostProcessor(Method method, Class<?> targetClass) {
63+
Function<AnnotatedElement, AuthorizationDeniedHandler> lookup = AuthorizationAnnotationUtils
64+
.withDefaults(AuthorizationDeniedHandler.class);
65+
AuthorizationDeniedHandler deniedHandler = lookup.apply(method);
66+
if (deniedHandler != null) {
67+
return this.postProcessorResolver.apply(deniedHandler.postProcessorClass());
68+
}
69+
deniedHandler = lookup.apply(targetClass(method, targetClass));
70+
if (deniedHandler != null) {
71+
return this.postProcessorResolver.apply(deniedHandler.postProcessorClass());
72+
}
73+
return this.defaultPostProcessor;
74+
}
75+
6376
private PostAuthorize findPostAuthorizeAnnotation(Method method, Class<?> targetClass) {
6477
Function<AnnotatedElement, PostAuthorize> lookup = findUniqueAnnotation(PostAuthorize.class);
6578
PostAuthorize postAuthorize = lookup.apply(method);

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,24 @@ ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
5555
return ExpressionAttribute.NULL_ATTRIBUTE;
5656
}
5757
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(preAuthorize.value());
58-
MethodAuthorizationDeniedHandler handler = this.handlerResolver.apply(preAuthorize.handlerClass());
58+
MethodAuthorizationDeniedHandler handler = resolveHandler(method, targetClass);
5959
return new PreAuthorizeExpressionAttribute(expression, handler);
6060
}
6161

62+
private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) {
63+
Function<AnnotatedElement, AuthorizationDeniedHandler> lookup = AuthorizationAnnotationUtils
64+
.withDefaults(AuthorizationDeniedHandler.class);
65+
AuthorizationDeniedHandler deniedHandler = lookup.apply(method);
66+
if (deniedHandler != null) {
67+
return this.handlerResolver.apply(deniedHandler.handlerClass());
68+
}
69+
deniedHandler = lookup.apply(targetClass(method, targetClass));
70+
if (deniedHandler != null) {
71+
return this.handlerResolver.apply(deniedHandler.handlerClass());
72+
}
73+
return this.defaultHandler;
74+
}
75+
6276
private PreAuthorize findPreAuthorizeAnnotation(Method method, Class<?> targetClass) {
6377
Function<AnnotatedElement, PreAuthorize> lookup = findUniqueAnnotation(PreAuthorize.class);
6478
PreAuthorize preAuthorize = lookup.apply(method);

0 commit comments

Comments
 (0)