mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-01 17:52:13 +00:00
Add @AuthorizationDeniedHandler for Method Authorization Denied Handling
Issue gh-14601
This commit is contained in:
parent
75197ca531
commit
8d914ef145
@ -39,6 +39,7 @@ import org.springframework.security.access.prepost.PostFilter;
|
|||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.access.prepost.PreFilter;
|
import org.springframework.security.access.prepost.PreFilter;
|
||||||
import org.springframework.security.authorization.AuthorizationResult;
|
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.AuthorizeReturnObject;
|
||||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
||||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
||||||
@ -127,54 +128,67 @@ public interface MethodSecurityService {
|
|||||||
@RequireAdminRole
|
@RequireAdminRole
|
||||||
void repeatedAnnotations();
|
void repeatedAnnotations();
|
||||||
|
|
||||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
|
||||||
String preAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
String preAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
||||||
|
|
||||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StartMaskingHandlerChild.class)
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class)
|
||||||
String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
|
String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
|
||||||
|
|
||||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
|
||||||
String preAuthorizeThrowAccessDeniedManually();
|
String preAuthorizeThrowAccessDeniedManually();
|
||||||
|
|
||||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = CardNumberMaskingPostProcessor.class)
|
@PostAuthorize("hasRole('ADMIN')")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class)
|
||||||
String postAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
String postAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
||||||
|
|
||||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = PostMaskingPostProcessor.class)
|
@PostAuthorize("hasRole('ADMIN')")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class)
|
||||||
String postAuthorizeThrowAccessDeniedManually();
|
String postAuthorizeThrowAccessDeniedManually();
|
||||||
|
|
||||||
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
|
@PreAuthorize("denyAll()")
|
||||||
@Mask("methodmask")
|
@Mask("methodmask")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
|
||||||
String preAuthorizeDeniedMethodWithMaskAnnotation();
|
String preAuthorizeDeniedMethodWithMaskAnnotation();
|
||||||
|
|
||||||
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
|
@PreAuthorize("denyAll()")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
|
||||||
String preAuthorizeDeniedMethodWithNoMaskAnnotation();
|
String preAuthorizeDeniedMethodWithNoMaskAnnotation();
|
||||||
|
|
||||||
@NullDenied(role = "ADMIN")
|
@NullDenied(role = "ADMIN")
|
||||||
String postAuthorizeDeniedWithNullDenied();
|
String postAuthorizeDeniedWithNullDenied();
|
||||||
|
|
||||||
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
|
@PostAuthorize("denyAll()")
|
||||||
@Mask("methodmask")
|
@Mask("methodmask")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||||
String postAuthorizeDeniedMethodWithMaskAnnotation();
|
String postAuthorizeDeniedMethodWithMaskAnnotation();
|
||||||
|
|
||||||
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
|
@PostAuthorize("denyAll()")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||||
String postAuthorizeDeniedMethodWithNoMaskAnnotation();
|
String postAuthorizeDeniedMethodWithNoMaskAnnotation();
|
||||||
|
|
||||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = MaskAnnotationHandler.class)
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@Mask(expression = "@myMasker.getMask()")
|
@Mask(expression = "@myMasker.getMask()")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
|
||||||
String preAuthorizeWithMaskAnnotationUsingBean();
|
String preAuthorizeWithMaskAnnotationUsingBean();
|
||||||
|
|
||||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = MaskAnnotationPostProcessor.class)
|
@PostAuthorize("hasRole('ADMIN')")
|
||||||
@Mask(expression = "@myMasker.getMask(returnObject)")
|
@Mask(expression = "@myMasker.getMask(returnObject)")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||||
String postAuthorizeWithMaskAnnotationUsingBean();
|
String postAuthorizeWithMaskAnnotationUsingBean();
|
||||||
|
|
||||||
@AuthorizeReturnObject
|
@AuthorizeReturnObject
|
||||||
UserRecordWithEmailProtected getUserRecordWithEmailProtected();
|
UserRecordWithEmailProtected getUserRecordWithEmailProtected();
|
||||||
|
|
||||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = UserFallbackDeniedHandler.class)
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = UserFallbackDeniedHandler.class)
|
||||||
UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized();
|
UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized();
|
||||||
|
|
||||||
@PreAuthorize(value = "@authz.checkResult(#result)", handlerClass = MethodAuthorizationDeniedHandler.class)
|
@PreAuthorize("@authz.checkResult(#result)")
|
||||||
@PostAuthorize(value = "@authz.checkResult(!#result)",
|
@PostAuthorize("@authz.checkResult(!#result)")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class,
|
||||||
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
|
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
|
||||||
String checkCustomResult(boolean result);
|
String checkCustomResult(boolean result);
|
||||||
|
|
||||||
@ -305,7 +319,8 @@ public interface MethodSecurityService {
|
|||||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Inherited
|
@Inherited
|
||||||
@PostAuthorize(value = "hasRole('{role}')", postProcessorClass = NullPostProcessor.class)
|
@PostAuthorize("hasRole('{role}')")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = NullPostProcessor.class)
|
||||||
@interface NullDenied {
|
@interface NullDenied {
|
||||||
|
|
||||||
String role();
|
String role();
|
||||||
|
@ -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.PostAuthorize;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.authorization.AuthorizationResult;
|
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.MethodAuthorizationDeniedHandler;
|
||||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
||||||
import org.springframework.security.authorization.method.MethodInvocationResult;
|
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||||
@ -45,48 +46,60 @@ import org.springframework.util.StringUtils;
|
|||||||
@ReactiveMethodSecurityService.Mask("classmask")
|
@ReactiveMethodSecurityService.Mask("classmask")
|
||||||
public interface ReactiveMethodSecurityService {
|
public interface ReactiveMethodSecurityService {
|
||||||
|
|
||||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
|
||||||
Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
||||||
|
|
||||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StartMaskingHandlerChild.class)
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class)
|
||||||
Mono<String> preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
|
Mono<String> preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
|
||||||
|
|
||||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
|
||||||
Mono<String> preAuthorizeThrowAccessDeniedManually();
|
Mono<String> preAuthorizeThrowAccessDeniedManually();
|
||||||
|
|
||||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = CardNumberMaskingPostProcessor.class)
|
@PostAuthorize("hasRole('ADMIN')")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class)
|
||||||
Mono<String> postAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
Mono<String> postAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
||||||
|
|
||||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = PostMaskingPostProcessor.class)
|
@PostAuthorize("hasRole('ADMIN')")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class)
|
||||||
Mono<String> postAuthorizeThrowAccessDeniedManually();
|
Mono<String> postAuthorizeThrowAccessDeniedManually();
|
||||||
|
|
||||||
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
|
@PreAuthorize("denyAll()")
|
||||||
@Mask("methodmask")
|
@Mask("methodmask")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
|
||||||
Mono<String> preAuthorizeDeniedMethodWithMaskAnnotation();
|
Mono<String> preAuthorizeDeniedMethodWithMaskAnnotation();
|
||||||
|
|
||||||
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
|
@PreAuthorize("denyAll()")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
|
||||||
Mono<String> preAuthorizeDeniedMethodWithNoMaskAnnotation();
|
Mono<String> preAuthorizeDeniedMethodWithNoMaskAnnotation();
|
||||||
|
|
||||||
@NullDenied(role = "ADMIN")
|
@NullDenied(role = "ADMIN")
|
||||||
Mono<String> postAuthorizeDeniedWithNullDenied();
|
Mono<String> postAuthorizeDeniedWithNullDenied();
|
||||||
|
|
||||||
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
|
@PostAuthorize("denyAll()")
|
||||||
@Mask("methodmask")
|
@Mask("methodmask")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||||
Mono<String> postAuthorizeDeniedMethodWithMaskAnnotation();
|
Mono<String> postAuthorizeDeniedMethodWithMaskAnnotation();
|
||||||
|
|
||||||
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
|
@PostAuthorize("denyAll()")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||||
Mono<String> postAuthorizeDeniedMethodWithNoMaskAnnotation();
|
Mono<String> postAuthorizeDeniedMethodWithNoMaskAnnotation();
|
||||||
|
|
||||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = MaskAnnotationHandler.class)
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@Mask(expression = "@myMasker.getMask()")
|
@Mask(expression = "@myMasker.getMask()")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
|
||||||
Mono<String> preAuthorizeWithMaskAnnotationUsingBean();
|
Mono<String> preAuthorizeWithMaskAnnotationUsingBean();
|
||||||
|
|
||||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = MaskAnnotationPostProcessor.class)
|
@PostAuthorize("hasRole('ADMIN')")
|
||||||
@Mask(expression = "@myMasker.getMask(returnObject)")
|
@Mask(expression = "@myMasker.getMask(returnObject)")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||||
Mono<String> postAuthorizeWithMaskAnnotationUsingBean();
|
Mono<String> postAuthorizeWithMaskAnnotationUsingBean();
|
||||||
|
|
||||||
@PreAuthorize(value = "@authz.checkReactiveResult(#result)", handlerClass = MethodAuthorizationDeniedHandler.class)
|
@PreAuthorize("@authz.checkReactiveResult(#result)")
|
||||||
@PostAuthorize(value = "@authz.checkReactiveResult(!#result)",
|
@PostAuthorize("@authz.checkReactiveResult(!#result)")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class,
|
||||||
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
|
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
|
||||||
Mono<String> checkCustomResult(boolean result);
|
Mono<String> checkCustomResult(boolean result);
|
||||||
|
|
||||||
@ -217,7 +230,8 @@ public interface ReactiveMethodSecurityService {
|
|||||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Inherited
|
@Inherited
|
||||||
@PostAuthorize(value = "hasRole('{value}')", postProcessorClass = NullPostProcessor.class)
|
@PostAuthorize("hasRole('{value}')")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = NullPostProcessor.class)
|
||||||
@interface NullDenied {
|
@interface NullDenied {
|
||||||
|
|
||||||
String role();
|
String role();
|
||||||
|
@ -18,6 +18,7 @@ package org.springframework.security.config.annotation.method.configuration;
|
|||||||
|
|
||||||
import org.springframework.security.access.prepost.PostAuthorize;
|
import org.springframework.security.access.prepost.PostAuthorize;
|
||||||
import org.springframework.security.authorization.AuthorizationResult;
|
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.MethodAuthorizationDeniedPostProcessor;
|
||||||
import org.springframework.security.authorization.method.MethodInvocationResult;
|
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||||
|
|
||||||
@ -36,7 +37,8 @@ public class UserRecordWithEmailProtected {
|
|||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = EmailMaskingPostProcessor.class)
|
@PostAuthorize("hasRole('ADMIN')")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = EmailMaskingPostProcessor.class)
|
||||||
public String email() {
|
public String email() {
|
||||||
return this.email;
|
return this.email;
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,6 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
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
|
* Annotation for specifying a method access-control expression which will be evaluated
|
||||||
* after a method has been invoked.
|
* after a method has been invoked.
|
||||||
@ -45,10 +42,4 @@ public @interface PostAuthorize {
|
|||||||
*/
|
*/
|
||||||
String value();
|
String value();
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the {@link MethodAuthorizationDeniedPostProcessor} class used to
|
|
||||||
* post-process access denied
|
|
||||||
*/
|
|
||||||
Class<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass() default ThrowingMethodAuthorizationDeniedPostProcessor.class;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,6 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
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
|
* Annotation for specifying a method access-control expression which will be evaluated to
|
||||||
* decide whether a method invocation is allowed or not.
|
* decide whether a method invocation is allowed or not.
|
||||||
@ -45,10 +42,4 @@ public @interface PreAuthorize {
|
|||||||
*/
|
*/
|
||||||
String value();
|
String value();
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the {@link MethodAuthorizationDeniedHandler} class used to handle access
|
|
||||||
* denied
|
|
||||||
*/
|
|
||||||
Class<? extends MethodAuthorizationDeniedHandler> handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -55,11 +55,24 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
|
|||||||
return ExpressionAttribute.NULL_ATTRIBUTE;
|
return ExpressionAttribute.NULL_ATTRIBUTE;
|
||||||
}
|
}
|
||||||
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value());
|
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value());
|
||||||
MethodAuthorizationDeniedPostProcessor postProcessor = this.postProcessorResolver
|
MethodAuthorizationDeniedPostProcessor postProcessor = resolvePostProcessor(method, targetClass);
|
||||||
.apply(postAuthorize.postProcessorClass());
|
|
||||||
return new PostAuthorizeExpressionAttribute(expression, postProcessor);
|
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) {
|
private PostAuthorize findPostAuthorizeAnnotation(Method method, Class<?> targetClass) {
|
||||||
Function<AnnotatedElement, PostAuthorize> lookup = findUniqueAnnotation(PostAuthorize.class);
|
Function<AnnotatedElement, PostAuthorize> lookup = findUniqueAnnotation(PostAuthorize.class);
|
||||||
PostAuthorize postAuthorize = lookup.apply(method);
|
PostAuthorize postAuthorize = lookup.apply(method);
|
||||||
|
@ -55,10 +55,24 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
|
|||||||
return ExpressionAttribute.NULL_ATTRIBUTE;
|
return ExpressionAttribute.NULL_ATTRIBUTE;
|
||||||
}
|
}
|
||||||
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(preAuthorize.value());
|
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(preAuthorize.value());
|
||||||
MethodAuthorizationDeniedHandler handler = this.handlerResolver.apply(preAuthorize.handlerClass());
|
MethodAuthorizationDeniedHandler handler = resolveHandler(method, targetClass);
|
||||||
return new PreAuthorizeExpressionAttribute(expression, handler);
|
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) {
|
private PreAuthorize findPreAuthorizeAnnotation(Method method, Class<?> targetClass) {
|
||||||
Function<AnnotatedElement, PreAuthorize> lookup = findUniqueAnnotation(PreAuthorize.class);
|
Function<AnnotatedElement, PreAuthorize> lookup = findUniqueAnnotation(PreAuthorize.class);
|
||||||
PreAuthorize preAuthorize = lookup.apply(method);
|
PreAuthorize preAuthorize = lookup.apply(method);
|
||||||
|
@ -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.
|
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.
|
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.
|
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.
|
||||||
The `@PreAuthorize` annotation works with implementations of `MethodAuthorizationDeniedHandler` while the `@PostAuthorize` annotation works with implementations of `MethodAuthorizationDeniedPostProcessor`.
|
|
||||||
|
|
||||||
=== Using with `@PreAuthorize`
|
=== 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]
|
[tabs]
|
||||||
======
|
======
|
||||||
@ -2287,7 +2286,8 @@ public class SecurityConfig {
|
|||||||
public class User {
|
public class User {
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
@PreAuthorize(value = "hasAuthority('user:read')", handlerClass = NullMethodAuthorizationDeniedHandler.class)
|
@PreAuthorize(value = "hasAuthority('user:read')")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = NullMethodAuthorizationDeniedHandler.class)
|
||||||
public String getEmail() {
|
public String getEmail() {
|
||||||
return this.email;
|
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
|
<1> Create an implementation of `MethodAuthorizationDeniedHandler` that returns a `null` value
|
||||||
<2> Register the `NullMethodAuthorizationDeniedHandler` as a bean
|
<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`:
|
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]
|
[tabs]
|
||||||
======
|
======
|
||||||
Java::
|
Java::
|
||||||
@ -2393,7 +2398,8 @@ public class SecurityConfig {
|
|||||||
public class User {
|
public class User {
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
@PostAuthorize(value = "hasAuthority('user:read')", postProcessorClass = EmailMaskingMethodAuthorizationDeniedPostProcessor.class)
|
@PostAuthorize(value = "hasAuthority('user:read')")
|
||||||
|
@AuthorizationDeniedHandler(postProcessorClass = EmailMaskingMethodAuthorizationDeniedPostProcessor.class)
|
||||||
public String getEmail() {
|
public String getEmail() {
|
||||||
return this.email;
|
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
|
<1> Create an implementation of `MethodAuthorizationDeniedPostProcessor` that returns a masked value of the unauthorized result value
|
||||||
<2> Register the `EmailMaskingMethodAuthorizationDeniedPostProcessor` as a bean
|
<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`:
|
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.
|
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.
|
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
|
=== 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.
|
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:
|
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
|
@Component
|
||||||
public class MyService {
|
public class MyService {
|
||||||
|
|
||||||
@PreAuthorize(value = "hasAuthority('user:read')", handlerClass = MaskAnnotationDeniedHandler.class)
|
@PreAuthorize(value = "hasAuthority('user:read')")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class)
|
||||||
@Mask("***")
|
@Mask("***")
|
||||||
public String foo() {
|
public String foo() {
|
||||||
return "foo";
|
return "foo";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize(value = "hasAuthority('user:read')", handlerClass = MaskAnnotationDeniedHandler.class)
|
@PreAuthorize(value = "hasAuthority('user:read')")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class)
|
||||||
@Mask("???")
|
@Mask("???")
|
||||||
public String bar() {
|
public String bar() {
|
||||||
return "bar";
|
return "bar";
|
||||||
@ -2571,13 +2580,15 @@ class SecurityConfig {
|
|||||||
@Component
|
@Component
|
||||||
class MyService {
|
class MyService {
|
||||||
|
|
||||||
@PreAuthorize(value = "hasAuthority('user:read')", handlerClass = MaskAnnotationDeniedHandler::class)
|
@PreAuthorize(value = "hasAuthority('user:read')")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class)
|
||||||
@Mask("***")
|
@Mask("***")
|
||||||
fun foo(): String {
|
fun foo(): String {
|
||||||
return "foo"
|
return "foo"
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize(value = "hasAuthority('user:read')", handlerClass = MaskAnnotationDeniedHandler::class)
|
@PreAuthorize(value = "hasAuthority('user:read')")
|
||||||
|
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class)
|
||||||
@Mask("???")
|
@Mask("???")
|
||||||
fun bar(): String {
|
fun bar(): String {
|
||||||
return "bar"
|
return "bar"
|
||||||
@ -2634,34 +2645,8 @@ fun barWhenDeniedThenReturnQuestionMarks() {
|
|||||||
|
|
||||||
=== Combining with Meta Annotation Support
|
=== Combining with Meta Annotation Support
|
||||||
|
|
||||||
Some authorization expressions may be long enough that it can become hard to read or to maintain.
|
You can also combine the `@AuthorizationDeniedHandler` with other annotations in order to reduce and simplify the annotations in a method.
|
||||||
For example, consider the following `@PreAuthorize` expression:
|
Let's consider the <<deciding-return-based-parameters,example from the previous section>> and merge `@AuthorizationDeniedHandler` with `@Mask`:
|
||||||
|
|
||||||
[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:
|
|
||||||
|
|
||||||
[tabs]
|
[tabs]
|
||||||
======
|
======
|
||||||
@ -2671,10 +2656,14 @@ Java::
|
|||||||
----
|
----
|
||||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@PreAuthorize(value = "@myAuthorizationBean.check()", handlerClass = NullAuthorizationDeniedHandler.class)
|
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class)
|
||||||
public @interface NullDenied {}
|
public @interface Mask {
|
||||||
|
|
||||||
@NullDenied
|
String value();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mask("***")
|
||||||
public String myMethod() {
|
public String myMethod() {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
@ -2686,16 +2675,17 @@ Kotlin::
|
|||||||
----
|
----
|
||||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@PreAuthorize(value = "@myAuthorizationBean.check()", handlerClass = NullAuthorizationDeniedHandler::class)
|
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class)
|
||||||
annotation class NullDenied
|
annotation class Mask(val value: String)
|
||||||
|
|
||||||
@NullDenied
|
@Mask("***")
|
||||||
fun myMethod(): String {
|
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.
|
Make sure to read the <<meta-annotations,Meta Annotations Support>> section for more details on the usage.
|
||||||
|
|
||||||
[[migration-enableglobalmethodsecurity]]
|
[[migration-enableglobalmethodsecurity]]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user