- Renamed @AuthorizationDeniedHandler to @HandleAuthorizationDenied
- Merged the post processor interface into MethodAuthorizationDeniedHandler , it now has two methods handleDeniedInvocation and handleDeniedInvocationResult
- @HandleAuthorizationDenied now handles AuthorizationDeniedException thrown from the method
Issue gh-14601
@ -56,9 +52,6 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
@@ -56,9 +52,6 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
@ -67,14 +60,14 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
@@ -67,14 +60,14 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
@ -144,12 +145,12 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
@@ -144,12 +145,12 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
@ -783,12 +782,21 @@ public class PrePostMethodSecurityConfigurationTests {
@@ -783,12 +782,21 @@ public class PrePostMethodSecurityConfigurationTests {
@ -938,14 +935,13 @@ public class PrePostMethodSecurityConfigurationTests {
@@ -938,14 +935,13 @@ public class PrePostMethodSecurityConfigurationTests {
@ -1477,18 +1473,11 @@ public class PrePostMethodSecurityConfigurationTests {
@@ -1477,18 +1473,11 @@ public class PrePostMethodSecurityConfigurationTests {
@ -73,18 +72,6 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
@@ -73,18 +72,6 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
@ -119,9 +106,17 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
@@ -119,9 +106,17 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
@ -227,14 +226,13 @@ public class ReactiveMethodSecurityConfigurationTests {
@@ -227,14 +226,13 @@ public class ReactiveMethodSecurityConfigurationTests {
@ -383,18 +381,11 @@ public class ReactiveMethodSecurityConfigurationTests {
@@ -383,18 +381,11 @@ public class ReactiveMethodSecurityConfigurationTests {
@ -34,7 +35,7 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
@@ -34,7 +35,7 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
@ -44,7 +45,7 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
@@ -44,7 +45,7 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
@ -55,17 +53,12 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
@@ -55,17 +53,12 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
@ -116,14 +109,14 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
@@ -116,14 +109,14 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
@ -49,8 +47,6 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
@@ -49,8 +47,6 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
@ -58,9 +54,6 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
@@ -58,9 +54,6 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
@ -99,14 +92,14 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
@@ -99,14 +92,14 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
@ -57,7 +56,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
@@ -57,7 +56,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
@ -119,7 +118,16 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
@@ -119,7 +118,16 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
@ -174,28 +182,22 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
@@ -174,28 +182,22 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
@ -60,7 +61,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
@@ -60,7 +61,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
@ -118,27 +119,39 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
@@ -118,27 +119,39 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
+"(for example, a Mono or Flux) or the function must be a Kotlin coroutine "
@ -153,17 +166,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
@@ -153,17 +166,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
@ -171,10 +174,24 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
@@ -171,10 +174,24 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
@ -261,14 +261,33 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
@@ -261,14 +261,33 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
@ -142,19 +142,12 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
@@ -142,19 +142,12 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
@ -162,28 +155,21 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
@@ -162,28 +155,21 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
@ -55,22 +55,22 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
@@ -55,22 +55,22 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
@ -86,23 +86,23 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
@@ -86,23 +86,23 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
@ -95,11 +95,19 @@ public final class PostAuthorizeReactiveAuthorizationManager
@@ -95,11 +95,19 @@ public final class PostAuthorizeReactiveAuthorizationManager
@ -60,9 +60,9 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
@@ -60,9 +60,9 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
@ -90,10 +90,10 @@ public final class PreAuthorizeReactiveAuthorizationManager
@@ -90,10 +90,10 @@ public final class PreAuthorizeReactiveAuthorizationManager
@ -127,7 +127,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@@ -127,7 +127,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@ -147,7 +147,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@@ -147,7 +147,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@ -173,7 +173,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@@ -173,7 +173,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@ -192,7 +192,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@@ -192,7 +192,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@ -211,7 +211,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@@ -211,7 +211,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@ -266,7 +266,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@@ -266,7 +266,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
@ -127,7 +127,8 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
@@ -127,7 +127,8 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
@ -145,7 +146,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
@@ -145,7 +146,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
@ -164,7 +165,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
@@ -164,7 +165,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
@ -2256,14 +2256,13 @@ You can also add the Spring Boot property `spring.jackson.default-property-inclu
@@ -2256,14 +2256,13 @@ You can also add the Spring Boot property `spring.jackson.default-property-inclu
[[fallback-values-authorization-denied]]
== Providing Fallback Values When Authorization is Denied
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.
There are some scenarios where you may not wish to throw an `AuthorizationDeniedException` 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 authorization denied happened before invoking the method.
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.
Spring Security provides support for handling authorization denied on method invocation by using the {security-api-url}org/springframework/security/authorization/method/HandleAuthorizationDenied.html[`@HandleAuthorizationDenied`].
The handler works for denied authorizations that happened in the <<authorizing-with-annotations,`@PreAuthorize` and `@PostAuthorize` annotations>> as well as {security-api-url}org/springframework/security/authorization/AuthorizationDeniedException.html[`AuthorizationDeniedException`] thrown from the method invocation itself.
=== 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 `@AuthorizationDeniedHandler`:
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 `@HandleAuthorizationDenied`:
class NullMethodAuthorizationDeniedHandler : MethodAuthorizationDeniedHandler { <1>
override fun handle(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any {
override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any {
return null
}
@ -2325,13 +2324,13 @@ class SecurityConfig {
@@ -2325,13 +2324,13 @@ class SecurityConfig {
}
class User (val name:String, @PreAuthorize(value = "hasAuthority('user:read')") @AuthorizationDeniedHandler(handlerClass = NullMethodAuthorizationDeniedHandler::class) val email:String) <3>
class User (val name:String, @PreAuthorize(value = "hasAuthority('user:read')") @HandleAuthorizationDenied(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> Annotate the method with `@AuthorizationDeniedHandler` and pass the `NullMethodAuthorizationDeniedHandler` to the `handlerClass` attribute
<3> Annotate the method with `@HandleAuthorizationDenied` and pass the `NullMethodAuthorizationDeniedHandler` to the `handlerClass` attribute
And then you can verify that a `null` value is returned instead of the `AccessDeniedException`:
@ -2371,9 +2370,12 @@ fun getEmailWhenProxiedThenNullEmail() {
@@ -2371,9 +2370,12 @@ fun getEmailWhenProxiedThenNullEmail() {
----
======
=== Using with `@PostAuthorize`
=== Using the Denied Result From the Method Invocation
There are some scenarios where you might want to return a secure result derived from the denied result.
For example, if a user is not authorized to see email addresses, you might want to apply some masking on the original email address, i.e. _useremail@example.com_ would become _use\\******@example.com_.
The same can be achieved with `@PostAuthorize`, however, since `@PostAuthorize` checks are performed after the method is invoked, we have access to the resulting value of the invocation, allowing you to provide fallback values based on the unauthorized results.
For those scenarios, you can override the `handleDeniedInvocationResult` from the `MethodAuthorizationDeniedHandler`, which has the {security-api-url}org/springframework/security/authorization/method/MethodInvocationResult.html[`MethodInvocationResult`] as an argument.
Let's continue with the previous example, but instead of returning `null`, we will return a masked value of the email:
@ -2397,8 +2404,8 @@ public class EmailMaskingMethodAuthorizationDeniedPostProcessor implements Metho
@@ -2397,8 +2404,8 @@ public class EmailMaskingMethodAuthorizationDeniedPostProcessor implements Metho
public class SecurityConfig {
@Bean <2>
public EmailMaskingMethodAuthorizationDeniedPostProcessor emailMaskingMethodAuthorizationDeniedPostProcessor() {
return new EmailMaskingMethodAuthorizationDeniedPostProcessor();
public EmailMaskingMethodAuthorizationDeniedHandler emailMaskingMethodAuthorizationDeniedHandler() {
return new EmailMaskingMethodAuthorizationDeniedHandler();
}
}
@ -2407,7 +2414,7 @@ public class User {
@@ -2407,7 +2414,7 @@ public class User {
class User (val name:String, @PostAuthorize(value = "hasAuthority('user:read')") @AuthorizationDeniedHandler(postProcessorClass = EmailMaskingMethodAuthorizationDeniedPostProcessor::class) val email:String) <3>
class User (val name:String, @PostAuthorize(value = "hasAuthority('user:read')") @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler::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> Annotate the method with `@AuthorizationDeniedHandler` and pass the `EmailMaskingMethodAuthorizationDeniedPostProcessor` to the `postProcessorClass` attribute
<1> Create an implementation of `MethodAuthorizationDeniedHandler` that returns a masked value of the unauthorized result value
<2> Register the `EmailMaskingMethodAuthorizationDeniedHandler` as a bean
<3> Annotate the method with `@HandleAuthorizationDenied` and pass the `EmailMaskingMethodAuthorizationDeniedHandler` to the `handlerClass` attribute
And then you can verify that a masked email is returned instead of an `AccessDeniedException`:
[WARNING]
====
Since you have access to the original denied value, make sure that you correctly handle it and do not return it to the caller.
====
[tabs]
======
Java::
@ -2481,20 +2497,20 @@ fun getEmailWhenProxiedThenMaskedEmail() {
@@ -2481,20 +2497,20 @@ fun getEmailWhenProxiedThenMaskedEmail() {
----
======
When implementing the `MethodAuthorizationDeniedHandler` or the `MethodAuthorizationDeniedPostProcessor` you have a few options on what you can return:
When implementing the `MethodAuthorizationDeniedHandler` you have a few options on what type you can return:
- A `null` value.
- A non-null value, respecting the method's return type.
- Throw an exception, usually an instance of `AccessDeniedException`. This is the default behavior.
- Throw an exception, usually an instance of `AuthorizationDeniedException`. This is the default behavior.
- A `Mono` type for reactive applications.
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.
Note that since the handler must be registered as beans in your application context, 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 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.
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 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:
@ -2517,7 +2533,7 @@ public @interface Mask {
@@ -2517,7 +2533,7 @@ public @interface Mask {
public class MaskAnnotationDeniedHandler implements MethodAuthorizationDeniedHandler {
@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {