Add @AuthorizationDeniedHandler for Method Authorization Denied Handling

Issue gh-14601
This commit is contained in:
Marcus Hert Da Coregio 2024-04-05 15:44:45 -03:00
parent 75197ca531
commit 8d914ef145
9 changed files with 185 additions and 99 deletions

View File

@ -39,6 +39,7 @@ import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.method.AuthorizationDeniedHandler;
import org.springframework.security.authorization.method.AuthorizeReturnObject;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
@ -127,54 +128,67 @@ public interface MethodSecurityService {
@RequireAdminRole
void repeatedAnnotations();
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
@PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
String preAuthorizeGetCardNumberIfAdmin(String cardNumber);
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StartMaskingHandlerChild.class)
@PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class)
String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
@PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
String preAuthorizeThrowAccessDeniedManually();
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = CardNumberMaskingPostProcessor.class)
@PostAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class)
String postAuthorizeGetCardNumberIfAdmin(String cardNumber);
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = PostMaskingPostProcessor.class)
@PostAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class)
String postAuthorizeThrowAccessDeniedManually();
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
@PreAuthorize("denyAll()")
@Mask("methodmask")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
String preAuthorizeDeniedMethodWithMaskAnnotation();
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
@PreAuthorize("denyAll()")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
String preAuthorizeDeniedMethodWithNoMaskAnnotation();
@NullDenied(role = "ADMIN")
String postAuthorizeDeniedWithNullDenied();
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
@PostAuthorize("denyAll()")
@Mask("methodmask")
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
String postAuthorizeDeniedMethodWithMaskAnnotation();
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
@PostAuthorize("denyAll()")
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
String postAuthorizeDeniedMethodWithNoMaskAnnotation();
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = MaskAnnotationHandler.class)
@PreAuthorize("hasRole('ADMIN')")
@Mask(expression = "@myMasker.getMask()")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
String preAuthorizeWithMaskAnnotationUsingBean();
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = MaskAnnotationPostProcessor.class)
@PostAuthorize("hasRole('ADMIN')")
@Mask(expression = "@myMasker.getMask(returnObject)")
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
String postAuthorizeWithMaskAnnotationUsingBean();
@AuthorizeReturnObject
UserRecordWithEmailProtected getUserRecordWithEmailProtected();
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = UserFallbackDeniedHandler.class)
@PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = UserFallbackDeniedHandler.class)
UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized();
@PreAuthorize(value = "@authz.checkResult(#result)", handlerClass = MethodAuthorizationDeniedHandler.class)
@PostAuthorize(value = "@authz.checkResult(!#result)",
@PreAuthorize("@authz.checkResult(#result)")
@PostAuthorize("@authz.checkResult(!#result)")
@AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class,
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
String checkCustomResult(boolean result);
@ -305,7 +319,8 @@ public interface MethodSecurityService {
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@PostAuthorize(value = "hasRole('{role}')", postProcessorClass = NullPostProcessor.class)
@PostAuthorize("hasRole('{role}')")
@AuthorizationDeniedHandler(postProcessorClass = NullPostProcessor.class)
@interface NullDenied {
String role();

View File

@ -33,6 +33,7 @@ import org.springframework.security.access.expression.method.DefaultMethodSecuri
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.method.AuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult;
@ -45,48 +46,60 @@ import org.springframework.util.StringUtils;
@ReactiveMethodSecurityService.Mask("classmask")
public interface ReactiveMethodSecurityService {
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
@PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber);
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StartMaskingHandlerChild.class)
@PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class)
Mono<String> preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
@PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
Mono<String> preAuthorizeThrowAccessDeniedManually();
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = CardNumberMaskingPostProcessor.class)
@PostAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class)
Mono<String> postAuthorizeGetCardNumberIfAdmin(String cardNumber);
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = PostMaskingPostProcessor.class)
@PostAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class)
Mono<String> postAuthorizeThrowAccessDeniedManually();
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
@PreAuthorize("denyAll()")
@Mask("methodmask")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
Mono<String> preAuthorizeDeniedMethodWithMaskAnnotation();
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
@PreAuthorize("denyAll()")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
Mono<String> preAuthorizeDeniedMethodWithNoMaskAnnotation();
@NullDenied(role = "ADMIN")
Mono<String> postAuthorizeDeniedWithNullDenied();
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
@PostAuthorize("denyAll()")
@Mask("methodmask")
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
Mono<String> postAuthorizeDeniedMethodWithMaskAnnotation();
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
@PostAuthorize("denyAll()")
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
Mono<String> postAuthorizeDeniedMethodWithNoMaskAnnotation();
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = MaskAnnotationHandler.class)
@PreAuthorize("hasRole('ADMIN')")
@Mask(expression = "@myMasker.getMask()")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
Mono<String> preAuthorizeWithMaskAnnotationUsingBean();
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = MaskAnnotationPostProcessor.class)
@PostAuthorize("hasRole('ADMIN')")
@Mask(expression = "@myMasker.getMask(returnObject)")
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
Mono<String> postAuthorizeWithMaskAnnotationUsingBean();
@PreAuthorize(value = "@authz.checkReactiveResult(#result)", handlerClass = MethodAuthorizationDeniedHandler.class)
@PostAuthorize(value = "@authz.checkReactiveResult(!#result)",
@PreAuthorize("@authz.checkReactiveResult(#result)")
@PostAuthorize("@authz.checkReactiveResult(!#result)")
@AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class,
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
Mono<String> checkCustomResult(boolean result);
@ -217,7 +230,8 @@ public interface ReactiveMethodSecurityService {
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@PostAuthorize(value = "hasRole('{value}')", postProcessorClass = NullPostProcessor.class)
@PostAuthorize("hasRole('{value}')")
@AuthorizationDeniedHandler(postProcessorClass = NullPostProcessor.class)
@interface NullDenied {
String role();

View File

@ -18,6 +18,7 @@ package org.springframework.security.config.annotation.method.configuration;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.method.AuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult;
@ -36,7 +37,8 @@ public class UserRecordWithEmailProtected {
return this.name;
}
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = EmailMaskingPostProcessor.class)
@PostAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(postProcessorClass = EmailMaskingPostProcessor.class)
public String email() {
return this.email;
}

View File

@ -23,9 +23,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
/**
* Annotation for specifying a method access-control expression which will be evaluated
* after a method has been invoked.
@ -45,10 +42,4 @@ public @interface PostAuthorize {
*/
String value();
/**
* @return the {@link MethodAuthorizationDeniedPostProcessor} class used to
* post-process access denied
*/
Class<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass() default ThrowingMethodAuthorizationDeniedPostProcessor.class;
}

View File

@ -23,9 +23,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
/**
* Annotation for specifying a method access-control expression which will be evaluated to
* decide whether a method invocation is allowed or not.
@ -45,10 +42,4 @@ public @interface PreAuthorize {
*/
String value();
/**
* @return the {@link MethodAuthorizationDeniedHandler} class used to handle access
* denied
*/
Class<? extends MethodAuthorizationDeniedHandler> handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class;
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2002-2024 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation for specifying handling behavior when an authorization denied happens in
* method security
*
* @author Marcus da Coregio
* @since 6.3
* @see org.springframework.security.access.prepost.PreAuthorize
* @see org.springframework.security.access.prepost.PostAuthorize
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface AuthorizationDeniedHandler {
/**
* The {@link MethodAuthorizationDeniedHandler} used to handle denied authorizations
* from {@link org.springframework.security.access.prepost.PreAuthorize}
* @return
*/
Class<? extends MethodAuthorizationDeniedHandler> handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class;
/**
* The {@link MethodAuthorizationDeniedPostProcessor} used to post process denied
* authorizations from
* {@link org.springframework.security.access.prepost.PostAuthorize}
* @return
*/
Class<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass() default ThrowingMethodAuthorizationDeniedPostProcessor.class;
}

View File

@ -55,11 +55,24 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value());
MethodAuthorizationDeniedPostProcessor postProcessor = this.postProcessorResolver
.apply(postAuthorize.postProcessorClass());
MethodAuthorizationDeniedPostProcessor postProcessor = resolvePostProcessor(method, targetClass);
return new PostAuthorizeExpressionAttribute(expression, postProcessor);
}
private MethodAuthorizationDeniedPostProcessor resolvePostProcessor(Method method, Class<?> targetClass) {
Function<AnnotatedElement, AuthorizationDeniedHandler> lookup = AuthorizationAnnotationUtils
.withDefaults(AuthorizationDeniedHandler.class);
AuthorizationDeniedHandler deniedHandler = lookup.apply(method);
if (deniedHandler != null) {
return this.postProcessorResolver.apply(deniedHandler.postProcessorClass());
}
deniedHandler = lookup.apply(targetClass(method, targetClass));
if (deniedHandler != null) {
return this.postProcessorResolver.apply(deniedHandler.postProcessorClass());
}
return this.defaultPostProcessor;
}
private PostAuthorize findPostAuthorizeAnnotation(Method method, Class<?> targetClass) {
Function<AnnotatedElement, PostAuthorize> lookup = findUniqueAnnotation(PostAuthorize.class);
PostAuthorize postAuthorize = lookup.apply(method);

View File

@ -55,10 +55,24 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(preAuthorize.value());
MethodAuthorizationDeniedHandler handler = this.handlerResolver.apply(preAuthorize.handlerClass());
MethodAuthorizationDeniedHandler handler = resolveHandler(method, targetClass);
return new PreAuthorizeExpressionAttribute(expression, handler);
}
private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) {
Function<AnnotatedElement, AuthorizationDeniedHandler> lookup = AuthorizationAnnotationUtils
.withDefaults(AuthorizationDeniedHandler.class);
AuthorizationDeniedHandler deniedHandler = lookup.apply(method);
if (deniedHandler != null) {
return this.handlerResolver.apply(deniedHandler.handlerClass());
}
deniedHandler = lookup.apply(targetClass(method, targetClass));
if (deniedHandler != null) {
return this.handlerResolver.apply(deniedHandler.handlerClass());
}
return this.defaultHandler;
}
private PreAuthorize findPreAuthorizeAnnotation(Method method, Class<?> targetClass) {
Function<AnnotatedElement, PreAuthorize> lookup = findUniqueAnnotation(PreAuthorize.class);
PreAuthorize preAuthorize = lookup.apply(method);

View File

@ -2251,12 +2251,11 @@ You can also add the Spring Boot property `spring.jackson.default-property-inclu
There are some scenarios where you may not wish to throw an `AccessDeniedException` when a method is invoked without the required permissions.
Instead, you might wish to return a post-processed result, like a masked result, or a default value in cases where access denied happened before invoking the method.
Spring Security provides support for handling and post-processing method access denied with the <<authorizing-with-annotations,`@PreAuthorize` and `@PostAuthorize` annotations>> respectively.
The `@PreAuthorize` annotation works with implementations of `MethodAuthorizationDeniedHandler` while the `@PostAuthorize` annotation works with implementations of `MethodAuthorizationDeniedPostProcessor`.
Spring Security provides support for handling and post-processing method access denied by combining {security-api-url}org/springframework/security/authorization/method/AuthorizationDeniedHandler.html[`@AuthorizationDeniedHandler`] with the <<authorizing-with-annotations,`@PreAuthorize` and `@PostAuthorize` annotations>> respectively.
=== Using with `@PreAuthorize`
Let's consider the example from the <<authorize-object,previous section>>, but instead of creating the `AccessDeniedExceptionInterceptor` to transform an `AccessDeniedException` to a `null` return value, we will use the `handlerClass` attribute from `@PreAuthorize`:
Let's consider the example from the <<authorize-object,previous section>>, but instead of creating the `AccessDeniedExceptionInterceptor` to transform an `AccessDeniedException` to a `null` return value, we will use the `handlerClass` attribute from `@AuthorizationDeniedHandler`:
[tabs]
======
@ -2287,7 +2286,8 @@ public class SecurityConfig {
public class User {
// ...
@PreAuthorize(value = "hasAuthority('user:read')", handlerClass = NullMethodAuthorizationDeniedHandler.class)
@PreAuthorize(value = "hasAuthority('user:read')")
@AuthorizationDeniedHandler(handlerClass = NullMethodAuthorizationDeniedHandler.class)
public String getEmail() {
return this.email;
}
@ -2317,16 +2317,21 @@ class SecurityConfig {
}
class User (val name:String, @get:PreAuthorize(value = "hasAuthority('user:read')", handlerClass = NullMethodAuthorizationDeniedHandler::class) val email:String) <3>
class User (val name:String, @PreAuthorize(value = "hasAuthority('user:read')") @AuthorizationDeniedHandler(handlerClass = NullMethodAuthorizationDeniedHandler::class) val email:String) <3>
----
======
<1> Create an implementation of `MethodAuthorizationDeniedHandler` that returns a `null` value
<2> Register the `NullMethodAuthorizationDeniedHandler` as a bean
<3> Pass the `NullMethodAuthorizationDeniedHandler` to the `handlerClass` attribute of `@PreAuthorize`
<3> Annotate the method with `@AuthorizationDeniedHandler` and pass the `NullMethodAuthorizationDeniedHandler` to the `handlerClass` attribute
And then you can verify that a `null` value is returned instead of the `AccessDeniedException`:
[TIP]
====
You can also annotate your class with `@Component` instead of creating a `@Bean` method
====
[tabs]
======
Java::
@ -2393,7 +2398,8 @@ public class SecurityConfig {
public class User {
// ...
@PostAuthorize(value = "hasAuthority('user:read')", postProcessorClass = EmailMaskingMethodAuthorizationDeniedPostProcessor.class)
@PostAuthorize(value = "hasAuthority('user:read')")
@AuthorizationDeniedHandler(postProcessorClass = EmailMaskingMethodAuthorizationDeniedPostProcessor.class)
public String getEmail() {
return this.email;
}
@ -2424,13 +2430,13 @@ class SecurityConfig {
}
class User (val name:String, @PostAuthorize(value = "hasAuthority('user:read')", postProcessorClass = EmailMaskingMethodAuthorizationDeniedPostProcessor::class) val email:String) <3>
class User (val name:String, @PostAuthorize(value = "hasAuthority('user:read')") @AuthorizationDeniedHandler(postProcessorClass = EmailMaskingMethodAuthorizationDeniedPostProcessor::class) val email:String) <3>
----
======
<1> Create an implementation of `MethodAuthorizationDeniedPostProcessor` that returns a masked value of the unauthorized result value
<2> Register the `EmailMaskingMethodAuthorizationDeniedPostProcessor` as a bean
<3> Pass the `EmailMaskingMethodAuthorizationDeniedPostProcessor` to the `postProcessorClass` attribute of `@PostAuthorize`
<3> Annotate the method with `@AuthorizationDeniedHandler` and pass the `EmailMaskingMethodAuthorizationDeniedPostProcessor` to the `postProcessorClass` attribute
And then you can verify that a masked email is returned instead of an `AccessDeniedException`:
@ -2477,9 +2483,10 @@ When implementing the `MethodAuthorizationDeniedHandler` or the `MethodAuthoriza
Note that since the handler and the post-processor must be registered as beans, you can inject dependencies into them if you need a more complex logic.
In addition to that, you have available the `MethodInvocation` or the `MethodInvocationResult`, as well as the `AuthorizationResult` for more details related to the authorization decision.
[[deciding-return-based-parameters]]
=== Deciding What to Return Based on Available Parameters
Consider a scenario where there might multiple mask values for different methods, it would be not so productive if we had to create a handler or post-processor for each of those methods, although it is perfectly fine to do that.
Consider a scenario where there might be multiple mask values for different methods, it would be not so productive if we had to create a handler or post-processor for each of those methods, although it is perfectly fine to do that.
In such cases, we can use the information passed via parameters to decide what to do.
For example, we can create a custom `@Mask` annotation and a handler that detects that annotation to decide what mask value to return:
@ -2523,13 +2530,15 @@ public class SecurityConfig {
@Component
public class MyService {
@PreAuthorize(value = "hasAuthority('user:read')", handlerClass = MaskAnnotationDeniedHandler.class)
@PreAuthorize(value = "hasAuthority('user:read')")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class)
@Mask("***")
public String foo() {
return "foo";
}
@PreAuthorize(value = "hasAuthority('user:read')", handlerClass = MaskAnnotationDeniedHandler.class)
@PreAuthorize(value = "hasAuthority('user:read')")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class)
@Mask("???")
public String bar() {
return "bar";
@ -2571,13 +2580,15 @@ class SecurityConfig {
@Component
class MyService {
@PreAuthorize(value = "hasAuthority('user:read')", handlerClass = MaskAnnotationDeniedHandler::class)
@PreAuthorize(value = "hasAuthority('user:read')")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class)
@Mask("***")
fun foo(): String {
return "foo"
}
@PreAuthorize(value = "hasAuthority('user:read')", handlerClass = MaskAnnotationDeniedHandler::class)
@PreAuthorize(value = "hasAuthority('user:read')")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class)
@Mask("???")
fun bar(): String {
return "bar"
@ -2634,34 +2645,8 @@ fun barWhenDeniedThenReturnQuestionMarks() {
=== Combining with Meta Annotation Support
Some authorization expressions may be long enough that it can become hard to read or to maintain.
For example, consider the following `@PreAuthorize` expression:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@PreAuthorize(value = "@myAuthorizationBean.check()", handlerClass = NullAuthorizationDeniedHandler.class)
public String myMethod() {
// ...
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@PreAuthorize(value = "@myAuthorizationBean.check()", handlerClass = NullAuthorizationDeniedHandler::class)
fun myMethod(): String {
// ...
}
----
======
The way it is, it is somewhat hard to read it, but we can do better.
By using the <<meta-annotations,meta annotation support>>, we can simplify it to:
You can also combine the `@AuthorizationDeniedHandler` with other annotations in order to reduce and simplify the annotations in a method.
Let's consider the <<deciding-return-based-parameters,example from the previous section>> and merge `@AuthorizationDeniedHandler` with `@Mask`:
[tabs]
======
@ -2671,10 +2656,14 @@ Java::
----
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize(value = "@myAuthorizationBean.check()", handlerClass = NullAuthorizationDeniedHandler.class)
public @interface NullDenied {}
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class)
public @interface Mask {
@NullDenied
String value();
}
@Mask("***")
public String myMethod() {
// ...
}
@ -2686,16 +2675,17 @@ Kotlin::
----
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@PreAuthorize(value = "@myAuthorizationBean.check()", handlerClass = NullAuthorizationDeniedHandler::class)
annotation class NullDenied
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class)
annotation class Mask(val value: String)
@NullDenied
@Mask("***")
fun myMethod(): String {
// ...
}
----
======
Now you do not have to remember to add both annotations when you need a mask behavior in your method.
Make sure to read the <<meta-annotations,Meta Annotations Support>> section for more details on the usage.
[[migration-enableglobalmethodsecurity]]