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:
Marcus Hert Da Coregio 2024-04-09 10:56:40 -03:00
parent 14da8f4fc8
commit 2fbbcc4bd0
31 changed files with 425 additions and 440 deletions

View File

@ -27,22 +27,18 @@ import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ObservationAuthorizationManager; import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler; 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.Authentication;
import org.springframework.util.function.SingletonSupplier; import org.springframework.util.function.SingletonSupplier;
final class DeferringObservationAuthorizationManager<T> final class DeferringObservationAuthorizationManager<T>
implements AuthorizationManager<T>, MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor { implements AuthorizationManager<T>, MethodAuthorizationDeniedHandler {
private final Supplier<AuthorizationManager<T>> delegate; private final Supplier<AuthorizationManager<T>> delegate;
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
DeferringObservationAuthorizationManager(ObjectProvider<ObservationRegistry> provider, DeferringObservationAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
AuthorizationManager<T> delegate) { AuthorizationManager<T> delegate) {
this.delegate = SingletonSupplier.of(() -> { this.delegate = SingletonSupplier.of(() -> {
@ -55,9 +51,6 @@ final class DeferringObservationAuthorizationManager<T>
if (delegate instanceof MethodAuthorizationDeniedHandler h) { if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h; this.handler = h;
} }
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
this.postProcessor = p;
}
} }
@Override @Override
@ -66,14 +59,14 @@ final class DeferringObservationAuthorizationManager<T>
} }
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handle(methodInvocation, authorizationResult); return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
} }
@Override @Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult, public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) { AuthorizationResult authorizationResult) {
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult); return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
} }
} }

View File

@ -28,22 +28,18 @@ import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager; import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler; 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.Authentication;
import org.springframework.util.function.SingletonSupplier; import org.springframework.util.function.SingletonSupplier;
final class DeferringObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T>, final class DeferringObservationReactiveAuthorizationManager<T>
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor { implements ReactiveAuthorizationManager<T>, MethodAuthorizationDeniedHandler {
private final Supplier<ReactiveAuthorizationManager<T>> delegate; private final Supplier<ReactiveAuthorizationManager<T>> delegate;
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
DeferringObservationReactiveAuthorizationManager(ObjectProvider<ObservationRegistry> provider, DeferringObservationReactiveAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
ReactiveAuthorizationManager<T> delegate) { ReactiveAuthorizationManager<T> delegate) {
this.delegate = SingletonSupplier.of(() -> { this.delegate = SingletonSupplier.of(() -> {
@ -56,9 +52,6 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
if (delegate instanceof MethodAuthorizationDeniedHandler h) { if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h; this.handler = h;
} }
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
this.postProcessor = p;
}
} }
@Override @Override
@ -67,14 +60,14 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
} }
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handle(methodInvocation, authorizationResult); return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
} }
@Override @Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult, public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) { AuthorizationResult authorizationResult) {
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult); return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
} }
} }

View File

@ -39,10 +39,9 @@ import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter; import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.method.AuthorizationDeniedHandler;
import org.springframework.security.authorization.method.AuthorizeReturnObject; import org.springframework.security.authorization.method.AuthorizeReturnObject;
import org.springframework.security.authorization.method.HandleAuthorizationDenied;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -129,32 +128,32 @@ public interface MethodSecurityService {
void repeatedAnnotations(); void repeatedAnnotations();
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class) @HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
String preAuthorizeGetCardNumberIfAdmin(String cardNumber); String preAuthorizeGetCardNumberIfAdmin(String cardNumber);
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class) @HandleAuthorizationDenied(handlerClass = StartMaskingHandlerChild.class)
String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber); String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class) @HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
String preAuthorizeThrowAccessDeniedManually(); String preAuthorizeThrowAccessDeniedManually();
@PostAuthorize("hasRole('ADMIN')") @PostAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class) @HandleAuthorizationDenied(handlerClass = CardNumberMaskingPostProcessor.class)
String postAuthorizeGetCardNumberIfAdmin(String cardNumber); String postAuthorizeGetCardNumberIfAdmin(String cardNumber);
@PostAuthorize("hasRole('ADMIN')") @PostAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class) @HandleAuthorizationDenied(handlerClass = PostMaskingPostProcessor.class)
String postAuthorizeThrowAccessDeniedManually(); String postAuthorizeThrowAccessDeniedManually();
@PreAuthorize("denyAll()") @PreAuthorize("denyAll()")
@Mask("methodmask") @Mask("methodmask")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
String preAuthorizeDeniedMethodWithMaskAnnotation(); String preAuthorizeDeniedMethodWithMaskAnnotation();
@PreAuthorize("denyAll()") @PreAuthorize("denyAll()")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
String preAuthorizeDeniedMethodWithNoMaskAnnotation(); String preAuthorizeDeniedMethodWithNoMaskAnnotation();
@NullDenied(role = "ADMIN") @NullDenied(role = "ADMIN")
@ -162,40 +161,39 @@ public interface MethodSecurityService {
@PostAuthorize("denyAll()") @PostAuthorize("denyAll()")
@Mask("methodmask") @Mask("methodmask")
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
String postAuthorizeDeniedMethodWithMaskAnnotation(); String postAuthorizeDeniedMethodWithMaskAnnotation();
@PostAuthorize("denyAll()") @PostAuthorize("denyAll()")
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
String postAuthorizeDeniedMethodWithNoMaskAnnotation(); String postAuthorizeDeniedMethodWithNoMaskAnnotation();
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@Mask(expression = "@myMasker.getMask()") @Mask(expression = "@myMasker.getMask()")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
String preAuthorizeWithMaskAnnotationUsingBean(); String preAuthorizeWithMaskAnnotationUsingBean();
@PostAuthorize("hasRole('ADMIN')") @PostAuthorize("hasRole('ADMIN')")
@Mask(expression = "@myMasker.getMask(returnObject)") @Mask(expression = "@myMasker.getMask(returnObject)")
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
String postAuthorizeWithMaskAnnotationUsingBean(); String postAuthorizeWithMaskAnnotationUsingBean();
@AuthorizeReturnObject @AuthorizeReturnObject
UserRecordWithEmailProtected getUserRecordWithEmailProtected(); UserRecordWithEmailProtected getUserRecordWithEmailProtected();
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = UserFallbackDeniedHandler.class) @HandleAuthorizationDenied(handlerClass = UserFallbackDeniedHandler.class)
UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized(); UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized();
@PreAuthorize("@authz.checkResult(#result)") @PreAuthorize("@authz.checkResult(#result)")
@PostAuthorize("@authz.checkResult(!#result)") @PostAuthorize("@authz.checkResult(!#result)")
@AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class, @HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
String checkCustomResult(boolean result); String checkCustomResult(boolean result);
class StarMaskingHandler implements MethodAuthorizationDeniedHandler { class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
return "***"; return "***";
} }
@ -204,8 +202,8 @@ public interface MethodSecurityService {
class StartMaskingHandlerChild extends StarMaskingHandler { class StartMaskingHandlerChild extends StarMaskingHandler {
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
return super.handle(methodInvocation, result) + "-child"; return super.handleDeniedInvocation(methodInvocation, result) + "-child";
} }
} }
@ -218,7 +216,6 @@ public interface MethodSecurityService {
this.maskValueResolver = new MaskValueResolver(context); this.maskValueResolver = new MaskValueResolver(context);
} }
@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class); Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class);
if (mask == null) { if (mask == null) {
@ -227,9 +224,15 @@ public interface MethodSecurityService {
return this.maskValueResolver.resolveValue(mask, methodInvocation, null); 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; MaskValueResolver maskValueResolver;
@ -238,7 +241,16 @@ public interface MethodSecurityService {
} }
@Override @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) { AuthorizationResult authorizationResult) {
MethodInvocation mi = methodInvocationResult.getMethodInvocation(); MethodInvocation mi = methodInvocationResult.getMethodInvocation();
Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class); Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class);
@ -274,31 +286,38 @@ public interface MethodSecurityService {
} }
class PostMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { class PostMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
@Override @Override
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) { public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return "***"; return "***";
} }
} }
class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
static String MASK = "****-****-****-"; static String MASK = "****-****-****-";
@Override @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(); String cardNumber = (String) contextObject.getResult();
return MASK + cardNumber.substring(cardNumber.length() - 4); return MASK + cardNumber.substring(cardNumber.length() - 4);
} }
} }
class NullPostProcessor implements MethodAuthorizationDeniedPostProcessor { class NullPostProcessor implements MethodAuthorizationDeniedHandler {
@Override @Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult, public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) { AuthorizationResult authorizationResult) {
return null; return null;
} }
@ -320,7 +339,7 @@ public interface MethodSecurityService {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Inherited @Inherited
@PostAuthorize("hasRole('{role}')") @PostAuthorize("hasRole('{role}')")
@AuthorizationDeniedHandler(postProcessorClass = NullPostProcessor.class) @HandleAuthorizationDenied(handlerClass = NullPostProcessor.class)
@interface NullDenied { @interface NullDenied {
String role(); String role();
@ -333,7 +352,8 @@ public interface MethodSecurityService {
"Protected"); "Protected");
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return FALLBACK; return FALLBACK;
} }

View File

@ -18,7 +18,8 @@ package org.springframework.security.config.annotation.method.configuration;
import java.util.List; 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.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -144,12 +145,12 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
@Override @Override
public String preAuthorizeThrowAccessDeniedManually() { public String preAuthorizeThrowAccessDeniedManually() {
throw new AccessDeniedException("Access Denied"); throw new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false));
} }
@Override @Override
public String postAuthorizeThrowAccessDeniedManually() { public String postAuthorizeThrowAccessDeniedManually() {
throw new AccessDeniedException("Access Denied"); throw new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false));
} }
@Override @Override

View File

@ -67,7 +67,6 @@ import org.springframework.security.authorization.method.AuthorizationIntercepto
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizeReturnObject; import org.springframework.security.authorization.method.AuthorizeReturnObject;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.PrePostTemplateDefaults; import org.springframework.security.authorization.method.PrePostTemplateDefaults;
import org.springframework.security.config.Customizer; 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.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/** /**
* Tests for {@link PrePostMethodSecurityConfiguration}. * Tests for {@link PrePostMethodSecurityConfiguration}.
@ -783,12 +782,21 @@ public class PrePostMethodSecurityConfigurationTests {
@Test @Test
@WithMockUser(roles = "ADMIN") @WithMockUser(roles = "ADMIN")
void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenNotHandled() { void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenHandled() {
this.spring.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class) this.spring.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class)
.autowire(); .autowire();
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
assertThatExceptionOfType(AccessDeniedException.class) assertThat(service.preAuthorizeThrowAccessDeniedManually()).isEqualTo("***");
.isThrownBy(service::preAuthorizeThrowAccessDeniedManually); }
@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 @Test
@ -813,17 +821,6 @@ public class PrePostMethodSecurityConfigurationTests {
assertThat(result).isEqualTo("classmask"); 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 @Test
@WithMockUser @WithMockUser
void postAuthorizeWhenNullDeniedMetaAnnotationThanWorks() { void postAuthorizeWhenNullDeniedMetaAnnotationThanWorks() {
@ -938,14 +935,13 @@ public class PrePostMethodSecurityConfigurationTests {
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
MethodAuthorizationDeniedHandler handler = this.spring.getContext() MethodAuthorizationDeniedHandler handler = this.spring.getContext()
.getBean(MethodAuthorizationDeniedHandler.class); .getBean(MethodAuthorizationDeniedHandler.class);
MethodAuthorizationDeniedPostProcessor postProcessor = this.spring.getContext()
.getBean(MethodAuthorizationDeniedPostProcessor.class);
assertThat(service.checkCustomResult(false)).isNull(); assertThat(service.checkCustomResult(false)).isNull();
verify(handler).handle(any(), any(Authz.AuthzResult.class)); verify(handler).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
verifyNoInteractions(postProcessor); verify(handler, never()).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class));
clearInvocations(handler);
assertThat(service.checkCustomResult(true)).isNull(); assertThat(service.checkCustomResult(true)).isNull();
verify(postProcessor).postProcessResult(any(), any(Authz.AuthzResult.class)); verify(handler).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class));
verifyNoMoreInteractions(handler); verify(handler, never()).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
} }
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() { private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
@ -1477,18 +1473,11 @@ public class PrePostMethodSecurityConfigurationTests {
MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class); MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class);
MethodAuthorizationDeniedPostProcessor postProcessor = mock(MethodAuthorizationDeniedPostProcessor.class);
@Bean @Bean
MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() { MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() {
return this.handler; return this.handler;
} }
@Bean
MethodAuthorizationDeniedPostProcessor methodAuthorizationDeniedPostProcessor() {
return this.postProcessor;
}
} }
} }

View File

@ -22,7 +22,6 @@ import reactor.test.StepVerifier;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
@ -73,18 +72,6 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
.verifyComplete(); .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 @Test
@WithMockUser @WithMockUser
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() { void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() {
@ -119,9 +106,17 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
ReactiveMethodSecurityService.PostMaskingPostProcessor.class) ReactiveMethodSecurityService.PostMaskingPostProcessor.class)
.autowire(); .autowire();
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
StepVerifier.create(service.postAuthorizeThrowAccessDeniedManually()) StepVerifier.create(service.postAuthorizeThrowAccessDeniedManually()).expectNext("***").verifyComplete();
.expectError(AccessDeniedException.class) }
.verify();
@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 @Test

View File

@ -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.AuthorizationAdvisorProxyFactory.TargetVisitor;
import org.springframework.security.authorization.method.AuthorizeReturnObject; import org.springframework.security.authorization.method.AuthorizeReturnObject;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.test.SpringTestContext; 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.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/** /**
* @author Tadaya Tsuyukubo * @author Tadaya Tsuyukubo
@ -227,14 +226,13 @@ public class ReactiveMethodSecurityConfigurationTests {
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
MethodAuthorizationDeniedHandler handler = this.spring.getContext() MethodAuthorizationDeniedHandler handler = this.spring.getContext()
.getBean(MethodAuthorizationDeniedHandler.class); .getBean(MethodAuthorizationDeniedHandler.class);
MethodAuthorizationDeniedPostProcessor postProcessor = this.spring.getContext()
.getBean(MethodAuthorizationDeniedPostProcessor.class);
assertThat(service.checkCustomResult(false).block()).isNull(); assertThat(service.checkCustomResult(false).block()).isNull();
verify(handler).handle(any(), any(Authz.AuthzResult.class)); verify(handler).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
verifyNoInteractions(postProcessor); verify(handler, never()).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class));
clearInvocations(handler);
assertThat(service.checkCustomResult(true).block()).isNull(); assertThat(service.checkCustomResult(true).block()).isNull();
verify(postProcessor).postProcessResult(any(), any(Authz.AuthzResult.class)); verify(handler).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class));
verifyNoMoreInteractions(handler); verify(handler, never()).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
} }
private static Consumer<User.UserBuilder> authorities(String... authorities) { private static Consumer<User.UserBuilder> authorities(String... authorities) {
@ -383,18 +381,11 @@ public class ReactiveMethodSecurityConfigurationTests {
MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class); MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class);
MethodAuthorizationDeniedPostProcessor postProcessor = mock(MethodAuthorizationDeniedPostProcessor.class);
@Bean @Bean
MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() { MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() {
return this.handler; return this.handler;
} }
@Bean
MethodAuthorizationDeniedPostProcessor methodAuthorizationDeniedPostProcessor() {
return this.postProcessor;
}
} }
} }

View File

@ -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.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.method.AuthorizationDeniedHandler; import org.springframework.security.authorization.method.HandleAuthorizationDenied;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -47,32 +46,32 @@ import org.springframework.util.StringUtils;
public interface ReactiveMethodSecurityService { public interface ReactiveMethodSecurityService {
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class) @HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber); Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber);
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class) @HandleAuthorizationDenied(handlerClass = StartMaskingHandlerChild.class)
Mono<String> preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber); Mono<String> preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class) @HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
Mono<String> preAuthorizeThrowAccessDeniedManually(); Mono<String> preAuthorizeThrowAccessDeniedManually();
@PostAuthorize("hasRole('ADMIN')") @PostAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class) @HandleAuthorizationDenied(handlerClass = CardNumberMaskingPostProcessor.class)
Mono<String> postAuthorizeGetCardNumberIfAdmin(String cardNumber); Mono<String> postAuthorizeGetCardNumberIfAdmin(String cardNumber);
@PostAuthorize("hasRole('ADMIN')") @PostAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class) @HandleAuthorizationDenied(handlerClass = PostMaskingPostProcessor.class)
Mono<String> postAuthorizeThrowAccessDeniedManually(); Mono<String> postAuthorizeThrowAccessDeniedManually();
@PreAuthorize("denyAll()") @PreAuthorize("denyAll()")
@Mask("methodmask") @Mask("methodmask")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
Mono<String> preAuthorizeDeniedMethodWithMaskAnnotation(); Mono<String> preAuthorizeDeniedMethodWithMaskAnnotation();
@PreAuthorize("denyAll()") @PreAuthorize("denyAll()")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
Mono<String> preAuthorizeDeniedMethodWithNoMaskAnnotation(); Mono<String> preAuthorizeDeniedMethodWithNoMaskAnnotation();
@NullDenied(role = "ADMIN") @NullDenied(role = "ADMIN")
@ -80,33 +79,32 @@ public interface ReactiveMethodSecurityService {
@PostAuthorize("denyAll()") @PostAuthorize("denyAll()")
@Mask("methodmask") @Mask("methodmask")
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
Mono<String> postAuthorizeDeniedMethodWithMaskAnnotation(); Mono<String> postAuthorizeDeniedMethodWithMaskAnnotation();
@PostAuthorize("denyAll()") @PostAuthorize("denyAll()")
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
Mono<String> postAuthorizeDeniedMethodWithNoMaskAnnotation(); Mono<String> postAuthorizeDeniedMethodWithNoMaskAnnotation();
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@Mask(expression = "@myMasker.getMask()") @Mask(expression = "@myMasker.getMask()")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
Mono<String> preAuthorizeWithMaskAnnotationUsingBean(); Mono<String> preAuthorizeWithMaskAnnotationUsingBean();
@PostAuthorize("hasRole('ADMIN')") @PostAuthorize("hasRole('ADMIN')")
@Mask(expression = "@myMasker.getMask(returnObject)") @Mask(expression = "@myMasker.getMask(returnObject)")
@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
Mono<String> postAuthorizeWithMaskAnnotationUsingBean(); Mono<String> postAuthorizeWithMaskAnnotationUsingBean();
@PreAuthorize("@authz.checkReactiveResult(#result)") @PreAuthorize("@authz.checkReactiveResult(#result)")
@PostAuthorize("@authz.checkReactiveResult(!#result)") @PostAuthorize("@authz.checkReactiveResult(!#result)")
@AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class, @HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
Mono<String> checkCustomResult(boolean result); Mono<String> checkCustomResult(boolean result);
class StarMaskingHandler implements MethodAuthorizationDeniedHandler { class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
return "***"; return "***";
} }
@ -115,8 +113,8 @@ public interface ReactiveMethodSecurityService {
class StartMaskingHandlerChild extends StarMaskingHandler { class StartMaskingHandlerChild extends StarMaskingHandler {
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
return super.handle(methodInvocation, result) + "-child"; return super.handleDeniedInvocation(methodInvocation, result) + "-child";
} }
} }
@ -130,7 +128,7 @@ public interface ReactiveMethodSecurityService {
} }
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) {
Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class); Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class);
if (mask == null) { if (mask == null) {
mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod().getDeclaringClass(), Mask.class); 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; MaskValueResolver maskValueResolver;
@ -149,7 +147,16 @@ public interface ReactiveMethodSecurityService {
} }
@Override @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) { AuthorizationResult authorizationResult) {
MethodInvocation mi = methodInvocationResult.getMethodInvocation(); MethodInvocation mi = methodInvocationResult.getMethodInvocation();
Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class); Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class);
@ -185,31 +192,38 @@ public interface ReactiveMethodSecurityService {
} }
class PostMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { class PostMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
@Override @Override
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) { public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return "***"; return "***";
} }
} }
class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
static String MASK = "****-****-****-"; static String MASK = "****-****-****-";
@Override @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(); String cardNumber = (String) contextObject.getResult();
return MASK + cardNumber.substring(cardNumber.length() - 4); return MASK + cardNumber.substring(cardNumber.length() - 4);
} }
} }
class NullPostProcessor implements MethodAuthorizationDeniedPostProcessor { class NullPostProcessor implements MethodAuthorizationDeniedHandler {
@Override @Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult, public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) { AuthorizationResult authorizationResult) {
return null; return null;
} }
@ -231,7 +245,7 @@ public interface ReactiveMethodSecurityService {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Inherited @Inherited
@PostAuthorize("hasRole('{value}')") @PostAuthorize("hasRole('{value}')")
@AuthorizationDeniedHandler(postProcessorClass = NullPostProcessor.class) @HandleAuthorizationDenied(handlerClass = NullPostProcessor.class)
@interface NullDenied { @interface NullDenied {
String role(); String role();

View File

@ -18,7 +18,8 @@ package org.springframework.security.config.annotation.method.configuration;
import reactor.core.publisher.Mono; 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 { public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurityService {
@ -34,7 +35,7 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
@Override @Override
public Mono<String> preAuthorizeThrowAccessDeniedManually() { public Mono<String> preAuthorizeThrowAccessDeniedManually() {
return Mono.error(new AccessDeniedException("Access Denied")); return Mono.error(new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false)));
} }
@Override @Override
@ -44,7 +45,7 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
@Override @Override
public Mono<String> postAuthorizeThrowAccessDeniedManually() { public Mono<String> postAuthorizeThrowAccessDeniedManually() {
return Mono.error(new AccessDeniedException("Access Denied")); return Mono.error(new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false)));
} }
@Override @Override

View File

@ -16,10 +16,12 @@
package org.springframework.security.config.annotation.method.configuration; package org.springframework.security.config.annotation.method.configuration;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.method.AuthorizationDeniedHandler; import org.springframework.security.authorization.method.HandleAuthorizationDenied;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.MethodInvocationResult;
public class UserRecordWithEmailProtected { public class UserRecordWithEmailProtected {
@ -38,15 +40,21 @@ public class UserRecordWithEmailProtected {
} }
@PostAuthorize("hasRole('ADMIN')") @PostAuthorize("hasRole('ADMIN')")
@AuthorizationDeniedHandler(postProcessorClass = EmailMaskingPostProcessor.class) @HandleAuthorizationDenied(handlerClass = EmailMaskingPostProcessor.class)
public String email() { public String email() {
return this.email; return this.email;
} }
public static class EmailMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { public static class EmailMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
@Override @Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult, public Object handleDeniedInvocation(MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return "***";
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) { AuthorizationResult authorizationResult) {
String email = (String) methodInvocationResult.getResult(); String email = (String) methodInvocationResult.getResult();
return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*"); return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*");

View File

@ -28,10 +28,8 @@ import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor; import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler; 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.Authentication;
import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -42,8 +40,8 @@ import org.springframework.util.Assert;
* @author Josh Cummings * @author Josh Cummings
* @since 6.0 * @since 6.0
*/ */
public final class ObservationAuthorizationManager<T> implements AuthorizationManager<T>, MessageSourceAware, public final class ObservationAuthorizationManager<T>
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor { implements AuthorizationManager<T>, MessageSourceAware, MethodAuthorizationDeniedHandler {
private final ObservationRegistry registry; private final ObservationRegistry registry;
@ -55,17 +53,12 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager<T> delegate) { public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager<T> delegate) {
this.registry = registry; this.registry = registry;
this.delegate = delegate; this.delegate = delegate;
if (delegate instanceof MethodAuthorizationDeniedHandler h) { if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h; this.handler = h;
} }
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
this.postProcessor = p;
}
} }
@Override @Override
@ -116,14 +109,14 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
} }
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handle(methodInvocation, authorizationResult); return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
} }
@Override @Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult, public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) { AuthorizationResult authorizationResult) {
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult); return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
} }
} }

View File

@ -25,10 +25,8 @@ import reactor.core.publisher.Mono;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler; 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.Authentication;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -38,8 +36,8 @@ import org.springframework.util.Assert;
* @author Josh Cummings * @author Josh Cummings
* @since 6.0 * @since 6.0
*/ */
public final class ObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T>, public final class ObservationReactiveAuthorizationManager<T>
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor { implements ReactiveAuthorizationManager<T>, MethodAuthorizationDeniedHandler {
private final ObservationRegistry registry; private final ObservationRegistry registry;
@ -49,8 +47,6 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
public ObservationReactiveAuthorizationManager(ObservationRegistry registry, public ObservationReactiveAuthorizationManager(ObservationRegistry registry,
ReactiveAuthorizationManager<T> delegate) { ReactiveAuthorizationManager<T> delegate) {
this.registry = registry; this.registry = registry;
@ -58,9 +54,6 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
if (delegate instanceof MethodAuthorizationDeniedHandler h) { if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h; this.handler = h;
} }
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
this.postProcessor = p;
}
} }
@Override @Override
@ -99,14 +92,14 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
} }
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handle(methodInvocation, authorizationResult); return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult);
} }
@Override @Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult, public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) { AuthorizationResult authorizationResult) {
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult); return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
} }
} }

View File

@ -33,7 +33,6 @@ import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -57,7 +56,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
private final AuthorizationManager<MethodInvocationResult> authorizationManager; private final AuthorizationManager<MethodInvocationResult> authorizationManager;
private final MethodAuthorizationDeniedPostProcessor defaultPostProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor(); private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
private int order; private int order;
@ -119,7 +118,16 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
*/ */
@Override @Override
public Object invoke(MethodInvocation mi) throws Throwable { 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); return attemptAuthorization(mi, result);
} }
@ -174,28 +182,22 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
private Object attemptAuthorization(MethodInvocation mi, Object result) { private Object attemptAuthorization(MethodInvocation mi, Object result) {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi)); this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
MethodInvocationResult object = new MethodInvocationResult(mi, result); MethodInvocationResult object = new MethodInvocationResult(mi, result);
AuthorizationDecision decision; AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, object);
try {
decision = this.authorizationManager.check(this::getAuthentication, object);
}
catch (AuthorizationDeniedException denied) {
return postProcess(object, denied);
}
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, object, decision); this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, object, decision);
if (decision != null && !decision.isGranted()) { if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager " this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
+ this.authorizationManager + " and decision " + decision)); + this.authorizationManager + " and decision " + decision));
return postProcess(object, decision); return handlePostInvocationDenied(object, decision);
} }
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi)); this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
return result; return result;
} }
private Object postProcess(MethodInvocationResult mi, AuthorizationResult decision) { private Object handlePostInvocationDenied(MethodInvocationResult mi, AuthorizationDecision decision) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) { if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler deniedHandler) {
return postProcessableDecision.postProcessResult(mi, decision); return deniedHandler.handleDeniedInvocationResult(mi, decision);
} }
return this.defaultPostProcessor.postProcessResult(mi, decision); return this.defaultHandler.handleDeniedInvocationResult(mi, decision);
} }
private Authentication getAuthentication() { private Authentication getAuthentication() {

View File

@ -26,6 +26,7 @@ import org.aopalliance.intercept.MethodInvocation;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.publisher.Signal;
import org.springframework.aop.Pointcut; import org.springframework.aop.Pointcut;
import org.springframework.core.KotlinDetector; import org.springframework.core.KotlinDetector;
@ -60,7 +61,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
private int order = AuthorizationInterceptorsOrder.LAST.getOrder(); 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. * 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 " + "(for example, a Mono or Flux) or the function must be a Kotlin coroutine "
+ "in order to support Reactor Context"); + "in order to support Reactor Context");
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication(); 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); ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type);
if (hasFlowReturnType) { if (hasFlowReturnType) {
if (isSuspendingFunction) { if (isSuspendingFunction) {
Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi); Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi);
return Flux.from(publisher).flatMap(postAuthorize); return Flux.from(publisher).materialize().flatMap(postAuthorize);
} }
else { else {
Assert.state(adapter != null, () -> "The returnType " + type + " on " + method Assert.state(adapter != null, () -> "The returnType " + type + " on " + method
+ " must have a org.springframework.core.ReactiveAdapter registered"); + " must have a org.springframework.core.ReactiveAdapter registered");
Flux<?> response = Flux.defer(() -> adapter.toPublisher(ReactiveMethodInvocationUtils.proceed(mi))) Flux<?> response = Flux.defer(() -> adapter.toPublisher(ReactiveMethodInvocationUtils.proceed(mi)))
.materialize()
.flatMap(postAuthorize); .flatMap(postAuthorize);
return KotlinDelegate.asFlow(response); return KotlinDelegate.asFlow(response);
} }
} }
Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi); Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi);
if (isMultiValue(type, adapter)) { 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; 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; return (adapter != null) ? adapter.fromPublisher(mono) : mono;
} }
@ -153,17 +166,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
MethodInvocationResult invocationResult = new MethodInvocationResult(mi, result); MethodInvocationResult invocationResult = new MethodInvocationResult(mi, result);
return this.authorizationManager.check(authentication, invocationResult) return this.authorizationManager.check(authentication, invocationResult)
.switchIfEmpty(Mono.just(new AuthorizationDecision(false))) .switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
.materialize() .flatMap((decision) -> postProcess(decision, invocationResult));
.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());
});
} }
private Mono<Object> postProcess(AuthorizationResult decision, MethodInvocationResult methodInvocationResult) { private Mono<Object> postProcess(AuthorizationResult decision, MethodInvocationResult methodInvocationResult) {
@ -171,10 +174,24 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
return Mono.just(methodInvocationResult.getResult()); return Mono.just(methodInvocationResult.getResult());
} }
return Mono.fromSupplier(() -> { return Mono.fromSupplier(() -> {
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) { if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return postProcessableDecision.postProcessResult(methodInvocationResult, decision); 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) -> { }).flatMap((processedResult) -> {
if (Mono.class.isAssignableFrom(processedResult.getClass())) { if (Mono.class.isAssignableFrom(processedResult.getClass())) {
return (Mono<?>) processedResult; return (Mono<?>) processedResult;

View File

@ -261,14 +261,33 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
return handle(mi, decision); return handle(mi, decision);
} }
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi)); 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) { private Object handle(MethodInvocation mi, AuthorizationResult decision) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { 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() { private Authentication getAuthentication() {

View File

@ -142,19 +142,12 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication(); Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
return this.authorizationManager.check(authentication, mi) return this.authorizationManager.check(authentication, mi)
.switchIfEmpty(Mono.just(new AuthorizationDecision(false))) .switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
.materialize() .flatMapMany((decision) -> {
.flatMapMany((signal) -> { if (decision.isGranted()) {
if (!signal.hasError()) { return mapping.onErrorResume(AuthorizationDeniedException.class,
AuthorizationDecision decision = signal.get(); (deniedEx) -> postProcess(deniedEx, mi));
if (decision.isGranted()) {
return mapping;
}
return postProcess(decision, mi);
} }
if (signal.getThrowable() instanceof AuthorizationDeniedException denied) { return postProcess(decision, mi);
return postProcess(denied, mi);
}
return Mono.error(signal.getThrowable());
}); });
} }
@ -162,28 +155,21 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication(); Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
return this.authorizationManager.check(authentication, mi) return this.authorizationManager.check(authentication, mi)
.switchIfEmpty(Mono.just(new AuthorizationDecision(false))) .switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
.materialize() .flatMap((decision) -> {
.flatMap((signal) -> { if (decision.isGranted()) {
if (!signal.hasError()) { return mapping.onErrorResume(AuthorizationDeniedException.class,
AuthorizationDecision decision = signal.get(); (deniedEx) -> postProcess(deniedEx, mi));
if (decision.isGranted()) {
return mapping;
}
return postProcess(decision, mi);
} }
if (signal.getThrowable() instanceof AuthorizationDeniedException denied) { return postProcess(decision, mi);
return postProcess(denied, mi);
}
return Mono.error(signal.getThrowable());
}); });
} }
private Mono<Object> postProcess(AuthorizationResult decision, MethodInvocation mi) { private Mono<Object> postProcess(AuthorizationResult decision, MethodInvocation mi) {
return Mono.fromSupplier(() -> { return Mono.fromSupplier(() -> {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { 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) -> { }).flatMap((result) -> {
if (Mono.class.isAssignableFrom(result.getClass())) { if (Mono.class.isAssignableFrom(result.getClass())) {
return (Mono<?>) result; return (Mono<?>) result;

View File

@ -25,32 +25,26 @@ import java.lang.annotation.Target;
/** /**
* Annotation for specifying handling behavior when an authorization denied happens in * 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 * @author Marcus da Coregio
* @since 6.3 * @since 6.3
* @see org.springframework.security.access.prepost.PreAuthorize * @see AuthorizationManagerAfterMethodInterceptor
* @see org.springframework.security.access.prepost.PostAuthorize * @see AuthorizationManagerBeforeMethodInterceptor
*/ */
@Target({ ElementType.METHOD, ElementType.TYPE }) @Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Inherited @Inherited
@Documented @Documented
public @interface AuthorizationDeniedHandler { public @interface HandleAuthorizationDenied {
/** /**
* The {@link MethodAuthorizationDeniedHandler} used to handle denied authorizations * The {@link MethodAuthorizationDeniedHandler} used to handle denied authorization
* from {@link org.springframework.security.access.prepost.PreAuthorize} * results
* @return * @return
*/ */
Class<? extends MethodAuthorizationDeniedHandler> handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class; Class<? extends MethodAuthorizationDeniedHandler> handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class;
/**
* The {@link MethodAuthorizationDeniedPostProcessor} used to post process denied
* authorizations from
* {@link org.springframework.security.access.prepost.PostAuthorize}
* @return
*/
Class<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass() default ThrowingMethodAuthorizationDeniedPostProcessor.class;
} }

View File

@ -27,13 +27,14 @@ import org.springframework.security.authorization.AuthorizationResult;
* @author Marcus da Coregio * @author Marcus da Coregio
* @since 6.3 * @since 6.3
* @see org.springframework.security.access.prepost.PreAuthorize * @see org.springframework.security.access.prepost.PreAuthorize
* @see org.springframework.security.access.prepost.PostAuthorize
*/ */
public interface MethodAuthorizationDeniedHandler { public interface MethodAuthorizationDeniedHandler {
/** /**
* Handle denied method invocations, implementations might either throw an * Handle denied method invocations, implementations might either throw an
* {@link org.springframework.security.access.AccessDeniedException} or a replacement * {@link org.springframework.security.authorization.AuthorizationDeniedException} or
* result instead of invoking the method, e.g. a masked value. * a replacement result instead of invoking the method, e.g. a masked value.
* @param methodInvocation the {@link MethodInvocation} related to the authorization * @param methodInvocation the {@link MethodInvocation} related to the authorization
* denied * denied
* @param authorizationResult the authorization denied result * @param authorizationResult the authorization denied result
@ -41,6 +42,24 @@ public interface MethodAuthorizationDeniedHandler {
* {@link reactor.core.publisher.Mono} for reactive applications * {@link reactor.core.publisher.Mono} for reactive applications
*/ */
@Nullable @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);
}
} }

View File

@ -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);
}

View File

@ -38,7 +38,7 @@ import org.springframework.security.core.Authentication;
* @since 5.6 * @since 5.6
*/ */
public final class PostAuthorizeAuthorizationManager public final class PostAuthorizeAuthorizationManager
implements AuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor { implements AuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedHandler {
private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
@ -96,11 +96,19 @@ public final class PostAuthorizeAuthorizationManager
} }
@Override @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) { AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation()); ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation());
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute; PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
return postAuthorizeAttribute.getPostProcessor().postProcessResult(methodInvocationResult, authorizationResult); return postAuthorizeAttribute.getHandler()
.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
} }
} }

View File

@ -27,16 +27,16 @@ import org.springframework.util.Assert;
*/ */
class PostAuthorizeExpressionAttribute extends ExpressionAttribute { class PostAuthorizeExpressionAttribute extends ExpressionAttribute {
private final MethodAuthorizationDeniedPostProcessor postProcessor; private final MethodAuthorizationDeniedHandler handler;
PostAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedPostProcessor postProcessor) { PostAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedHandler handler) {
super(expression); super(expression);
Assert.notNull(postProcessor, "postProcessor cannot be null"); Assert.notNull(handler, "handler cannot be null");
this.postProcessor = postProcessor; this.handler = handler;
} }
MethodAuthorizationDeniedPostProcessor getPostProcessor() { MethodAuthorizationDeniedHandler getHandler() {
return this.postProcessor; return this.handler;
} }
} }

View File

@ -38,12 +38,12 @@ import org.springframework.util.Assert;
*/ */
final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> { 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() { PostAuthorizeExpressionAttributeRegistry() {
this.postProcessorResolver = (clazz) -> this.defaultPostProcessor; this.handlerResolver = (clazz) -> this.defaultHandler;
} }
@NonNull @NonNull
@ -55,22 +55,22 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
return ExpressionAttribute.NULL_ATTRIBUTE; return ExpressionAttribute.NULL_ATTRIBUTE;
} }
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value()); Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value());
MethodAuthorizationDeniedPostProcessor postProcessor = resolvePostProcessor(method, targetClass); MethodAuthorizationDeniedHandler deniedHandler = resolveHandler(method, targetClass);
return new PostAuthorizeExpressionAttribute(expression, postProcessor); return new PostAuthorizeExpressionAttribute(expression, deniedHandler);
} }
private MethodAuthorizationDeniedPostProcessor resolvePostProcessor(Method method, Class<?> targetClass) { private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) {
Function<AnnotatedElement, AuthorizationDeniedHandler> lookup = AuthorizationAnnotationUtils Function<AnnotatedElement, HandleAuthorizationDenied> lookup = AuthorizationAnnotationUtils
.withDefaults(AuthorizationDeniedHandler.class); .withDefaults(HandleAuthorizationDenied.class);
AuthorizationDeniedHandler deniedHandler = lookup.apply(method); HandleAuthorizationDenied deniedHandler = lookup.apply(method);
if (deniedHandler != null) { if (deniedHandler != null) {
return this.postProcessorResolver.apply(deniedHandler.postProcessorClass()); return this.handlerResolver.apply(deniedHandler.handlerClass());
} }
deniedHandler = lookup.apply(targetClass(method, targetClass)); deniedHandler = lookup.apply(targetClass(method, targetClass));
if (deniedHandler != null) { 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) { private PostAuthorize findPostAuthorizeAnnotation(Method method, Class<?> targetClass) {
@ -86,23 +86,23 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
*/ */
void setApplicationContext(ApplicationContext context) { void setApplicationContext(ApplicationContext context) {
Assert.notNull(context, "context cannot be null"); Assert.notNull(context, "context cannot be null");
this.postProcessorResolver = (postProcessorClass) -> resolvePostProcessor(context, postProcessorClass); this.handlerResolver = (clazz) -> resolveHandler(context, clazz);
} }
private MethodAuthorizationDeniedPostProcessor resolvePostProcessor(ApplicationContext context, private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context,
Class<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass) { Class<? extends MethodAuthorizationDeniedHandler> handlerClass) {
if (postProcessorClass == this.defaultPostProcessor.getClass()) { if (handlerClass == this.defaultHandler.getClass()) {
return this.defaultPostProcessor; return this.defaultHandler;
} }
String[] beanNames = context.getBeanNamesForType(postProcessorClass); String[] beanNames = context.getBeanNamesForType(handlerClass);
if (beanNames.length == 0) { 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) { 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)); + " but found " + Arrays.toString(beanNames));
} }
return context.getBean(beanNames[0], postProcessorClass); return context.getBean(beanNames[0], handlerClass);
} }
} }

View File

@ -38,7 +38,7 @@ import org.springframework.util.Assert;
* @since 5.8 * @since 5.8
*/ */
public final class PostAuthorizeReactiveAuthorizationManager public final class PostAuthorizeReactiveAuthorizationManager
implements ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor { implements ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedHandler {
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
@ -95,11 +95,19 @@ public final class PostAuthorizeReactiveAuthorizationManager
} }
@Override @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) { AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation()); ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation());
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute; PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
return postAuthorizeAttribute.getPostProcessor().postProcessResult(methodInvocationResult, authorizationResult); return postAuthorizeAttribute.getHandler()
.handleDeniedInvocationResult(methodInvocationResult, authorizationResult);
} }
} }

View File

@ -86,10 +86,10 @@ public final class PreAuthorizeAuthorizationManager
} }
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation); ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
PreAuthorizeExpressionAttribute postAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute; PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
return postAuthorizeAttribute.getHandler().handle(methodInvocation, authorizationResult); return preAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult);
} }
} }

View File

@ -60,9 +60,9 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
} }
private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) { private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) {
Function<AnnotatedElement, AuthorizationDeniedHandler> lookup = AuthorizationAnnotationUtils Function<AnnotatedElement, HandleAuthorizationDenied> lookup = AuthorizationAnnotationUtils
.withDefaults(AuthorizationDeniedHandler.class); .withDefaults(HandleAuthorizationDenied.class);
AuthorizationDeniedHandler deniedHandler = lookup.apply(method); HandleAuthorizationDenied deniedHandler = lookup.apply(method);
if (deniedHandler != null) { if (deniedHandler != null) {
return this.handlerResolver.apply(deniedHandler.handlerClass()); return this.handlerResolver.apply(deniedHandler.handlerClass());
} }

View File

@ -90,10 +90,10 @@ public final class PreAuthorizeReactiveAuthorizationManager
} }
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation); ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute; PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
return preAuthorizeAttribute.getHandler().handle(methodInvocation, authorizationResult); return preAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult);
} }
} }

View File

@ -31,11 +31,20 @@ import org.springframework.security.authorization.AuthorizationResult;
public final class ThrowingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { public final class ThrowingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler {
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
if (result instanceof AuthorizationDeniedException denied) { if (authorizationResult instanceof AuthorizationDeniedException denied) {
throw 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);
} }
} }

View File

@ -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);
}
}

View File

@ -127,7 +127,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class); HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class))) given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
.willAnswer(this::masking); .willAnswer(this::masking);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
@ -147,7 +147,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class); HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class))) given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
.willAnswer((invocation) -> { .willAnswer((invocation) -> {
MethodInvocationResult argument = invocation.getArgument(0); MethodInvocationResult argument = invocation.getArgument(0);
if (!"john".equals(argument.getResult())) { if (!"john".equals(argument.getResult())) {
@ -173,7 +173,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class); HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class))) given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
.willAnswer(this::masking); .willAnswer(this::masking);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
@ -192,7 +192,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class); HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class))) given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
.willAnswer(this::monoMasking); .willAnswer(this::monoMasking);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
@ -211,7 +211,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class); HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class))) given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class)))
.willReturn(null); .willReturn(null);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
@ -266,7 +266,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
} }
interface HandlingReactiveAuthorizationManager interface HandlingReactiveAuthorizationManager
extends ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor { extends ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedHandler {
} }

View File

@ -127,7 +127,8 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class); HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); 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( AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager); Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation); Object result = interceptor.invoke(mockMethodInvocation);
@ -145,7 +146,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class); HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); 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("***")); .willReturn(Mono.just("***"));
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager); Pointcut.TRUE, mockReactiveAuthorizationManager);
@ -164,7 +165,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class); HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); 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("***")); .willReturn(Mono.just("***"));
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager); Pointcut.TRUE, mockReactiveAuthorizationManager);

View File

@ -2256,14 +2256,13 @@ You can also add the Spring Boot property `spring.jackson.default-property-inclu
[[fallback-values-authorization-denied]] [[fallback-values-authorization-denied]]
== Providing Fallback Values When Authorization is 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. 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 access denied happened before invoking the method. Instead, you might wish to return a post-processed result, like a masked result, or a default value in cases where 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 `@HandleAuthorizationDenied`:
Let's consider the example from the <<authorize-object,previous section>>, but instead of creating the `AccessDeniedExceptionInterceptor` to transform an `AccessDeniedException` to a `null` return value, we will use the `handlerClass` attribute from `@AuthorizationDeniedHandler`:
[tabs] [tabs]
====== ======
@ -2274,7 +2273,7 @@ Java::
public class NullMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { <1> public class NullMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { <1>
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return null; return null;
} }
@ -2295,7 +2294,7 @@ public class User {
// ... // ...
@PreAuthorize(value = "hasAuthority('user:read')") @PreAuthorize(value = "hasAuthority('user:read')")
@AuthorizationDeniedHandler(handlerClass = NullMethodAuthorizationDeniedHandler.class) @HandleAuthorizationDenied(handlerClass = NullMethodAuthorizationDeniedHandler.class)
public String getEmail() { public String getEmail() {
return this.email; return this.email;
} }
@ -2308,7 +2307,7 @@ Kotlin::
---- ----
class NullMethodAuthorizationDeniedHandler : MethodAuthorizationDeniedHandler { <1> class NullMethodAuthorizationDeniedHandler : MethodAuthorizationDeniedHandler { <1>
override fun handle(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any { override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any {
return null 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 <1> Create an implementation of `MethodAuthorizationDeniedHandler` that returns a `null` value
<2> Register the `NullMethodAuthorizationDeniedHandler` as a bean <2> Register the `NullMethodAuthorizationDeniedHandler` as a bean
<3> 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`: 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: Let's continue with the previous example, but instead of returning `null`, we will return a masked value of the email:
[tabs] [tabs]
@ -2382,10 +2384,15 @@ Java::
+ +
[source,java,role="primary"] [source,java,role="primary"]
---- ----
public class EmailMaskingMethodAuthorizationDeniedPostProcessor implements MethodAuthorizationDeniedPostProcessor { <1> public class EmailMaskingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { <1>
@Override @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(); String email = (String) methodInvocationResult.getResult();
return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*"); return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*");
} }
@ -2397,8 +2404,8 @@ public class EmailMaskingMethodAuthorizationDeniedPostProcessor implements Metho
public class SecurityConfig { public class SecurityConfig {
@Bean <2> @Bean <2>
public EmailMaskingMethodAuthorizationDeniedPostProcessor emailMaskingMethodAuthorizationDeniedPostProcessor() { public EmailMaskingMethodAuthorizationDeniedHandler emailMaskingMethodAuthorizationDeniedHandler() {
return new EmailMaskingMethodAuthorizationDeniedPostProcessor(); return new EmailMaskingMethodAuthorizationDeniedHandler();
} }
} }
@ -2407,7 +2414,7 @@ public class User {
// ... // ...
@PostAuthorize(value = "hasAuthority('user:read')") @PostAuthorize(value = "hasAuthority('user:read')")
@AuthorizationDeniedHandler(postProcessorClass = EmailMaskingMethodAuthorizationDeniedPostProcessor.class) @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler.class)
public String getEmail() { public String getEmail() {
return this.email; return this.email;
} }
@ -2418,9 +2425,13 @@ Kotlin::
+ +
[source,kotlin,role="secondary"] [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 val email = methodInvocationResult.result as String
return email.replace("(^[^@]{3}|(?!^)\\G)[^@]".toRegex(), "$1*") return email.replace("(^[^@]{3}|(?!^)\\G)[^@]".toRegex(), "$1*")
} }
@ -2432,22 +2443,27 @@ class EmailMaskingMethodAuthorizationDeniedPostProcessor : MethodAuthorizationDe
class SecurityConfig { class SecurityConfig {
@Bean @Bean
fun emailMaskingMethodAuthorizationDeniedPostProcessor(): EmailMaskingMethodAuthorizationDeniedPostProcessor { fun emailMaskingMethodAuthorizationDeniedHandler(): EmailMaskingMethodAuthorizationDeniedHandler {
return EmailMaskingMethodAuthorizationDeniedPostProcessor() 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 <1> Create an implementation of `MethodAuthorizationDeniedHandler` that returns a masked value of the unauthorized result value
<2> Register the `EmailMaskingMethodAuthorizationDeniedPostProcessor` as a bean <2> Register the `EmailMaskingMethodAuthorizationDeniedHandler` as a bean
<3> Annotate the method with `@AuthorizationDeniedHandler` and pass the `EmailMaskingMethodAuthorizationDeniedPostProcessor` to the `postProcessorClass` attribute <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`: 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] [tabs]
====== ======
Java:: 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 `null` value.
- A non-null value, respecting the method's return type. - 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. - 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. 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-return-based-parameters]]
=== Deciding What to Return Based on Available Parameters === Deciding What to Return Based on Available Parameters
Consider a scenario where there might 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. In such cases, we can use the information passed via parameters to decide what to do.
For example, we can create a custom `@Mask` annotation and a handler that detects that annotation to decide what mask value to return: For example, we can create a custom `@Mask` annotation and a handler that detects that annotation to decide what mask value to return:
@ -2517,7 +2533,7 @@ public @interface Mask {
public class MaskAnnotationDeniedHandler implements MethodAuthorizationDeniedHandler { public class MaskAnnotationDeniedHandler implements MethodAuthorizationDeniedHandler {
@Override @Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class); Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class);
return mask.value(); return mask.value();
} }
@ -2539,14 +2555,14 @@ public class SecurityConfig {
public class MyService { public class MyService {
@PreAuthorize(value = "hasAuthority('user:read')") @PreAuthorize(value = "hasAuthority('user:read')")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
@Mask("***") @Mask("***")
public String foo() { public String foo() {
return "foo"; return "foo";
} }
@PreAuthorize(value = "hasAuthority('user:read')") @PreAuthorize(value = "hasAuthority('user:read')")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
@Mask("???") @Mask("???")
public String bar() { public String bar() {
return "bar"; return "bar";
@ -2567,7 +2583,7 @@ annotation class Mask(val value: String)
class MaskAnnotationDeniedHandler : MethodAuthorizationDeniedHandler { 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) val mask = AnnotationUtils.getAnnotation(methodInvocation.method, Mask::class.java)
return mask.value return mask.value
} }
@ -2589,14 +2605,14 @@ class SecurityConfig {
class MyService { class MyService {
@PreAuthorize(value = "hasAuthority('user:read')") @PreAuthorize(value = "hasAuthority('user:read')")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class)
@Mask("***") @Mask("***")
fun foo(): String { fun foo(): String {
return "foo" return "foo"
} }
@PreAuthorize(value = "hasAuthority('user:read')") @PreAuthorize(value = "hasAuthority('user:read')")
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class)
@Mask("???") @Mask("???")
fun bar(): String { fun bar(): String {
return "bar" return "bar"
@ -2653,8 +2669,8 @@ fun barWhenDeniedThenReturnQuestionMarks() {
=== Combining with Meta Annotation Support === 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. 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 `@AuthorizationDeniedHandler` with `@Mask`: Let's consider the <<deciding-return-based-parameters,example from the previous section>> and merge `@HandleAuthorizationDenied` with `@Mask`:
[tabs] [tabs]
====== ======
@ -2664,7 +2680,7 @@ Java::
---- ----
@Target({ ElementType.METHOD, ElementType.TYPE }) @Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
public @interface Mask { public @interface Mask {
String value(); String value();
@ -2683,7 +2699,7 @@ Kotlin::
---- ----
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class) @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class)
annotation class Mask(val value: String) annotation class Mask(val value: String)
@Mask("***") @Mask("***")