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

@@ -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,13 +147,14 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
HandlingReactiveAuthorizationManager.class);
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer((invocation) -> {
MethodInvocationResult argument = invocation.getArgument(0);
if (!"john".equals(argument.getResult())) {
return monoMasking(invocation);
}
return Mono.just(argument.getResult());
});
given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class)))
.willAnswer((invocation) -> {
MethodInvocationResult argument = invocation.getArgument(0);
if (!"john".equals(argument.getResult())) {
return monoMasking(invocation);
}
return Mono.just(argument.getResult());
});
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
Pointcut.TRUE, mockReactiveAuthorizationManager);
@@ -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);
}
}
}