Polish Method Authorization Denied Handling
- 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
This commit is contained in:
parent
14da8f4fc8
commit
2fbbcc4bd0
|
@ -27,22 +27,18 @@ import org.springframework.security.authorization.AuthorizationManager;
|
|||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.authorization.ObservationAuthorizationManager;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
|
||||
final class DeferringObservationAuthorizationManager<T>
|
||||
implements AuthorizationManager<T>, MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
|
||||
implements AuthorizationManager<T>, MethodAuthorizationDeniedHandler {
|
||||
|
||||
private final Supplier<AuthorizationManager<T>> delegate;
|
||||
|
||||
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
|
||||
DeferringObservationAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
|
||||
AuthorizationManager<T> delegate) {
|
||||
this.delegate = SingletonSupplier.of(() -> {
|
||||
|
@ -55,9 +51,6 @@ final class DeferringObservationAuthorizationManager<T>
|
|||
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
|
||||
this.handler = h;
|
||||
}
|
||||
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
|
||||
this.postProcessor = p;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -66,14 +59,14 @@ final class DeferringObservationAuthorizationManager<T>
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
return this.handler.handle(methodInvocation, authorizationResult);
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
|
||||
return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,22 +28,18 @@ import org.springframework.security.authorization.AuthorizationResult;
|
|||
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
|
||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
|
||||
final class DeferringObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T>,
|
||||
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
|
||||
final class DeferringObservationReactiveAuthorizationManager<T>
|
||||
implements ReactiveAuthorizationManager<T>, MethodAuthorizationDeniedHandler {
|
||||
|
||||
private final Supplier<ReactiveAuthorizationManager<T>> delegate;
|
||||
|
||||
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
|
||||
DeferringObservationReactiveAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
|
||||
ReactiveAuthorizationManager<T> delegate) {
|
||||
this.delegate = SingletonSupplier.of(() -> {
|
||||
|
@ -56,9 +52,6 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
|
|||
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
|
||||
this.handler = h;
|
||||
}
|
||||
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
|
||||
this.postProcessor = p;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -67,14 +60,14 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
return this.handler.handle(methodInvocation, authorizationResult);
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
|
||||
return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,10 +39,9 @@ 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.HandleAuthorizationDenied;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
@ -129,32 +128,32 @@ public interface MethodSecurityService {
|
|||
void repeatedAnnotations();
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
|
||||
String preAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class)
|
||||
@HandleAuthorizationDenied(handlerClass = StartMaskingHandlerChild.class)
|
||||
String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
|
||||
String preAuthorizeThrowAccessDeniedManually();
|
||||
|
||||
@PostAuthorize("hasRole('ADMIN')")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = CardNumberMaskingPostProcessor.class)
|
||||
String postAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
||||
|
||||
@PostAuthorize("hasRole('ADMIN')")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = PostMaskingPostProcessor.class)
|
||||
String postAuthorizeThrowAccessDeniedManually();
|
||||
|
||||
@PreAuthorize("denyAll()")
|
||||
@Mask("methodmask")
|
||||
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
|
||||
String preAuthorizeDeniedMethodWithMaskAnnotation();
|
||||
|
||||
@PreAuthorize("denyAll()")
|
||||
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
|
||||
String preAuthorizeDeniedMethodWithNoMaskAnnotation();
|
||||
|
||||
@NullDenied(role = "ADMIN")
|
||||
|
@ -162,40 +161,39 @@ public interface MethodSecurityService {
|
|||
|
||||
@PostAuthorize("denyAll()")
|
||||
@Mask("methodmask")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
|
||||
String postAuthorizeDeniedMethodWithMaskAnnotation();
|
||||
|
||||
@PostAuthorize("denyAll()")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
|
||||
String postAuthorizeDeniedMethodWithNoMaskAnnotation();
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Mask(expression = "@myMasker.getMask()")
|
||||
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
|
||||
String preAuthorizeWithMaskAnnotationUsingBean();
|
||||
|
||||
@PostAuthorize("hasRole('ADMIN')")
|
||||
@Mask(expression = "@myMasker.getMask(returnObject)")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
|
||||
String postAuthorizeWithMaskAnnotationUsingBean();
|
||||
|
||||
@AuthorizeReturnObject
|
||||
UserRecordWithEmailProtected getUserRecordWithEmailProtected();
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@AuthorizationDeniedHandler(handlerClass = UserFallbackDeniedHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = UserFallbackDeniedHandler.class)
|
||||
UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized();
|
||||
|
||||
@PreAuthorize("@authz.checkResult(#result)")
|
||||
@PostAuthorize("@authz.checkResult(!#result)")
|
||||
@AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class,
|
||||
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
|
||||
String checkCustomResult(boolean result);
|
||||
|
||||
class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
return "***";
|
||||
}
|
||||
|
||||
|
@ -204,8 +202,8 @@ public interface MethodSecurityService {
|
|||
class StartMaskingHandlerChild extends StarMaskingHandler {
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
return super.handle(methodInvocation, result) + "-child";
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
return super.handleDeniedInvocation(methodInvocation, result) + "-child";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -218,7 +216,6 @@ public interface MethodSecurityService {
|
|||
this.maskValueResolver = new MaskValueResolver(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class);
|
||||
if (mask == null) {
|
||||
|
@ -227,9 +224,15 @@ public interface MethodSecurityService {
|
|||
return this.maskValueResolver.resolveValue(mask, methodInvocation, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return handle(methodInvocation, authorizationResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
MaskValueResolver maskValueResolver;
|
||||
|
||||
|
@ -238,7 +241,16 @@ public interface MethodSecurityService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||
public Object handleDeniedInvocation(MethodInvocation mi, AuthorizationResult authorizationResult) {
|
||||
Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class);
|
||||
if (mask == null) {
|
||||
mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class);
|
||||
}
|
||||
return this.maskValueResolver.resolveValue(mask, mi, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
|
||||
AuthorizationResult authorizationResult) {
|
||||
MethodInvocation mi = methodInvocationResult.getMethodInvocation();
|
||||
Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class);
|
||||
|
@ -274,31 +286,38 @@ public interface MethodSecurityService {
|
|||
|
||||
}
|
||||
|
||||
class PostMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
class PostMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return "***";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
static String MASK = "****-****-****-";
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return "***";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult contextObject, AuthorizationResult result) {
|
||||
String cardNumber = (String) contextObject.getResult();
|
||||
return MASK + cardNumber.substring(cardNumber.length() - 4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class NullPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
class NullPostProcessor implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return null;
|
||||
}
|
||||
|
@ -320,7 +339,7 @@ public interface MethodSecurityService {
|
|||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@PostAuthorize("hasRole('{role}')")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = NullPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = NullPostProcessor.class)
|
||||
@interface NullDenied {
|
||||
|
||||
String role();
|
||||
|
@ -333,7 +352,8 @@ public interface MethodSecurityService {
|
|||
"Protected");
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return FALLBACK;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,8 @@ package org.springframework.security.config.annotation.method.configuration;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationDeniedException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
|
@ -144,12 +145,12 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
|
|||
|
||||
@Override
|
||||
public String preAuthorizeThrowAccessDeniedManually() {
|
||||
throw new AccessDeniedException("Access Denied");
|
||||
throw new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postAuthorizeThrowAccessDeniedManually() {
|
||||
throw new AccessDeniedException("Access Denied");
|
||||
throw new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -67,7 +67,6 @@ import org.springframework.security.authorization.method.AuthorizationIntercepto
|
|||
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.AuthorizeReturnObject;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
|
||||
import org.springframework.security.config.Customizer;
|
||||
|
@ -92,10 +91,10 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link PrePostMethodSecurityConfiguration}.
|
||||
|
@ -783,12 +782,21 @@ public class PrePostMethodSecurityConfigurationTests {
|
|||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenNotHandled() {
|
||||
void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenHandled() {
|
||||
this.spring.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(service::preAuthorizeThrowAccessDeniedManually);
|
||||
assertThat(service.preAuthorizeThrowAccessDeniedManually()).isEqualTo("***");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
void postAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPostAuthorizeThenHandled() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.PostMaskingPostProcessor.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
assertThat(service.postAuthorizeThrowAccessDeniedManually()).isEqualTo("***");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -813,17 +821,6 @@ public class PrePostMethodSecurityConfigurationTests {
|
|||
assertThat(result).isEqualTo("classmask");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
void postAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPostAuthorizeThenNotHandled() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.PostMaskingPostProcessor.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(service::postAuthorizeThrowAccessDeniedManually);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void postAuthorizeWhenNullDeniedMetaAnnotationThanWorks() {
|
||||
|
@ -938,14 +935,13 @@ public class PrePostMethodSecurityConfigurationTests {
|
|||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
MethodAuthorizationDeniedHandler handler = this.spring.getContext()
|
||||
.getBean(MethodAuthorizationDeniedHandler.class);
|
||||
MethodAuthorizationDeniedPostProcessor postProcessor = this.spring.getContext()
|
||||
.getBean(MethodAuthorizationDeniedPostProcessor.class);
|
||||
assertThat(service.checkCustomResult(false)).isNull();
|
||||
verify(handler).handle(any(), any(Authz.AuthzResult.class));
|
||||
verifyNoInteractions(postProcessor);
|
||||
verify(handler).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
|
||||
verify(handler, never()).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class));
|
||||
clearInvocations(handler);
|
||||
assertThat(service.checkCustomResult(true)).isNull();
|
||||
verify(postProcessor).postProcessResult(any(), any(Authz.AuthzResult.class));
|
||||
verifyNoMoreInteractions(handler);
|
||||
verify(handler).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class));
|
||||
verify(handler, never()).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
|
||||
}
|
||||
|
||||
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
|
||||
|
@ -1477,18 +1473,11 @@ public class PrePostMethodSecurityConfigurationTests {
|
|||
|
||||
MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class);
|
||||
|
||||
MethodAuthorizationDeniedPostProcessor postProcessor = mock(MethodAuthorizationDeniedPostProcessor.class);
|
||||
|
||||
@Bean
|
||||
MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
MethodAuthorizationDeniedPostProcessor methodAuthorizationDeniedPostProcessor() {
|
||||
return this.postProcessor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import reactor.test.StepVerifier;
|
|||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||
|
@ -73,18 +72,6 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
|
|||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenNotHandled() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class)
|
||||
.autowire();
|
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
|
||||
StepVerifier.create(service.preAuthorizeThrowAccessDeniedManually())
|
||||
.expectError(AccessDeniedException.class)
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() {
|
||||
|
@ -119,9 +106,17 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
|
|||
ReactiveMethodSecurityService.PostMaskingPostProcessor.class)
|
||||
.autowire();
|
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
|
||||
StepVerifier.create(service.postAuthorizeThrowAccessDeniedManually())
|
||||
.expectError(AccessDeniedException.class)
|
||||
.verify();
|
||||
StepVerifier.create(service.postAuthorizeThrowAccessDeniedManually()).expectNext("***").verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenHandled() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class)
|
||||
.autowire();
|
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
|
||||
StepVerifier.create(service.preAuthorizeThrowAccessDeniedManually()).expectNext("***").verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -48,7 +48,6 @@ import org.springframework.security.authorization.method.AuthorizationAdvisorPro
|
|||
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
|
||||
import org.springframework.security.authorization.method.AuthorizeReturnObject;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
|
@ -60,10 +59,10 @@ import org.springframework.security.test.context.support.WithMockUser;
|
|||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* @author Tadaya Tsuyukubo
|
||||
|
@ -227,14 +226,13 @@ public class ReactiveMethodSecurityConfigurationTests {
|
|||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
|
||||
MethodAuthorizationDeniedHandler handler = this.spring.getContext()
|
||||
.getBean(MethodAuthorizationDeniedHandler.class);
|
||||
MethodAuthorizationDeniedPostProcessor postProcessor = this.spring.getContext()
|
||||
.getBean(MethodAuthorizationDeniedPostProcessor.class);
|
||||
assertThat(service.checkCustomResult(false).block()).isNull();
|
||||
verify(handler).handle(any(), any(Authz.AuthzResult.class));
|
||||
verifyNoInteractions(postProcessor);
|
||||
verify(handler).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
|
||||
verify(handler, never()).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class));
|
||||
clearInvocations(handler);
|
||||
assertThat(service.checkCustomResult(true).block()).isNull();
|
||||
verify(postProcessor).postProcessResult(any(), any(Authz.AuthzResult.class));
|
||||
verifyNoMoreInteractions(handler);
|
||||
verify(handler).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class));
|
||||
verify(handler, never()).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
|
||||
}
|
||||
|
||||
private static Consumer<User.UserBuilder> authorities(String... authorities) {
|
||||
|
@ -383,18 +381,11 @@ public class ReactiveMethodSecurityConfigurationTests {
|
|||
|
||||
MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class);
|
||||
|
||||
MethodAuthorizationDeniedPostProcessor postProcessor = mock(MethodAuthorizationDeniedPostProcessor.class);
|
||||
|
||||
@Bean
|
||||
MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
MethodAuthorizationDeniedPostProcessor methodAuthorizationDeniedPostProcessor() {
|
||||
return this.postProcessor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,9 +33,8 @@ 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.HandleAuthorizationDenied;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
@ -47,32 +46,32 @@ import org.springframework.util.StringUtils;
|
|||
public interface ReactiveMethodSecurityService {
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
|
||||
Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class)
|
||||
@HandleAuthorizationDenied(handlerClass = StartMaskingHandlerChild.class)
|
||||
Mono<String> preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
|
||||
Mono<String> preAuthorizeThrowAccessDeniedManually();
|
||||
|
||||
@PostAuthorize("hasRole('ADMIN')")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = CardNumberMaskingPostProcessor.class)
|
||||
Mono<String> postAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
||||
|
||||
@PostAuthorize("hasRole('ADMIN')")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = PostMaskingPostProcessor.class)
|
||||
Mono<String> postAuthorizeThrowAccessDeniedManually();
|
||||
|
||||
@PreAuthorize("denyAll()")
|
||||
@Mask("methodmask")
|
||||
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
|
||||
Mono<String> preAuthorizeDeniedMethodWithMaskAnnotation();
|
||||
|
||||
@PreAuthorize("denyAll()")
|
||||
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
|
||||
Mono<String> preAuthorizeDeniedMethodWithNoMaskAnnotation();
|
||||
|
||||
@NullDenied(role = "ADMIN")
|
||||
|
@ -80,33 +79,32 @@ public interface ReactiveMethodSecurityService {
|
|||
|
||||
@PostAuthorize("denyAll()")
|
||||
@Mask("methodmask")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
|
||||
Mono<String> postAuthorizeDeniedMethodWithMaskAnnotation();
|
||||
|
||||
@PostAuthorize("denyAll()")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
|
||||
Mono<String> postAuthorizeDeniedMethodWithNoMaskAnnotation();
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Mask(expression = "@myMasker.getMask()")
|
||||
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
|
||||
Mono<String> preAuthorizeWithMaskAnnotationUsingBean();
|
||||
|
||||
@PostAuthorize("hasRole('ADMIN')")
|
||||
@Mask(expression = "@myMasker.getMask(returnObject)")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
|
||||
Mono<String> postAuthorizeWithMaskAnnotationUsingBean();
|
||||
|
||||
@PreAuthorize("@authz.checkReactiveResult(#result)")
|
||||
@PostAuthorize("@authz.checkReactiveResult(!#result)")
|
||||
@AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class,
|
||||
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
|
||||
Mono<String> checkCustomResult(boolean result);
|
||||
|
||||
class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
return "***";
|
||||
}
|
||||
|
||||
|
@ -115,8 +113,8 @@ public interface ReactiveMethodSecurityService {
|
|||
class StartMaskingHandlerChild extends StarMaskingHandler {
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
return super.handle(methodInvocation, result) + "-child";
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
return super.handleDeniedInvocation(methodInvocation, result) + "-child";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -130,7 +128,7 @@ public interface ReactiveMethodSecurityService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class);
|
||||
if (mask == null) {
|
||||
mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod().getDeclaringClass(), Mask.class);
|
||||
|
@ -140,7 +138,7 @@ public interface ReactiveMethodSecurityService {
|
|||
|
||||
}
|
||||
|
||||
class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
MaskValueResolver maskValueResolver;
|
||||
|
||||
|
@ -149,7 +147,16 @@ public interface ReactiveMethodSecurityService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||
public Object handleDeniedInvocation(MethodInvocation mi, AuthorizationResult authorizationResult) {
|
||||
Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class);
|
||||
if (mask == null) {
|
||||
mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class);
|
||||
}
|
||||
return this.maskValueResolver.resolveValue(mask, mi, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
|
||||
AuthorizationResult authorizationResult) {
|
||||
MethodInvocation mi = methodInvocationResult.getMethodInvocation();
|
||||
Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class);
|
||||
|
@ -185,31 +192,38 @@ public interface ReactiveMethodSecurityService {
|
|||
|
||||
}
|
||||
|
||||
class PostMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
class PostMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return "***";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
static String MASK = "****-****-****-";
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return "***";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult contextObject, AuthorizationResult result) {
|
||||
String cardNumber = (String) contextObject.getResult();
|
||||
return MASK + cardNumber.substring(cardNumber.length() - 4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class NullPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
class NullPostProcessor implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return null;
|
||||
}
|
||||
|
@ -231,7 +245,7 @@ public interface ReactiveMethodSecurityService {
|
|||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@PostAuthorize("hasRole('{value}')")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = NullPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = NullPostProcessor.class)
|
||||
@interface NullDenied {
|
||||
|
||||
String role();
|
||||
|
|
|
@ -18,7 +18,8 @@ package org.springframework.security.config.annotation.method.configuration;
|
|||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationDeniedException;
|
||||
|
||||
public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurityService {
|
||||
|
||||
|
@ -34,7 +35,7 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
|
|||
|
||||
@Override
|
||||
public Mono<String> preAuthorizeThrowAccessDeniedManually() {
|
||||
return Mono.error(new AccessDeniedException("Access Denied"));
|
||||
return Mono.error(new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -44,7 +45,7 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
|
|||
|
||||
@Override
|
||||
public Mono<String> postAuthorizeThrowAccessDeniedManually() {
|
||||
return Mono.error(new AccessDeniedException("Access Denied"));
|
||||
return Mono.error(new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
package org.springframework.security.config.annotation.method.configuration;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
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.HandleAuthorizationDenied;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||
|
||||
public class UserRecordWithEmailProtected {
|
||||
|
@ -38,15 +40,21 @@ public class UserRecordWithEmailProtected {
|
|||
}
|
||||
|
||||
@PostAuthorize("hasRole('ADMIN')")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = EmailMaskingPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = EmailMaskingPostProcessor.class)
|
||||
public String email() {
|
||||
return this.email;
|
||||
}
|
||||
|
||||
public static class EmailMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
public static class EmailMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return "***";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
|
||||
AuthorizationResult authorizationResult) {
|
||||
String email = (String) methodInvocationResult.getResult();
|
||||
return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*");
|
||||
|
|
|
@ -28,10 +28,8 @@ import org.springframework.context.MessageSourceAware;
|
|||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.SpringSecurityMessageSource;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -42,8 +40,8 @@ import org.springframework.util.Assert;
|
|||
* @author Josh Cummings
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class ObservationAuthorizationManager<T> implements AuthorizationManager<T>, MessageSourceAware,
|
||||
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
|
||||
public final class ObservationAuthorizationManager<T>
|
||||
implements AuthorizationManager<T>, MessageSourceAware, MethodAuthorizationDeniedHandler {
|
||||
|
||||
private final ObservationRegistry registry;
|
||||
|
||||
|
@ -55,17 +53,12 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
|
|||
|
||||
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
|
||||
public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager<T> delegate) {
|
||||
this.registry = registry;
|
||||
this.delegate = delegate;
|
||||
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
|
||||
this.handler = h;
|
||||
}
|
||||
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
|
||||
this.postProcessor = p;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -116,14 +109,14 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
return this.handler.handle(methodInvocation, authorizationResult);
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
|
||||
return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,10 +25,8 @@ import reactor.core.publisher.Mono;
|
|||
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
@ -38,8 +36,8 @@ import org.springframework.util.Assert;
|
|||
* @author Josh Cummings
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class ObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T>,
|
||||
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
|
||||
public final class ObservationReactiveAuthorizationManager<T>
|
||||
implements ReactiveAuthorizationManager<T>, MethodAuthorizationDeniedHandler {
|
||||
|
||||
private final ObservationRegistry registry;
|
||||
|
||||
|
@ -49,8 +47,6 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
|
|||
|
||||
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
|
||||
public ObservationReactiveAuthorizationManager(ObservationRegistry registry,
|
||||
ReactiveAuthorizationManager<T> delegate) {
|
||||
this.registry = registry;
|
||||
|
@ -58,9 +54,6 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
|
|||
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
|
||||
this.handler = h;
|
||||
}
|
||||
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
|
||||
this.postProcessor = p;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -99,14 +92,14 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
return this.handler.handle(methodInvocation, authorizationResult);
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
|
||||
return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ import org.springframework.security.authorization.AuthorizationDecision;
|
|||
import org.springframework.security.authorization.AuthorizationDeniedException;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
|
@ -57,7 +56,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
|
|||
|
||||
private final AuthorizationManager<MethodInvocationResult> authorizationManager;
|
||||
|
||||
private final MethodAuthorizationDeniedPostProcessor defaultPostProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private int order;
|
||||
|
||||
|
@ -119,7 +118,16 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
|
|||
*/
|
||||
@Override
|
||||
public Object invoke(MethodInvocation mi) throws Throwable {
|
||||
Object result = mi.proceed();
|
||||
Object result;
|
||||
try {
|
||||
result = mi.proceed();
|
||||
}
|
||||
catch (AuthorizationDeniedException ex) {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
return handler.handleDeniedInvocation(mi, ex);
|
||||
}
|
||||
return this.defaultHandler.handleDeniedInvocation(mi, ex);
|
||||
}
|
||||
return attemptAuthorization(mi, result);
|
||||
}
|
||||
|
||||
|
@ -174,28 +182,22 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
|
|||
private Object attemptAuthorization(MethodInvocation mi, Object result) {
|
||||
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
|
||||
MethodInvocationResult object = new MethodInvocationResult(mi, result);
|
||||
AuthorizationDecision decision;
|
||||
try {
|
||||
decision = this.authorizationManager.check(this::getAuthentication, object);
|
||||
}
|
||||
catch (AuthorizationDeniedException denied) {
|
||||
return postProcess(object, denied);
|
||||
}
|
||||
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, object);
|
||||
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, object, decision);
|
||||
if (decision != null && !decision.isGranted()) {
|
||||
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
|
||||
+ this.authorizationManager + " and decision " + decision));
|
||||
return postProcess(object, decision);
|
||||
return handlePostInvocationDenied(object, decision);
|
||||
}
|
||||
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object postProcess(MethodInvocationResult mi, AuthorizationResult decision) {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
||||
return postProcessableDecision.postProcessResult(mi, decision);
|
||||
private Object handlePostInvocationDenied(MethodInvocationResult mi, AuthorizationDecision decision) {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler deniedHandler) {
|
||||
return deniedHandler.handleDeniedInvocationResult(mi, decision);
|
||||
}
|
||||
return this.defaultPostProcessor.postProcessResult(mi, decision);
|
||||
return this.defaultHandler.handleDeniedInvocationResult(mi, decision);
|
||||
}
|
||||
|
||||
private Authentication getAuthentication() {
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.aopalliance.intercept.MethodInvocation;
|
|||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Signal;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
|
@ -60,7 +61,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
|
|||
|
||||
private int order = AuthorizationInterceptorsOrder.LAST.getOrder();
|
||||
|
||||
private final MethodAuthorizationDeniedPostProcessor defaultPostProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
/**
|
||||
* Creates an instance for the {@link PostAuthorize} annotation.
|
||||
|
@ -118,27 +119,39 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
|
|||
+ "(for example, a Mono or Flux) or the function must be a Kotlin coroutine "
|
||||
+ "in order to support Reactor Context");
|
||||
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
|
||||
Function<Object, Mono<?>> postAuthorize = (result) -> postAuthorize(authentication, mi, result);
|
||||
Function<Signal<?>, Mono<?>> postAuthorize = (signal) -> {
|
||||
if (signal.isOnComplete()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
if (!signal.hasError()) {
|
||||
return postAuthorize(authentication, mi, signal.get());
|
||||
}
|
||||
if (signal.getThrowable() instanceof AuthorizationDeniedException denied) {
|
||||
return postProcess(denied, mi);
|
||||
}
|
||||
return Mono.error(signal.getThrowable());
|
||||
};
|
||||
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type);
|
||||
if (hasFlowReturnType) {
|
||||
if (isSuspendingFunction) {
|
||||
Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi);
|
||||
return Flux.from(publisher).flatMap(postAuthorize);
|
||||
return Flux.from(publisher).materialize().flatMap(postAuthorize);
|
||||
}
|
||||
else {
|
||||
Assert.state(adapter != null, () -> "The returnType " + type + " on " + method
|
||||
+ " must have a org.springframework.core.ReactiveAdapter registered");
|
||||
Flux<?> response = Flux.defer(() -> adapter.toPublisher(ReactiveMethodInvocationUtils.proceed(mi)))
|
||||
.materialize()
|
||||
.flatMap(postAuthorize);
|
||||
return KotlinDelegate.asFlow(response);
|
||||
}
|
||||
}
|
||||
Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi);
|
||||
if (isMultiValue(type, adapter)) {
|
||||
Flux<?> flux = Flux.from(publisher).flatMap(postAuthorize);
|
||||
Flux<?> flux = Flux.from(publisher).materialize().flatMap(postAuthorize);
|
||||
return (adapter != null) ? adapter.fromPublisher(flux) : flux;
|
||||
}
|
||||
Mono<?> mono = Mono.from(publisher).flatMap(postAuthorize);
|
||||
Mono<?> mono = Mono.from(publisher).materialize().flatMap(postAuthorize);
|
||||
return (adapter != null) ? adapter.fromPublisher(mono) : mono;
|
||||
}
|
||||
|
||||
|
@ -153,17 +166,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
|
|||
MethodInvocationResult invocationResult = new MethodInvocationResult(mi, result);
|
||||
return this.authorizationManager.check(authentication, invocationResult)
|
||||
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
|
||||
.materialize()
|
||||
.flatMap((signal) -> {
|
||||
if (!signal.hasError()) {
|
||||
AuthorizationDecision decision = signal.get();
|
||||
return postProcess(decision, invocationResult);
|
||||
}
|
||||
if (signal.getThrowable() instanceof AuthorizationDeniedException denied) {
|
||||
return postProcess(denied, invocationResult);
|
||||
}
|
||||
return Mono.error(signal.getThrowable());
|
||||
});
|
||||
.flatMap((decision) -> postProcess(decision, invocationResult));
|
||||
}
|
||||
|
||||
private Mono<Object> postProcess(AuthorizationResult decision, MethodInvocationResult methodInvocationResult) {
|
||||
|
@ -171,10 +174,24 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
|
|||
return Mono.just(methodInvocationResult.getResult());
|
||||
}
|
||||
return Mono.fromSupplier(() -> {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
||||
return postProcessableDecision.postProcessResult(methodInvocationResult, decision);
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
return handler.handleDeniedInvocationResult(methodInvocationResult, decision);
|
||||
}
|
||||
return this.defaultPostProcessor.postProcessResult(methodInvocationResult, decision);
|
||||
return this.defaultHandler.handleDeniedInvocationResult(methodInvocationResult, decision);
|
||||
}).flatMap((processedResult) -> {
|
||||
if (Mono.class.isAssignableFrom(processedResult.getClass())) {
|
||||
return (Mono<?>) processedResult;
|
||||
}
|
||||
return Mono.justOrEmpty(processedResult);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Object> postProcess(AuthorizationResult decision, MethodInvocation methodInvocation) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
return handler.handleDeniedInvocation(methodInvocation, decision);
|
||||
}
|
||||
return this.defaultHandler.handleDeniedInvocation(methodInvocation, decision);
|
||||
}).flatMap((processedResult) -> {
|
||||
if (Mono.class.isAssignableFrom(processedResult.getClass())) {
|
||||
return (Mono<?>) processedResult;
|
||||
|
|
|
@ -261,14 +261,33 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
|
|||
return handle(mi, decision);
|
||||
}
|
||||
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
|
||||
return mi.proceed();
|
||||
return proceed(mi);
|
||||
}
|
||||
|
||||
private Object proceed(MethodInvocation mi) throws Throwable {
|
||||
try {
|
||||
return mi.proceed();
|
||||
}
|
||||
catch (AuthorizationDeniedException ex) {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
return handler.handleDeniedInvocation(mi, ex);
|
||||
}
|
||||
return this.defaultHandler.handleDeniedInvocation(mi, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Object handle(MethodInvocation mi, AuthorizationDeniedException denied) {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
return handler.handleDeniedInvocation(mi, denied);
|
||||
}
|
||||
return this.defaultHandler.handleDeniedInvocation(mi, denied);
|
||||
}
|
||||
|
||||
private Object handle(MethodInvocation mi, AuthorizationResult decision) {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
return handler.handle(mi, decision);
|
||||
return handler.handleDeniedInvocation(mi, decision);
|
||||
}
|
||||
return this.defaultHandler.handle(mi, decision);
|
||||
return this.defaultHandler.handleDeniedInvocation(mi, decision);
|
||||
}
|
||||
|
||||
private Authentication getAuthentication() {
|
||||
|
|
|
@ -142,19 +142,12 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
|
|||
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
|
||||
return this.authorizationManager.check(authentication, mi)
|
||||
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
|
||||
.materialize()
|
||||
.flatMapMany((signal) -> {
|
||||
if (!signal.hasError()) {
|
||||
AuthorizationDecision decision = signal.get();
|
||||
if (decision.isGranted()) {
|
||||
return mapping;
|
||||
}
|
||||
return postProcess(decision, mi);
|
||||
.flatMapMany((decision) -> {
|
||||
if (decision.isGranted()) {
|
||||
return mapping.onErrorResume(AuthorizationDeniedException.class,
|
||||
(deniedEx) -> postProcess(deniedEx, mi));
|
||||
}
|
||||
if (signal.getThrowable() instanceof AuthorizationDeniedException denied) {
|
||||
return postProcess(denied, mi);
|
||||
}
|
||||
return Mono.error(signal.getThrowable());
|
||||
return postProcess(decision, mi);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -162,28 +155,21 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
|
|||
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
|
||||
return this.authorizationManager.check(authentication, mi)
|
||||
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
|
||||
.materialize()
|
||||
.flatMap((signal) -> {
|
||||
if (!signal.hasError()) {
|
||||
AuthorizationDecision decision = signal.get();
|
||||
if (decision.isGranted()) {
|
||||
return mapping;
|
||||
}
|
||||
return postProcess(decision, mi);
|
||||
.flatMap((decision) -> {
|
||||
if (decision.isGranted()) {
|
||||
return mapping.onErrorResume(AuthorizationDeniedException.class,
|
||||
(deniedEx) -> postProcess(deniedEx, mi));
|
||||
}
|
||||
if (signal.getThrowable() instanceof AuthorizationDeniedException denied) {
|
||||
return postProcess(denied, mi);
|
||||
}
|
||||
return Mono.error(signal.getThrowable());
|
||||
return postProcess(decision, mi);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Object> postProcess(AuthorizationResult decision, MethodInvocation mi) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
return handler.handle(mi, decision);
|
||||
return handler.handleDeniedInvocation(mi, decision);
|
||||
}
|
||||
return this.defaultHandler.handle(mi, decision);
|
||||
return this.defaultHandler.handleDeniedInvocation(mi, decision);
|
||||
}).flatMap((result) -> {
|
||||
if (Mono.class.isAssignableFrom(result.getClass())) {
|
||||
return (Mono<?>) result;
|
||||
|
|
|
@ -25,32 +25,26 @@ import java.lang.annotation.Target;
|
|||
|
||||
/**
|
||||
* Annotation for specifying handling behavior when an authorization denied happens in
|
||||
* method security
|
||||
* method security or an
|
||||
* {@link org.springframework.security.authorization.AuthorizationDeniedException} is
|
||||
* thrown during method invocation
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.3
|
||||
* @see org.springframework.security.access.prepost.PreAuthorize
|
||||
* @see org.springframework.security.access.prepost.PostAuthorize
|
||||
* @see AuthorizationManagerAfterMethodInterceptor
|
||||
* @see AuthorizationManagerBeforeMethodInterceptor
|
||||
*/
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface AuthorizationDeniedHandler {
|
||||
public @interface HandleAuthorizationDenied {
|
||||
|
||||
/**
|
||||
* The {@link MethodAuthorizationDeniedHandler} used to handle denied authorizations
|
||||
* from {@link org.springframework.security.access.prepost.PreAuthorize}
|
||||
* The {@link MethodAuthorizationDeniedHandler} used to handle denied authorization
|
||||
* results
|
||||
* @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;
|
||||
|
||||
}
|
|
@ -27,13 +27,14 @@ import org.springframework.security.authorization.AuthorizationResult;
|
|||
* @author Marcus da Coregio
|
||||
* @since 6.3
|
||||
* @see org.springframework.security.access.prepost.PreAuthorize
|
||||
* @see org.springframework.security.access.prepost.PostAuthorize
|
||||
*/
|
||||
public interface MethodAuthorizationDeniedHandler {
|
||||
|
||||
/**
|
||||
* Handle denied method invocations, implementations might either throw an
|
||||
* {@link org.springframework.security.access.AccessDeniedException} or a replacement
|
||||
* result instead of invoking the method, e.g. a masked value.
|
||||
* {@link org.springframework.security.authorization.AuthorizationDeniedException} or
|
||||
* a replacement result instead of invoking the method, e.g. a masked value.
|
||||
* @param methodInvocation the {@link MethodInvocation} related to the authorization
|
||||
* denied
|
||||
* @param authorizationResult the authorization denied result
|
||||
|
@ -41,6 +42,24 @@ public interface MethodAuthorizationDeniedHandler {
|
|||
* {@link reactor.core.publisher.Mono} for reactive applications
|
||||
*/
|
||||
@Nullable
|
||||
Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult);
|
||||
Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult);
|
||||
|
||||
/**
|
||||
* Handle denied method invocations, implementations might either throw an
|
||||
* {@link org.springframework.security.authorization.AuthorizationDeniedException} or
|
||||
* a replacement result instead of invoking the method, e.g. a masked value. By
|
||||
* default, this method invokes
|
||||
* {@link #handleDeniedInvocation(MethodInvocation, AuthorizationResult)}.
|
||||
* @param methodInvocationResult the object containing the {@link MethodInvocation}
|
||||
* and the result produced
|
||||
* @param authorizationResult the authorization denied result
|
||||
* @return a replacement result for the denied method invocation, or null, or a
|
||||
* {@link reactor.core.publisher.Mono} for reactive applications
|
||||
*/
|
||||
@Nullable
|
||||
default Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
|
||||
AuthorizationResult authorizationResult) {
|
||||
return handleDeniedInvocation(methodInvocationResult.getMethodInvocation(), authorizationResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* 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 org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
|
||||
/**
|
||||
* An interface to define a strategy to handle denied method invocation results
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.3
|
||||
* @see org.springframework.security.access.prepost.PostAuthorize
|
||||
*/
|
||||
public interface MethodAuthorizationDeniedPostProcessor {
|
||||
|
||||
/**
|
||||
* Post-process the denied result produced by a method invocation, implementations
|
||||
* might either throw an
|
||||
* {@link org.springframework.security.access.AccessDeniedException} or return a
|
||||
* replacement result instead of the denied result, e.g. a masked value.
|
||||
* @param methodInvocationResult the object containing the method invocation and the
|
||||
* result produced
|
||||
* @param authorizationResult the {@link AuthorizationResult} containing the
|
||||
* authorization denied details
|
||||
* @return a replacement result for the denied result, or null, or a
|
||||
* {@link reactor.core.publisher.Mono} for reactive applications
|
||||
*/
|
||||
@Nullable
|
||||
Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult);
|
||||
|
||||
}
|
|
@ -38,7 +38,7 @@ import org.springframework.security.core.Authentication;
|
|||
* @since 5.6
|
||||
*/
|
||||
public final class PostAuthorizeAuthorizationManager
|
||||
implements AuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
|
||||
implements AuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedHandler {
|
||||
|
||||
private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
||||
|
||||
|
@ -96,11 +96,19 @@ public final class PostAuthorizeAuthorizationManager
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
|
||||
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
|
||||
return postAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
|
||||
AuthorizationResult authorizationResult) {
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation());
|
||||
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
|
||||
return postAuthorizeAttribute.getPostProcessor().postProcessResult(methodInvocationResult, authorizationResult);
|
||||
return postAuthorizeAttribute.getHandler()
|
||||
.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,16 +27,16 @@ import org.springframework.util.Assert;
|
|||
*/
|
||||
class PostAuthorizeExpressionAttribute extends ExpressionAttribute {
|
||||
|
||||
private final MethodAuthorizationDeniedPostProcessor postProcessor;
|
||||
private final MethodAuthorizationDeniedHandler handler;
|
||||
|
||||
PostAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedPostProcessor postProcessor) {
|
||||
PostAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedHandler handler) {
|
||||
super(expression);
|
||||
Assert.notNull(postProcessor, "postProcessor cannot be null");
|
||||
this.postProcessor = postProcessor;
|
||||
Assert.notNull(handler, "handler cannot be null");
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
MethodAuthorizationDeniedPostProcessor getPostProcessor() {
|
||||
return this.postProcessor;
|
||||
MethodAuthorizationDeniedHandler getHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,12 +38,12 @@ import org.springframework.util.Assert;
|
|||
*/
|
||||
final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
|
||||
|
||||
private final MethodAuthorizationDeniedPostProcessor defaultPostProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private Function<Class<? extends MethodAuthorizationDeniedPostProcessor>, MethodAuthorizationDeniedPostProcessor> postProcessorResolver;
|
||||
private Function<Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
|
||||
|
||||
PostAuthorizeExpressionAttributeRegistry() {
|
||||
this.postProcessorResolver = (clazz) -> this.defaultPostProcessor;
|
||||
this.handlerResolver = (clazz) -> this.defaultHandler;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -55,22 +55,22 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
|
|||
return ExpressionAttribute.NULL_ATTRIBUTE;
|
||||
}
|
||||
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value());
|
||||
MethodAuthorizationDeniedPostProcessor postProcessor = resolvePostProcessor(method, targetClass);
|
||||
return new PostAuthorizeExpressionAttribute(expression, postProcessor);
|
||||
MethodAuthorizationDeniedHandler deniedHandler = resolveHandler(method, targetClass);
|
||||
return new PostAuthorizeExpressionAttribute(expression, deniedHandler);
|
||||
}
|
||||
|
||||
private MethodAuthorizationDeniedPostProcessor resolvePostProcessor(Method method, Class<?> targetClass) {
|
||||
Function<AnnotatedElement, AuthorizationDeniedHandler> lookup = AuthorizationAnnotationUtils
|
||||
.withDefaults(AuthorizationDeniedHandler.class);
|
||||
AuthorizationDeniedHandler deniedHandler = lookup.apply(method);
|
||||
private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) {
|
||||
Function<AnnotatedElement, HandleAuthorizationDenied> lookup = AuthorizationAnnotationUtils
|
||||
.withDefaults(HandleAuthorizationDenied.class);
|
||||
HandleAuthorizationDenied deniedHandler = lookup.apply(method);
|
||||
if (deniedHandler != null) {
|
||||
return this.postProcessorResolver.apply(deniedHandler.postProcessorClass());
|
||||
return this.handlerResolver.apply(deniedHandler.handlerClass());
|
||||
}
|
||||
deniedHandler = lookup.apply(targetClass(method, targetClass));
|
||||
if (deniedHandler != null) {
|
||||
return this.postProcessorResolver.apply(deniedHandler.postProcessorClass());
|
||||
return this.handlerResolver.apply(deniedHandler.handlerClass());
|
||||
}
|
||||
return this.defaultPostProcessor;
|
||||
return this.defaultHandler;
|
||||
}
|
||||
|
||||
private PostAuthorize findPostAuthorizeAnnotation(Method method, Class<?> targetClass) {
|
||||
|
@ -86,23 +86,23 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
|
|||
*/
|
||||
void setApplicationContext(ApplicationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
this.postProcessorResolver = (postProcessorClass) -> resolvePostProcessor(context, postProcessorClass);
|
||||
this.handlerResolver = (clazz) -> resolveHandler(context, clazz);
|
||||
}
|
||||
|
||||
private MethodAuthorizationDeniedPostProcessor resolvePostProcessor(ApplicationContext context,
|
||||
Class<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass) {
|
||||
if (postProcessorClass == this.defaultPostProcessor.getClass()) {
|
||||
return this.defaultPostProcessor;
|
||||
private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context,
|
||||
Class<? extends MethodAuthorizationDeniedHandler> handlerClass) {
|
||||
if (handlerClass == this.defaultHandler.getClass()) {
|
||||
return this.defaultHandler;
|
||||
}
|
||||
String[] beanNames = context.getBeanNamesForType(postProcessorClass);
|
||||
String[] beanNames = context.getBeanNamesForType(handlerClass);
|
||||
if (beanNames.length == 0) {
|
||||
throw new IllegalStateException("Could not find a bean of type " + postProcessorClass.getName());
|
||||
throw new IllegalStateException("Could not find a bean of type " + handlerClass.getName());
|
||||
}
|
||||
if (beanNames.length > 1) {
|
||||
throw new IllegalStateException("Expected to find a single bean of type " + postProcessorClass.getName()
|
||||
throw new IllegalStateException("Expected to find a single bean of type " + handlerClass.getName()
|
||||
+ " but found " + Arrays.toString(beanNames));
|
||||
}
|
||||
return context.getBean(beanNames[0], postProcessorClass);
|
||||
return context.getBean(beanNames[0], handlerClass);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ import org.springframework.util.Assert;
|
|||
* @since 5.8
|
||||
*/
|
||||
public final class PostAuthorizeReactiveAuthorizationManager
|
||||
implements ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
|
||||
implements ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedHandler {
|
||||
|
||||
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
||||
|
||||
|
@ -95,11 +95,19 @@ public final class PostAuthorizeReactiveAuthorizationManager
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
|
||||
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
|
||||
return postAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
|
||||
AuthorizationResult authorizationResult) {
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation());
|
||||
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
|
||||
return postAuthorizeAttribute.getPostProcessor().postProcessResult(methodInvocationResult, authorizationResult);
|
||||
return postAuthorizeAttribute.getHandler()
|
||||
.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -86,10 +86,10 @@ public final class PreAuthorizeAuthorizationManager
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
|
||||
PreAuthorizeExpressionAttribute postAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
|
||||
return postAuthorizeAttribute.getHandler().handle(methodInvocation, authorizationResult);
|
||||
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
|
||||
return preAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -60,9 +60,9 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
|
|||
}
|
||||
|
||||
private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) {
|
||||
Function<AnnotatedElement, AuthorizationDeniedHandler> lookup = AuthorizationAnnotationUtils
|
||||
.withDefaults(AuthorizationDeniedHandler.class);
|
||||
AuthorizationDeniedHandler deniedHandler = lookup.apply(method);
|
||||
Function<AnnotatedElement, HandleAuthorizationDenied> lookup = AuthorizationAnnotationUtils
|
||||
.withDefaults(HandleAuthorizationDenied.class);
|
||||
HandleAuthorizationDenied deniedHandler = lookup.apply(method);
|
||||
if (deniedHandler != null) {
|
||||
return this.handlerResolver.apply(deniedHandler.handlerClass());
|
||||
}
|
||||
|
|
|
@ -90,10 +90,10 @@ public final class PreAuthorizeReactiveAuthorizationManager
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
|
||||
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
|
||||
return preAuthorizeAttribute.getHandler().handle(methodInvocation, authorizationResult);
|
||||
return preAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,11 +31,20 @@ import org.springframework.security.authorization.AuthorizationResult;
|
|||
public final class ThrowingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
|
||||
if (result instanceof AuthorizationDeniedException denied) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
if (authorizationResult instanceof AuthorizationDeniedException denied) {
|
||||
throw denied;
|
||||
}
|
||||
throw new AuthorizationDeniedException("Access Denied", result);
|
||||
throw new AuthorizationDeniedException("Access Denied", authorizationResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
|
||||
AuthorizationResult authorizationResult) {
|
||||
if (authorizationResult instanceof AuthorizationDeniedException denied) {
|
||||
throw denied;
|
||||
}
|
||||
throw new AuthorizationDeniedException("Access Denied", authorizationResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* 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 org.springframework.security.authorization.AuthorizationDeniedException;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
|
||||
/**
|
||||
* An implementation of {@link MethodAuthorizationDeniedPostProcessor} that throws
|
||||
* {@link AuthorizationDeniedException}
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.3
|
||||
*/
|
||||
public final class ThrowingMethodAuthorizationDeniedPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult result) {
|
||||
if (result instanceof AuthorizationDeniedException denied) {
|
||||
throw denied;
|
||||
}
|
||||
throw new AuthorizationDeniedException("Access Denied", result);
|
||||
}
|
||||
|
||||
}
|
|
@ -127,7 +127,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
|||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class)))
|
||||
given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
|
||||
.willAnswer(this::masking);
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
|
@ -147,7 +147,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
|||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class)))
|
||||
given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
|
||||
.willAnswer((invocation) -> {
|
||||
MethodInvocationResult argument = invocation.getArgument(0);
|
||||
if (!"john".equals(argument.getResult())) {
|
||||
|
@ -173,7 +173,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
|||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class)))
|
||||
given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
|
||||
.willAnswer(this::masking);
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
|
@ -192,7 +192,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
|||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class)))
|
||||
given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
|
||||
.willAnswer(this::monoMasking);
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
|
@ -211,7 +211,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
|||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class)))
|
||||
given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
|
||||
.willReturn(null);
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
|
@ -266,7 +266,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
|||
}
|
||||
|
||||
interface HandlingReactiveAuthorizationManager
|
||||
extends ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
|
||||
extends ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedHandler {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -127,7 +127,8 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
|||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||
given(mockReactiveAuthorizationManager.handle(any(), any(AuthorizationResult.class))).willReturn("***");
|
||||
given(mockReactiveAuthorizationManager.handleDeniedInvocation(any(), any(AuthorizationResult.class)))
|
||||
.willReturn("***");
|
||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
|
@ -145,7 +146,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
|||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||
given(mockReactiveAuthorizationManager.handle(any(), any(AuthorizationResult.class)))
|
||||
given(mockReactiveAuthorizationManager.handleDeniedInvocation(any(), any(AuthorizationResult.class)))
|
||||
.willReturn(Mono.just("***"));
|
||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
|
@ -164,7 +165,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
|||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||
given(mockReactiveAuthorizationManager.handle(any(), any(AuthorizationResult.class)))
|
||||
given(mockReactiveAuthorizationManager.handleDeniedInvocation(any(), any(AuthorizationResult.class)))
|
||||
.willReturn(Mono.just("***"));
|
||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
|
|
|
@ -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`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
@ -2274,7 +2273,7 @@ Java::
|
|||
public class NullMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { <1>
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -2295,7 +2294,7 @@ public class User {
|
|||
// ...
|
||||
|
||||
@PreAuthorize(value = "hasAuthority('user:read')")
|
||||
@AuthorizationDeniedHandler(handlerClass = NullMethodAuthorizationDeniedHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = NullMethodAuthorizationDeniedHandler.class)
|
||||
public String getEmail() {
|
||||
return this.email;
|
||||
}
|
||||
|
@ -2308,7 +2307,7 @@ Kotlin::
|
|||
----
|
||||
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 {
|
|||
|
||||
}
|
||||
|
||||
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() {
|
|||
----
|
||||
======
|
||||
|
||||
=== Using with `@PostAuthorize`
|
||||
=== Using the Denied Result From the Method Invocation
|
||||
|
||||
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.
|
||||
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_.
|
||||
|
||||
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:
|
||||
|
||||
[tabs]
|
||||
|
@ -2382,10 +2384,15 @@ Java::
|
|||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class EmailMaskingMethodAuthorizationDeniedPostProcessor implements MethodAuthorizationDeniedPostProcessor { <1>
|
||||
public class EmailMaskingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { <1>
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
return "***";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) {
|
||||
String email = (String) methodInvocationResult.getResult();
|
||||
return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*");
|
||||
}
|
||||
|
@ -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 {
|
|||
// ...
|
||||
|
||||
@PostAuthorize(value = "hasAuthority('user:read')")
|
||||
@AuthorizationDeniedHandler(postProcessorClass = EmailMaskingMethodAuthorizationDeniedPostProcessor.class)
|
||||
@HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler.class)
|
||||
public String getEmail() {
|
||||
return this.email;
|
||||
}
|
||||
|
@ -2418,9 +2425,13 @@ Kotlin::
|
|||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class EmailMaskingMethodAuthorizationDeniedPostProcessor : MethodAuthorizationDeniedPostProcessor {
|
||||
class EmailMaskingMethodAuthorizationDeniedHandler : MethodAuthorizationDeniedHandler {
|
||||
|
||||
override fun postProcessResult(methodInvocationResult: MethodInvocationResult, authorizationResult: AuthorizationResult): Any {
|
||||
override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any {
|
||||
return "***"
|
||||
}
|
||||
|
||||
override fun handleDeniedInvocationResult(methodInvocationResult: MethodInvocationResult, authorizationResult: AuthorizationResult): Any {
|
||||
val email = methodInvocationResult.result as String
|
||||
return email.replace("(^[^@]{3}|(?!^)\\G)[^@]".toRegex(), "$1*")
|
||||
}
|
||||
|
@ -2432,22 +2443,27 @@ class EmailMaskingMethodAuthorizationDeniedPostProcessor : MethodAuthorizationDe
|
|||
class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
fun emailMaskingMethodAuthorizationDeniedPostProcessor(): EmailMaskingMethodAuthorizationDeniedPostProcessor {
|
||||
return EmailMaskingMethodAuthorizationDeniedPostProcessor()
|
||||
fun emailMaskingMethodAuthorizationDeniedHandler(): EmailMaskingMethodAuthorizationDeniedHandler {
|
||||
return EmailMaskingMethodAuthorizationDeniedHandler()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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() {
|
|||
----
|
||||
======
|
||||
|
||||
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 {
|
|||
public class MaskAnnotationDeniedHandler implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class);
|
||||
return mask.value();
|
||||
}
|
||||
|
@ -2539,14 +2555,14 @@ public class SecurityConfig {
|
|||
public class MyService {
|
||||
|
||||
@PreAuthorize(value = "hasAuthority('user:read')")
|
||||
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
|
||||
@Mask("***")
|
||||
public String foo() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
@PreAuthorize(value = "hasAuthority('user:read')")
|
||||
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
|
||||
@Mask("???")
|
||||
public String bar() {
|
||||
return "bar";
|
||||
|
@ -2567,7 +2583,7 @@ annotation class Mask(val value: String)
|
|||
|
||||
class MaskAnnotationDeniedHandler : MethodAuthorizationDeniedHandler {
|
||||
|
||||
override fun handle(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any {
|
||||
override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any {
|
||||
val mask = AnnotationUtils.getAnnotation(methodInvocation.method, Mask::class.java)
|
||||
return mask.value
|
||||
}
|
||||
|
@ -2589,14 +2605,14 @@ class SecurityConfig {
|
|||
class MyService {
|
||||
|
||||
@PreAuthorize(value = "hasAuthority('user:read')")
|
||||
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class)
|
||||
@Mask("***")
|
||||
fun foo(): String {
|
||||
return "foo"
|
||||
}
|
||||
|
||||
@PreAuthorize(value = "hasAuthority('user:read')")
|
||||
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class)
|
||||
@Mask("???")
|
||||
fun bar(): String {
|
||||
return "bar"
|
||||
|
@ -2653,8 +2669,8 @@ fun barWhenDeniedThenReturnQuestionMarks() {
|
|||
|
||||
=== Combining with Meta Annotation Support
|
||||
|
||||
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`:
|
||||
You can also combine the `@HandleAuthorizationDenied` 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 `@HandleAuthorizationDenied` with `@Mask`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
@ -2664,7 +2680,7 @@ Java::
|
|||
----
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
|
||||
public @interface Mask {
|
||||
|
||||
String value();
|
||||
|
@ -2683,7 +2699,7 @@ Kotlin::
|
|||
----
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class)
|
||||
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class)
|
||||
annotation class Mask(val value: String)
|
||||
|
||||
@Mask("***")
|
||||
|
|
Loading…
Reference in New Issue