Handle SpEL AuthorizationDeniedExceptions

Closes gh-14600
This commit is contained in:
Josh Cummings 2024-04-04 10:20:41 -06:00
parent 61eba00654
commit 50b85aea0d
13 changed files with 262 additions and 26 deletions

View File

@ -30,6 +30,7 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
@ -172,7 +173,13 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
private Object attemptAuthorization(MethodInvocation mi, Object result) {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
MethodInvocationResult object = new MethodInvocationResult(mi, result);
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, object);
AuthorizationDecision decision;
try {
decision = this.authorizationManager.check(this::getAuthentication, object);
}
catch (AuthorizationDeniedException denied) {
return postProcess(object, denied);
}
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, object, decision);
if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
@ -183,6 +190,13 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
return result;
}
private Object postProcess(MethodInvocationResult mi, AuthorizationDeniedException denied) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
return postProcessableDecision.postProcessResult(mi, denied);
}
return this.defaultPostProcessor.postProcessResult(mi, denied);
}
private Object postProcess(MethodInvocationResult mi, AuthorizationDecision decision) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
return postProcessableDecision.postProcessResult(mi, decision);

View File

@ -34,6 +34,7 @@ import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@ -151,7 +152,32 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
MethodInvocationResult invocationResult = new MethodInvocationResult(mi, result);
return this.authorizationManager.check(authentication, invocationResult)
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
.flatMap((decision) -> postProcess(decision, invocationResult));
.materialize()
.flatMap((signal) -> {
if (!signal.hasError()) {
AuthorizationDecision decision = signal.get();
return postProcess(decision, invocationResult);
}
if (signal.getThrowable() instanceof AuthorizationDeniedException denied) {
return postProcess(denied, invocationResult);
}
return Mono.error(signal.getThrowable());
});
}
private Mono<Object> postProcess(AuthorizationDeniedException denied,
MethodInvocationResult methodInvocationResult) {
return Mono.fromSupplier(() -> {
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
return postProcessableDecision.postProcessResult(methodInvocationResult, denied);
}
return this.defaultPostProcessor.postProcessResult(methodInvocationResult, denied);
}).flatMap((processedResult) -> {
if (Mono.class.isAssignableFrom(processedResult.getClass())) {
return (Mono<?>) processedResult;
}
return Mono.justOrEmpty(processedResult);
});
}
private Mono<Object> postProcess(AuthorizationDecision decision, MethodInvocationResult methodInvocationResult) {

View File

@ -34,8 +34,10 @@ import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -245,7 +247,13 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
private Object attemptAuthorization(MethodInvocation mi) throws Throwable {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, mi);
AuthorizationDecision decision;
try {
decision = this.authorizationManager.check(this::getAuthentication, mi);
}
catch (AuthorizationDeniedException denied) {
return handle(mi, denied);
}
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, mi, decision);
if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
@ -256,7 +264,14 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
return mi.proceed();
}
private Object handle(MethodInvocation mi, AuthorizationDecision decision) {
private Object handle(MethodInvocation mi, AuthorizationDeniedException denied) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handle(mi, denied);
}
return this.defaultHandler.handle(mi, denied);
}
private Object handle(MethodInvocation mi, AuthorizationResult decision) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handle(mi, decision);
}

View File

@ -33,6 +33,7 @@ import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@ -140,11 +141,19 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
return this.authorizationManager.check(authentication, mi)
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
.flatMapMany((decision) -> {
.materialize()
.flatMapMany((signal) -> {
if (!signal.hasError()) {
AuthorizationDecision decision = signal.get();
if (decision.isGranted()) {
return mapping;
}
return postProcess(decision, mi);
}
if (signal.getThrowable() instanceof AuthorizationDeniedException denied) {
return postProcess(denied, mi);
}
return Mono.error(signal.getThrowable());
});
}
@ -152,11 +161,33 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
return this.authorizationManager.check(authentication, mi)
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
.flatMap((decision) -> {
.materialize()
.flatMap((signal) -> {
if (!signal.hasError()) {
AuthorizationDecision decision = signal.get();
if (decision.isGranted()) {
return mapping;
}
return postProcess(decision, mi);
}
if (signal.getThrowable() instanceof AuthorizationDeniedException denied) {
return postProcess(denied, mi);
}
return Mono.error(signal.getThrowable());
});
}
private Mono<Object> postProcess(AuthorizationDeniedException denied, MethodInvocation mi) {
return Mono.fromSupplier(() -> {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handle(mi, denied);
}
return this.defaultHandler.handle(mi, denied);
}).flatMap((processedResult) -> {
if (Mono.class.isAssignableFrom(processedResult.getClass())) {
return (Mono<?>) processedResult;
}
return Mono.justOrEmpty(processedResult);
});
}

View File

@ -19,6 +19,7 @@ package org.springframework.security.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.lang.Nullable;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
/**
@ -43,4 +44,18 @@ public interface MethodAuthorizationDeniedHandler {
@Nullable
Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult);
/**
* Handle denied method invocations, implementations might either throw an
* {@link org.springframework.security.access.AccessDeniedException} or a replacement
* result instead of invoking the method, e.g. a masked value.
* @param methodInvocation the {@link MethodInvocation} related to the authorization
* denied
* @param authorizationDenied the authorization denied exception
* @return a replacement result for the denied method invocation, or null, or a
* {@link reactor.core.publisher.Mono} for reactive applications
*/
default Object handle(MethodInvocation methodInvocation, AuthorizationDeniedException authorizationDenied) {
return handle(methodInvocation, authorizationDenied.getAuthorizationResult());
}
}

View File

@ -17,6 +17,7 @@
package org.springframework.security.authorization.method;
import org.springframework.lang.Nullable;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
/**
@ -43,4 +44,21 @@ public interface MethodAuthorizationDeniedPostProcessor {
@Nullable
Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult);
/**
* 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 authorizationDenied the {@link AuthorizationDeniedException} 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
*/
default Object postProcessResult(MethodInvocationResult methodInvocationResult,
AuthorizationDeniedException authorizationDenied) {
return postProcessResult(methodInvocationResult, authorizationDenied.getAuthorizationResult());
}
}

View File

@ -35,4 +35,9 @@ public final class ThrowingMethodAuthorizationDeniedHandler implements MethodAut
throw new AuthorizationDeniedException("Access Denied", result);
}
@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationDeniedException authorizationDenied) {
throw authorizationDenied;
}
}

View File

@ -33,4 +33,10 @@ public final class ThrowingMethodAuthorizationDeniedPostProcessor implements Met
throw new AuthorizationDeniedException("Access Denied", result);
}
@Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
AuthorizationDeniedException authorizationDenied) {
throw authorizationDenied;
}
}

View File

@ -26,8 +26,10 @@ import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
@ -36,6 +38,7 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
@ -139,4 +142,24 @@ public class AuthorizationManagerAfterMethodInterceptorTests {
any(AuthorizationDecision.class));
}
@Test
public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() throws Throwable {
MethodInvocation mi = mock(MethodInvocation.class);
given(mi.proceed()).willReturn("ok");
AuthorizationManager<MethodInvocationResult> manager = mock(AuthorizationManager.class);
given(manager.check(any(), any()))
.willThrow(new MyAuthzDeniedException("denied", new AuthorizationDecision(false)));
AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor(
Pointcut.TRUE, manager);
assertThatExceptionOfType(MyAuthzDeniedException.class).isThrownBy(() -> advice.invoke(mi));
}
static class MyAuthzDeniedException extends AuthorizationDeniedException {
MyAuthzDeniedException(String msg, AuthorizationResult authorizationResult) {
super(msg, authorizationResult);
}
}
}

View File

@ -28,6 +28,7 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import static org.assertj.core.api.Assertions.assertThat;
@ -126,7 +127,8 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class)))
.willAnswer(this::masking);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
@ -145,7 +147,8 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer((invocation) -> {
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class)))
.willAnswer((invocation) -> {
MethodInvocationResult argument = invocation.getArgument(0);
if (!"john".equals(argument.getResult())) {
return monoMasking(invocation);
@ -170,7 +173,8 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class)))
.willAnswer(this::masking);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
@ -188,7 +192,8 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::monoMasking);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class)))
.willAnswer(this::monoMasking);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
@ -206,7 +211,8 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willReturn(null);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class)))
.willReturn(null);
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
@ -235,6 +241,20 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
verify(mockReactiveAuthorizationManager).check(any(), any());
}
@Test
public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
given(mockMethodInvocation.proceed()).willReturn(Mono.just("ok"));
ReactiveAuthorizationManager<MethodInvocationResult> manager = mock(ReactiveAuthorizationManager.class);
given(manager.check(any(), any()))
.willReturn(Mono.error(new MyAuthzDeniedException("denied", new AuthorizationDecision(false))));
AuthorizationManagerAfterReactiveMethodInterceptor advice = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, manager);
assertThatExceptionOfType(MyAuthzDeniedException.class)
.isThrownBy(() -> ((Mono<?>) advice.invoke(mockMethodInvocation)).block());
}
private Object masking(InvocationOnMock invocation) {
MethodInvocationResult result = invocation.getArgument(0);
return result.getResult() + "-masked";
@ -262,4 +282,12 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
}
static class MyAuthzDeniedException extends AuthorizationDeniedException {
MyAuthzDeniedException(String msg, AuthorizationResult authorizationResult) {
super(msg, authorizationResult);
}
}
}

View File

@ -25,8 +25,10 @@ import org.springframework.aop.Pointcut;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
@ -34,6 +36,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@ -133,4 +136,22 @@ public class AuthorizationManagerBeforeMethodInterceptorTests {
any(AuthorizationDecision.class));
}
@Test
public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() {
AuthorizationManager<MethodInvocation> manager = mock(AuthorizationManager.class);
given(manager.check(any(), any()))
.willThrow(new MyAuthzDeniedException("denied", new AuthorizationDecision(false)));
AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor(
Pointcut.TRUE, manager);
assertThatExceptionOfType(MyAuthzDeniedException.class).isThrownBy(() -> advice.invoke(null));
}
static class MyAuthzDeniedException extends AuthorizationDeniedException {
MyAuthzDeniedException(String msg, AuthorizationResult authorizationResult) {
super(msg, authorizationResult);
}
}
}

View File

@ -27,6 +27,7 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import static org.assertj.core.api.Assertions.assertThat;
@ -126,7 +127,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn("***");
given(mockReactiveAuthorizationManager.handle(any(), any(AuthorizationResult.class))).willReturn("***");
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@ -144,7 +145,8 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***"));
given(mockReactiveAuthorizationManager.handle(any(), any(AuthorizationResult.class)))
.willReturn(Mono.just("***"));
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@ -162,7 +164,8 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***"));
given(mockReactiveAuthorizationManager.handle(any(), any(AuthorizationResult.class)))
.willReturn(Mono.just("***"));
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
Object result = interceptor.invoke(mockMethodInvocation);
@ -209,6 +212,19 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
}
@Test
public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() throws Throwable {
MethodInvocation mockMethodInvocation = spy(
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
ReactiveAuthorizationManager<MethodInvocation> manager = mock(ReactiveAuthorizationManager.class);
given(manager.check(any(), any()))
.willThrow(new MyAuthzDeniedException("denied", new AuthorizationDecision(false)));
AuthorizationManagerBeforeReactiveMethodInterceptor advice = new AuthorizationManagerBeforeReactiveMethodInterceptor(
Pointcut.TRUE, manager);
assertThatExceptionOfType(MyAuthzDeniedException.class)
.isThrownBy(() -> ((Mono<?>) advice.invoke(mockMethodInvocation)).block());
}
interface HandlingReactiveAuthorizationManager
extends ReactiveAuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
@ -226,4 +242,12 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
}
static class MyAuthzDeniedException extends AuthorizationDeniedException {
MyAuthzDeniedException(String msg, AuthorizationResult authorizationResult) {
super(msg, authorizationResult);
}
}
}

View File

@ -1249,6 +1249,9 @@ open class AuthorizationLogic {
----
======
Or throw a custom `AuthorizationDeniedException` instance.
Note, though, that returning an object is preferred as this doesn't incur the expense of generating a stacktrace.
Then, you can access the custom details when you <<fallback-values-authorization-denied, customize how the authorization result is handled>>.
[[custom-authorization-managers]]
@ -1654,6 +1657,13 @@ Xml::
<4> This method may only be invoked by ``Princpal``s with an `aud` claim equal to "my-audience"
<5> This method may only be invoked if the bean ``authz``'s `check` method returns `true`
[NOTE]
====
You can use a bean like `authz` above to add programmatic authorization.
It can return a `boolean`, and `AuthorizationResult`, or throw an `AuthorizationDeniedException`.
For exceptions, you can <<fallback-values-authorization-denied, handle them at the method level>>.
====
[[using_method_parameters]]
=== Using Method Parameters