Handle SpEL AuthorizationDeniedExceptions
Closes gh-14600
This commit is contained in:
parent
61eba00654
commit
50b85aea0d
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue