Support SpEL Returning AuthorizationDecision
Closes gh-14598
This commit is contained in:
parent
0a9c482f62
commit
6f07d63938
|
@ -19,18 +19,30 @@ package org.springframework.security.config.annotation.method.configuration;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import io.micrometer.observation.ObservationRegistry;
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
import org.springframework.security.authorization.AuthorizationManager;
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
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.MethodAuthorizationDeniedPostProcessor;
|
||||||
|
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||||
|
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
|
||||||
|
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.util.function.SingletonSupplier;
|
import org.springframework.util.function.SingletonSupplier;
|
||||||
|
|
||||||
final class DeferringObservationAuthorizationManager<T> implements AuthorizationManager<T> {
|
final class DeferringObservationAuthorizationManager<T>
|
||||||
|
implements AuthorizationManager<T>, MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
|
||||||
|
|
||||||
private final Supplier<AuthorizationManager<T>> delegate;
|
private final Supplier<AuthorizationManager<T>> delegate;
|
||||||
|
|
||||||
|
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(() -> {
|
||||||
|
@ -40,6 +52,12 @@ final class DeferringObservationAuthorizationManager<T> implements Authorization
|
||||||
}
|
}
|
||||||
return new ObservationAuthorizationManager<>(registry, delegate);
|
return new ObservationAuthorizationManager<>(registry, delegate);
|
||||||
});
|
});
|
||||||
|
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
|
||||||
|
this.handler = h;
|
||||||
|
}
|
||||||
|
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
|
||||||
|
this.postProcessor = p;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -47,4 +65,15 @@ final class DeferringObservationAuthorizationManager<T> implements Authorization
|
||||||
return this.delegate.get().check(authentication, object);
|
return this.delegate.get().check(authentication, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||||
|
return this.handler.handle(methodInvocation, authorizationResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||||
|
AuthorizationResult authorizationResult) {
|
||||||
|
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,19 +19,31 @@ package org.springframework.security.config.annotation.method.configuration;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import io.micrometer.observation.ObservationRegistry;
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
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.MethodAuthorizationDeniedPostProcessor;
|
||||||
|
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||||
|
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
|
||||||
|
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.util.function.SingletonSupplier;
|
import org.springframework.util.function.SingletonSupplier;
|
||||||
|
|
||||||
final class DeferringObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> {
|
final class DeferringObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T>,
|
||||||
|
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
|
||||||
|
|
||||||
private final Supplier<ReactiveAuthorizationManager<T>> delegate;
|
private final Supplier<ReactiveAuthorizationManager<T>> delegate;
|
||||||
|
|
||||||
|
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(() -> {
|
||||||
|
@ -41,6 +53,12 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
|
||||||
}
|
}
|
||||||
return new ObservationReactiveAuthorizationManager<>(registry, delegate);
|
return new ObservationReactiveAuthorizationManager<>(registry, delegate);
|
||||||
});
|
});
|
||||||
|
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
|
||||||
|
this.handler = h;
|
||||||
|
}
|
||||||
|
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
|
||||||
|
this.postProcessor = p;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -48,4 +66,15 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
|
||||||
return this.delegate.get().check(authentication, object);
|
return this.delegate.get().check(authentication, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||||
|
return this.handler.handle(methodInvocation, authorizationResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||||
|
AuthorizationResult authorizationResult) {
|
||||||
|
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ package org.springframework.security.config.annotation.method.configuration;
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.authorization.AuthorizationResult;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@ -45,4 +47,20 @@ public class Authz {
|
||||||
return message != null && message.contains(authentication.getName());
|
return message != null && message.contains(authentication.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthorizationResult checkResult(boolean result) {
|
||||||
|
return new AuthzResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<AuthorizationResult> checkReactiveResult(boolean result) {
|
||||||
|
return Mono.just(checkResult(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AuthzResult extends AuthorizationDecision {
|
||||||
|
|
||||||
|
public AuthzResult(boolean granted) {
|
||||||
|
super(granted);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,6 +173,11 @@ public interface MethodSecurityService {
|
||||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = UserFallbackDeniedHandler.class)
|
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = UserFallbackDeniedHandler.class)
|
||||||
UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized();
|
UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized();
|
||||||
|
|
||||||
|
@PreAuthorize(value = "@authz.checkResult(#result)", handlerClass = MethodAuthorizationDeniedHandler.class)
|
||||||
|
@PostAuthorize(value = "@authz.checkResult(!#result)",
|
||||||
|
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
|
||||||
|
String checkCustomResult(boolean result);
|
||||||
|
|
||||||
class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
|
class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -28,4 +28,14 @@ public class MethodSecurityServiceConfig {
|
||||||
return new MethodSecurityServiceImpl();
|
return new MethodSecurityServiceImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
ReactiveMethodSecurityService reactiveService() {
|
||||||
|
return new ReactiveMethodSecurityServiceImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Authz authz() {
|
||||||
|
return new Authz();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,4 +197,9 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
|
||||||
return new UserRecordWithEmailProtected("username", "useremail@example.com");
|
return new UserRecordWithEmailProtected("username", "useremail@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String checkCustomResult(boolean result) {
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,8 @@ import org.springframework.security.authorization.method.AuthorizationAdvisorPro
|
||||||
import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder;
|
import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder;
|
||||||
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.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,6 +94,8 @@ import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.atLeastOnce;
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
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}.
|
||||||
|
@ -925,6 +929,23 @@ public class PrePostMethodSecurityConfigurationTests {
|
||||||
assertThat(user.name()).isEqualTo("Protected");
|
assertThat(user.name()).isEqualTo("Protected");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser
|
||||||
|
void getUserWhenNotAuthorizedThenHandlerUsesCustomAuthorizationDecision() {
|
||||||
|
this.spring.register(MethodSecurityServiceConfig.class, CustomResultConfig.class).autowire();
|
||||||
|
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||||
|
MethodAuthorizationDeniedHandler handler = this.spring.getContext()
|
||||||
|
.getBean(MethodAuthorizationDeniedHandler.class);
|
||||||
|
MethodAuthorizationDeniedPostProcessor postProcessor = this.spring.getContext()
|
||||||
|
.getBean(MethodAuthorizationDeniedPostProcessor.class);
|
||||||
|
assertThat(service.checkCustomResult(false)).isNull();
|
||||||
|
verify(handler).handle(any(), any(Authz.AuthzResult.class));
|
||||||
|
verifyNoInteractions(postProcessor);
|
||||||
|
assertThat(service.checkCustomResult(true)).isNull();
|
||||||
|
verify(postProcessor).postProcessResult(any(), any(Authz.AuthzResult.class));
|
||||||
|
verifyNoMoreInteractions(handler);
|
||||||
|
}
|
||||||
|
|
||||||
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
|
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
|
||||||
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
|
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
|
||||||
}
|
}
|
||||||
|
@ -1449,4 +1470,23 @@ public class PrePostMethodSecurityConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EnableMethodSecurity
|
||||||
|
static class CustomResultConfig {
|
||||||
|
|
||||||
|
MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class);
|
||||||
|
|
||||||
|
MethodAuthorizationDeniedPostProcessor postProcessor = mock(MethodAuthorizationDeniedPostProcessor.class);
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() {
|
||||||
|
return this.handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MethodAuthorizationDeniedPostProcessor methodAuthorizationDeniedPostProcessor() {
|
||||||
|
return this.postProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,8 @@ import org.springframework.security.authentication.TestAuthentication;
|
||||||
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
|
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
|
||||||
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.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;
|
||||||
|
@ -54,8 +56,14 @@ import org.springframework.security.config.test.SpringTestContextExtension;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
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.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Tadaya Tsuyukubo
|
* @author Tadaya Tsuyukubo
|
||||||
|
@ -65,7 +73,7 @@ public class ReactiveMethodSecurityConfigurationTests {
|
||||||
|
|
||||||
public final SpringTestContext spring = new SpringTestContext(this);
|
public final SpringTestContext spring = new SpringTestContext(this);
|
||||||
|
|
||||||
@Autowired
|
@Autowired(required = false)
|
||||||
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler;
|
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -212,6 +220,23 @@ public class ReactiveMethodSecurityConfigurationTests {
|
||||||
.verifyError(AccessDeniedException.class);
|
.verifyError(AccessDeniedException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser
|
||||||
|
void getUserWhenNotAuthorizedThenHandlerUsesCustomAuthorizationDecision() {
|
||||||
|
this.spring.register(MethodSecurityServiceConfig.class, CustomResultConfig.class).autowire();
|
||||||
|
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
|
||||||
|
MethodAuthorizationDeniedHandler handler = this.spring.getContext()
|
||||||
|
.getBean(MethodAuthorizationDeniedHandler.class);
|
||||||
|
MethodAuthorizationDeniedPostProcessor postProcessor = this.spring.getContext()
|
||||||
|
.getBean(MethodAuthorizationDeniedPostProcessor.class);
|
||||||
|
assertThat(service.checkCustomResult(false).block()).isNull();
|
||||||
|
verify(handler).handle(any(), any(Authz.AuthzResult.class));
|
||||||
|
verifyNoInteractions(postProcessor);
|
||||||
|
assertThat(service.checkCustomResult(true).block()).isNull();
|
||||||
|
verify(postProcessor).postProcessResult(any(), any(Authz.AuthzResult.class));
|
||||||
|
verifyNoMoreInteractions(handler);
|
||||||
|
}
|
||||||
|
|
||||||
private static Consumer<User.UserBuilder> authorities(String... authorities) {
|
private static Consumer<User.UserBuilder> authorities(String... authorities) {
|
||||||
return (builder) -> builder.authorities(authorities);
|
return (builder) -> builder.authorities(authorities);
|
||||||
}
|
}
|
||||||
|
@ -353,4 +378,23 @@ public class ReactiveMethodSecurityConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EnableReactiveMethodSecurity
|
||||||
|
static class CustomResultConfig {
|
||||||
|
|
||||||
|
MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class);
|
||||||
|
|
||||||
|
MethodAuthorizationDeniedPostProcessor postProcessor = mock(MethodAuthorizationDeniedPostProcessor.class);
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() {
|
||||||
|
return this.handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MethodAuthorizationDeniedPostProcessor methodAuthorizationDeniedPostProcessor() {
|
||||||
|
return this.postProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,11 @@ public interface ReactiveMethodSecurityService {
|
||||||
@Mask(expression = "@myMasker.getMask(returnObject)")
|
@Mask(expression = "@myMasker.getMask(returnObject)")
|
||||||
Mono<String> postAuthorizeWithMaskAnnotationUsingBean();
|
Mono<String> postAuthorizeWithMaskAnnotationUsingBean();
|
||||||
|
|
||||||
|
@PreAuthorize(value = "@authz.checkReactiveResult(#result)", handlerClass = MethodAuthorizationDeniedHandler.class)
|
||||||
|
@PostAuthorize(value = "@authz.checkReactiveResult(!#result)",
|
||||||
|
postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
|
||||||
|
Mono<String> checkCustomResult(boolean result);
|
||||||
|
|
||||||
class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
|
class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -82,4 +82,9 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
|
||||||
return Mono.just("ok");
|
return Mono.just("ok");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<String> checkCustomResult(boolean result) {
|
||||||
|
return Mono.just("ok");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -19,12 +19,43 @@ package org.springframework.security.access.expression;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.EvaluationException;
|
import org.springframework.expression.EvaluationException;
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.authorization.AuthorizationResult;
|
||||||
|
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
|
||||||
|
|
||||||
public final class ExpressionUtils {
|
public final class ExpressionUtils {
|
||||||
|
|
||||||
private ExpressionUtils() {
|
private ExpressionUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate a SpEL expression and coerce into an {@link AuthorizationDecision}
|
||||||
|
* @param expr a SpEL expression
|
||||||
|
* @param ctx an {@link EvaluationContext}
|
||||||
|
* @return the resulting {@link AuthorizationDecision}
|
||||||
|
* @since 6.3
|
||||||
|
*/
|
||||||
|
public static AuthorizationResult evaluate(Expression expr, EvaluationContext ctx) {
|
||||||
|
try {
|
||||||
|
Object result = expr.getValue(ctx);
|
||||||
|
if (result instanceof AuthorizationResult decision) {
|
||||||
|
return decision;
|
||||||
|
}
|
||||||
|
if (result instanceof Boolean granted) {
|
||||||
|
return new ExpressionAuthorizationDecision(granted, expr);
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"SpEL expression must return either a Boolean or an AuthorizationDecision");
|
||||||
|
}
|
||||||
|
catch (EvaluationException ex) {
|
||||||
|
throw new IllegalArgumentException("Failed to evaluate expression '" + expr.getExpressionString() + "'",
|
||||||
|
ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
|
public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
|
||||||
try {
|
try {
|
||||||
return expr.getValue(ctx, Boolean.class);
|
return expr.getValue(ctx, Boolean.class);
|
||||||
|
|
|
@ -21,11 +21,17 @@ import java.util.function.Supplier;
|
||||||
import io.micrometer.observation.Observation;
|
import io.micrometer.observation.Observation;
|
||||||
import io.micrometer.observation.ObservationConvention;
|
import io.micrometer.observation.ObservationConvention;
|
||||||
import io.micrometer.observation.ObservationRegistry;
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.context.MessageSourceAware;
|
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.MethodAuthorizationDeniedPostProcessor;
|
||||||
|
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||||
|
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
|
||||||
|
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.SpringSecurityMessageSource;
|
import org.springframework.security.core.SpringSecurityMessageSource;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
@ -36,7 +42,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> implements AuthorizationManager<T>, MessageSourceAware,
|
||||||
|
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
|
||||||
|
|
||||||
private final ObservationRegistry registry;
|
private final ObservationRegistry registry;
|
||||||
|
|
||||||
|
@ -46,9 +53,19 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
|
||||||
|
|
||||||
private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
|
private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
|
||||||
|
|
||||||
|
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) {
|
||||||
|
this.handler = h;
|
||||||
|
}
|
||||||
|
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
|
||||||
|
this.postProcessor = p;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -98,4 +115,15 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
|
||||||
this.messages = new MessageSourceAccessor(messageSource);
|
this.messages = new MessageSourceAccessor(messageSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||||
|
return this.handler.handle(methodInvocation, authorizationResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||||
|
AuthorizationResult authorizationResult) {
|
||||||
|
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,15 @@ import io.micrometer.observation.Observation;
|
||||||
import io.micrometer.observation.ObservationConvention;
|
import io.micrometer.observation.ObservationConvention;
|
||||||
import io.micrometer.observation.ObservationRegistry;
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
|
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
import reactor.core.publisher.Mono;
|
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.MethodAuthorizationDeniedPostProcessor;
|
||||||
|
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||||
|
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
|
||||||
|
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
@ -32,7 +38,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> implements ReactiveAuthorizationManager<T>,
|
||||||
|
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
|
||||||
|
|
||||||
private final ObservationRegistry registry;
|
private final ObservationRegistry registry;
|
||||||
|
|
||||||
|
@ -40,10 +47,20 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
|
||||||
|
|
||||||
private ObservationConvention<AuthorizationObservationContext<?>> convention = new AuthorizationObservationConvention();
|
private ObservationConvention<AuthorizationObservationContext<?>> convention = new AuthorizationObservationConvention();
|
||||||
|
|
||||||
|
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;
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
|
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
|
||||||
|
this.handler = h;
|
||||||
|
}
|
||||||
|
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
|
||||||
|
this.postProcessor = p;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -81,4 +98,15 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
|
||||||
this.convention = convention;
|
this.convention = convention;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||||
|
return this.handler.handle(methodInvocation, authorizationResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||||
|
AuthorizationResult authorizationResult) {
|
||||||
|
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,7 +184,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object postProcess(MethodInvocationResult mi, AuthorizationDecision decision) {
|
private Object postProcess(MethodInvocationResult mi, AuthorizationDecision decision) {
|
||||||
if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
||||||
return postProcessableDecision.postProcessResult(mi, decision);
|
return postProcessableDecision.postProcessResult(mi, decision);
|
||||||
}
|
}
|
||||||
return this.defaultPostProcessor.postProcessResult(mi, decision);
|
return this.defaultPostProcessor.postProcessResult(mi, decision);
|
||||||
|
|
|
@ -159,7 +159,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
|
||||||
return Mono.just(methodInvocationResult.getResult());
|
return Mono.just(methodInvocationResult.getResult());
|
||||||
}
|
}
|
||||||
return Mono.fromSupplier(() -> {
|
return Mono.fromSupplier(() -> {
|
||||||
if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
||||||
return postProcessableDecision.postProcessResult(methodInvocationResult, decision);
|
return postProcessableDecision.postProcessResult(methodInvocationResult, decision);
|
||||||
}
|
}
|
||||||
return this.defaultPostProcessor.postProcessResult(methodInvocationResult, decision);
|
return this.defaultPostProcessor.postProcessResult(methodInvocationResult, decision);
|
||||||
|
|
|
@ -257,7 +257,7 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object handle(MethodInvocation mi, AuthorizationDecision decision) {
|
private Object handle(MethodInvocation mi, AuthorizationDecision decision) {
|
||||||
if (decision instanceof MethodAuthorizationDeniedHandler handler) {
|
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
||||||
return handler.handle(mi, decision);
|
return handler.handle(mi, decision);
|
||||||
}
|
}
|
||||||
return this.defaultHandler.handle(mi, decision);
|
return this.defaultHandler.handle(mi, decision);
|
||||||
|
|
|
@ -162,7 +162,7 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
|
||||||
|
|
||||||
private Mono<Object> postProcess(AuthorizationDecision decision, MethodInvocation mi) {
|
private Mono<Object> postProcess(AuthorizationDecision decision, MethodInvocation mi) {
|
||||||
return Mono.fromSupplier(() -> {
|
return Mono.fromSupplier(() -> {
|
||||||
if (decision instanceof MethodAuthorizationDeniedHandler handler) {
|
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
||||||
return handler.handle(mi, decision);
|
return handler.handle(mi, decision);
|
||||||
}
|
}
|
||||||
return this.defaultHandler.handle(mi, decision);
|
return this.defaultHandler.handle(mi, decision);
|
||||||
|
|
|
@ -1,41 +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.expression.Expression;
|
|
||||||
import org.springframework.security.authorization.AuthorizationResult;
|
|
||||||
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
class PostAuthorizeAuthorizationDecision extends ExpressionAuthorizationDecision
|
|
||||||
implements MethodAuthorizationDeniedPostProcessor {
|
|
||||||
|
|
||||||
private final MethodAuthorizationDeniedPostProcessor postProcessor;
|
|
||||||
|
|
||||||
PostAuthorizeAuthorizationDecision(boolean granted, Expression expression,
|
|
||||||
MethodAuthorizationDeniedPostProcessor postProcessor) {
|
|
||||||
super(granted, expression);
|
|
||||||
Assert.notNull(postProcessor, "postProcessor cannot be null");
|
|
||||||
this.postProcessor = postProcessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult result) {
|
|
||||||
return this.postProcessor.postProcessResult(methodInvocationResult, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -27,6 +27,7 @@ import org.springframework.security.access.expression.method.MethodSecurityExpre
|
||||||
import org.springframework.security.access.prepost.PostAuthorize;
|
import org.springframework.security.access.prepost.PostAuthorize;
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,7 +38,8 @@ import org.springframework.security.core.Authentication;
|
||||||
* @author Evgeniy Cheban
|
* @author Evgeniy Cheban
|
||||||
* @since 5.6
|
* @since 5.6
|
||||||
*/
|
*/
|
||||||
public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
|
public final class PostAuthorizeAuthorizationManager
|
||||||
|
implements AuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
|
||||||
|
|
||||||
private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
||||||
|
|
||||||
|
@ -88,13 +90,18 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
|
||||||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
|
|
||||||
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
|
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
|
||||||
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi.getMethodInvocation());
|
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi.getMethodInvocation());
|
||||||
expressionHandler.setReturnObject(mi.getResult(), ctx);
|
expressionHandler.setReturnObject(mi.getResult(), ctx);
|
||||||
boolean granted = ExpressionUtils.evaluateAsBoolean(postAuthorizeAttribute.getExpression(), ctx);
|
return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
|
||||||
return new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(),
|
}
|
||||||
postAuthorizeAttribute.getPostProcessor());
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||||
|
AuthorizationResult authorizationResult) {
|
||||||
|
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation());
|
||||||
|
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
|
||||||
|
return postAuthorizeAttribute.getPostProcessor().postProcessResult(methodInvocationResult, authorizationResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.springframework.security.access.expression.method.DefaultMethodSecuri
|
||||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
import org.springframework.security.access.prepost.PostAuthorize;
|
import org.springframework.security.access.prepost.PostAuthorize;
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.authorization.AuthorizationResult;
|
||||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
@ -37,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> {
|
implements ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
|
||||||
|
|
||||||
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
||||||
|
|
||||||
|
@ -82,15 +83,23 @@ public final class PostAuthorizeReactiveAuthorizationManager
|
||||||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
|
|
||||||
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
|
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
return authentication
|
return authentication
|
||||||
.map((auth) -> expressionHandler.createEvaluationContext(auth, mi))
|
.map((auth) -> expressionHandler.createEvaluationContext(auth, mi))
|
||||||
.doOnNext((ctx) -> expressionHandler.setReturnObject(result.getResult(), ctx))
|
.doOnNext((ctx) -> expressionHandler.setReturnObject(result.getResult(), ctx))
|
||||||
.flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx))
|
.flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx))
|
||||||
.map((granted) -> new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(), postAuthorizeAttribute.getPostProcessor()));
|
.cast(AuthorizationDecision.class);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
|
||||||
|
AuthorizationResult authorizationResult) {
|
||||||
|
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation());
|
||||||
|
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
|
||||||
|
return postAuthorizeAttribute.getPostProcessor().postProcessResult(methodInvocationResult, authorizationResult);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +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.aopalliance.intercept.MethodInvocation;
|
|
||||||
|
|
||||||
import org.springframework.expression.Expression;
|
|
||||||
import org.springframework.security.authorization.AuthorizationResult;
|
|
||||||
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
class PreAuthorizeAuthorizationDecision extends ExpressionAuthorizationDecision
|
|
||||||
implements MethodAuthorizationDeniedHandler {
|
|
||||||
|
|
||||||
private final MethodAuthorizationDeniedHandler handler;
|
|
||||||
|
|
||||||
PreAuthorizeAuthorizationDecision(boolean granted, Expression expression,
|
|
||||||
MethodAuthorizationDeniedHandler handler) {
|
|
||||||
super(granted, expression);
|
|
||||||
Assert.notNull(handler, "handler cannot be null");
|
|
||||||
this.handler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
|
|
||||||
return this.handler.handle(methodInvocation, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -27,6 +27,7 @@ import org.springframework.security.access.expression.method.MethodSecurityExpre
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,7 +38,8 @@ import org.springframework.security.core.Authentication;
|
||||||
* @author Evgeniy Cheban
|
* @author Evgeniy Cheban
|
||||||
* @since 5.6
|
* @since 5.6
|
||||||
*/
|
*/
|
||||||
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
public final class PreAuthorizeAuthorizationManager
|
||||||
|
implements AuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
|
||||||
|
|
||||||
private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
||||||
|
|
||||||
|
@ -80,11 +82,15 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana
|
||||||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
|
|
||||||
EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi);
|
EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi);
|
||||||
boolean granted = ExpressionUtils.evaluateAsBoolean(preAuthorizeAttribute.getExpression(), ctx);
|
return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
|
||||||
return new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(),
|
}
|
||||||
preAuthorizeAttribute.getHandler());
|
|
||||||
|
@Override
|
||||||
|
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||||
|
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
|
||||||
|
PreAuthorizeExpressionAttribute postAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
|
||||||
|
return postAuthorizeAttribute.getHandler().handle(methodInvocation, authorizationResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.springframework.security.access.expression.method.DefaultMethodSecuri
|
||||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.authorization.AuthorizationResult;
|
||||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
@ -36,7 +37,8 @@ import org.springframework.util.Assert;
|
||||||
* @author Evgeniy Cheban
|
* @author Evgeniy Cheban
|
||||||
* @since 5.8
|
* @since 5.8
|
||||||
*/
|
*/
|
||||||
public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager<MethodInvocation> {
|
public final class PreAuthorizeReactiveAuthorizationManager
|
||||||
|
implements ReactiveAuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
|
||||||
|
|
||||||
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
||||||
|
|
||||||
|
@ -79,13 +81,19 @@ public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveA
|
||||||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
return authentication
|
return authentication
|
||||||
.map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi))
|
.map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi))
|
||||||
.flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx))
|
.flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx))
|
||||||
.map((granted) -> new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(), preAuthorizeAttribute.getHandler()));
|
.cast(AuthorizationDecision.class);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||||
|
ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
|
||||||
|
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
|
||||||
|
return preAuthorizeAttribute.getHandler().handle(methodInvocation, authorizationResult);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ import reactor.core.publisher.Mono;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.EvaluationException;
|
import org.springframework.expression.EvaluationException;
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.security.authorization.AuthorizationResult;
|
||||||
|
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For internal use only, as this contract is likely to change.
|
* For internal use only, as this contract is likely to change.
|
||||||
|
@ -30,6 +32,33 @@ import org.springframework.expression.Expression;
|
||||||
*/
|
*/
|
||||||
final class ReactiveExpressionUtils {
|
final class ReactiveExpressionUtils {
|
||||||
|
|
||||||
|
static Mono<AuthorizationResult> evaluate(Expression expr, EvaluationContext ctx) {
|
||||||
|
return Mono.defer(() -> {
|
||||||
|
Object value;
|
||||||
|
try {
|
||||||
|
value = expr.getValue(ctx);
|
||||||
|
}
|
||||||
|
catch (EvaluationException ex) {
|
||||||
|
return Mono.error(() -> new IllegalArgumentException(
|
||||||
|
"Failed to evaluate expression '" + expr.getExpressionString() + "'", ex));
|
||||||
|
}
|
||||||
|
if (value instanceof Mono<?> mono) {
|
||||||
|
return mono.flatMap((data) -> adapt(expr, data));
|
||||||
|
}
|
||||||
|
return adapt(expr, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Mono<AuthorizationResult> adapt(Expression expr, Object value) {
|
||||||
|
if (value instanceof Boolean granted) {
|
||||||
|
return Mono.just(new ExpressionAuthorizationDecision(granted, expr));
|
||||||
|
}
|
||||||
|
if (value instanceof AuthorizationResult decision) {
|
||||||
|
return Mono.just(decision);
|
||||||
|
}
|
||||||
|
return createInvalidReturnTypeMono(expr);
|
||||||
|
}
|
||||||
|
|
||||||
static Mono<Boolean> evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
|
static Mono<Boolean> evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
|
||||||
return Mono.defer(() -> {
|
return Mono.defer(() -> {
|
||||||
Object value;
|
Object value;
|
||||||
|
@ -56,9 +85,9 @@ final class ReactiveExpressionUtils {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Mono<Boolean> createInvalidReturnTypeMono(Expression expr) {
|
private static <T> Mono<T> createInvalidReturnTypeMono(Expression expr) {
|
||||||
return Mono.error(() -> new IllegalStateException(
|
return Mono.error(() -> new IllegalStateException("Expression: '" + expr.getExpressionString()
|
||||||
"Expression: '" + expr.getExpressionString() + "' must return boolean or Mono<Boolean>"));
|
+ "' must return boolean, Mono<Boolean>, AuthorizationResult, or Mono<AuthorizationResult>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReactiveExpressionUtils() {
|
private ReactiveExpressionUtils() {
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* 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.access.expression;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class ExpressionUtilsTests {
|
||||||
|
|
||||||
|
private final Object details = new Object();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void evaluateWhenAuthorizationDecisionThenReturns() {
|
||||||
|
SpelExpressionParser parser = new SpelExpressionParser();
|
||||||
|
Expression expression = parser.parseExpression("#root.returnDecision()");
|
||||||
|
StandardEvaluationContext context = new StandardEvaluationContext(this);
|
||||||
|
assertThat(ExpressionUtils.evaluate(expression, context)).isInstanceOf(AuthorizationDecisionDetails.class)
|
||||||
|
.extracting("details")
|
||||||
|
.isEqualTo(this.details);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void evaluateWhenBooleanThenReturnsExpressionAuthorizationDecision() {
|
||||||
|
SpelExpressionParser parser = new SpelExpressionParser();
|
||||||
|
Expression expression = parser.parseExpression("#root.returnResult()");
|
||||||
|
StandardEvaluationContext context = new StandardEvaluationContext(this);
|
||||||
|
assertThat(ExpressionUtils.evaluate(expression, context)).isInstanceOf(ExpressionAuthorizationDecision.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthorizationDecision returnDecision() {
|
||||||
|
return new AuthorizationDecisionDetails(false, this.details);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean returnResult() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class AuthorizationDecisionDetails extends AuthorizationDecision {
|
||||||
|
|
||||||
|
final Object details;
|
||||||
|
|
||||||
|
AuthorizationDecisionDetails(boolean granted, Object details) {
|
||||||
|
super(granted);
|
||||||
|
this.details = details;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,16 +19,15 @@ package org.springframework.security.authorization.method;
|
||||||
import org.aopalliance.intercept.MethodInvocation;
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.aop.Pointcut;
|
import org.springframework.aop.Pointcut;
|
||||||
import org.springframework.expression.common.LiteralExpression;
|
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
import org.springframework.security.authorization.AuthorizationDeniedException;
|
import org.springframework.security.authorization.AuthorizationDeniedException;
|
||||||
import org.springframework.security.authorization.AuthorizationResult;
|
|
||||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
@ -125,10 +124,10 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
||||||
MethodInvocation mockMethodInvocation = spy(
|
MethodInvocation mockMethodInvocation = spy(
|
||||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
||||||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||||
ReactiveAuthorizationManager.class);
|
HandlingReactiveAuthorizationManager.class);
|
||||||
given(mockReactiveAuthorizationManager.check(any(), any()))
|
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking);
|
||||||
.will((invocation) -> Mono.just(createDecision(new MaskingPostProcessor())));
|
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||||
Object result = interceptor.invoke(mockMethodInvocation);
|
Object result = interceptor.invoke(mockMethodInvocation);
|
||||||
|
@ -144,15 +143,16 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
||||||
MethodInvocation mockMethodInvocation = spy(
|
MethodInvocation mockMethodInvocation = spy(
|
||||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
||||||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||||
ReactiveAuthorizationManager.class);
|
HandlingReactiveAuthorizationManager.class);
|
||||||
given(mockReactiveAuthorizationManager.check(any(), any())).willAnswer((invocation) -> {
|
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer((invocation) -> {
|
||||||
MethodInvocationResult argument = invocation.getArgument(1);
|
MethodInvocationResult argument = invocation.getArgument(0);
|
||||||
if ("john".equals(argument.getResult())) {
|
if (!"john".equals(argument.getResult())) {
|
||||||
return Mono.just(new AuthorizationDecision(true));
|
return monoMasking(invocation);
|
||||||
}
|
}
|
||||||
return Mono.just(createDecision(new MaskingPostProcessor()));
|
return Mono.just(argument.getResult());
|
||||||
});
|
});
|
||||||
|
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||||
Object result = interceptor.invoke(mockMethodInvocation);
|
Object result = interceptor.invoke(mockMethodInvocation);
|
||||||
|
@ -168,11 +168,10 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
||||||
MethodInvocation mockMethodInvocation = spy(
|
MethodInvocation mockMethodInvocation = spy(
|
||||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||||
ReactiveAuthorizationManager.class);
|
HandlingReactiveAuthorizationManager.class);
|
||||||
PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
|
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking);
|
||||||
new LiteralExpression("1234"), new MaskingPostProcessor());
|
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision));
|
|
||||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||||
Object result = interceptor.invoke(mockMethodInvocation);
|
Object result = interceptor.invoke(mockMethodInvocation);
|
||||||
|
@ -187,11 +186,10 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
||||||
MethodInvocation mockMethodInvocation = spy(
|
MethodInvocation mockMethodInvocation = spy(
|
||||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||||
ReactiveAuthorizationManager.class);
|
HandlingReactiveAuthorizationManager.class);
|
||||||
PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
|
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::monoMasking);
|
||||||
new LiteralExpression("1234"), new MonoMaskingPostProcessor());
|
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision));
|
|
||||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||||
Object result = interceptor.invoke(mockMethodInvocation);
|
Object result = interceptor.invoke(mockMethodInvocation);
|
||||||
|
@ -206,11 +204,10 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
||||||
MethodInvocation mockMethodInvocation = spy(
|
MethodInvocation mockMethodInvocation = spy(
|
||||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||||
ReactiveAuthorizationManager.class);
|
HandlingReactiveAuthorizationManager.class);
|
||||||
PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
|
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willReturn(null);
|
||||||
new LiteralExpression("1234"), new NullPostProcessor());
|
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision));
|
|
||||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||||
Object result = interceptor.invoke(mockMethodInvocation);
|
Object result = interceptor.invoke(mockMethodInvocation);
|
||||||
|
@ -238,34 +235,18 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
||||||
verify(mockReactiveAuthorizationManager).check(any(), any());
|
verify(mockReactiveAuthorizationManager).check(any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
private PostAuthorizeAuthorizationDecision createDecision(MethodAuthorizationDeniedPostProcessor postProcessor) {
|
private Object masking(InvocationOnMock invocation) {
|
||||||
return new PostAuthorizeAuthorizationDecision(false, new LiteralExpression("1234"), postProcessor);
|
MethodInvocationResult result = invocation.getArgument(0);
|
||||||
|
return result.getResult() + "-masked";
|
||||||
}
|
}
|
||||||
|
|
||||||
static class MaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
private Object monoMasking(InvocationOnMock invocation) {
|
||||||
|
MethodInvocationResult result = invocation.getArgument(0);
|
||||||
@Override
|
return Mono.just(result.getResult() + "-masked");
|
||||||
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
|
|
||||||
return contextObject.getResult() + "-masked";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class MonoMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
interface HandlingReactiveAuthorizationManager
|
||||||
|
extends ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
|
||||||
@Override
|
|
||||||
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
|
|
||||||
return Mono.just(contextObject.getResult() + "-masked");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static class NullPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,10 @@ import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.aop.Pointcut;
|
import org.springframework.aop.Pointcut;
|
||||||
import org.springframework.expression.common.LiteralExpression;
|
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
import org.springframework.security.authorization.AuthorizationDeniedException;
|
import org.springframework.security.authorization.AuthorizationDeniedException;
|
||||||
import org.springframework.security.authorization.AuthorizationResult;
|
|
||||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
@ -125,11 +123,10 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
||||||
MethodInvocation mockMethodInvocation = spy(
|
MethodInvocation mockMethodInvocation = spy(
|
||||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||||
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||||
ReactiveAuthorizationManager.class);
|
HandlingReactiveAuthorizationManager.class);
|
||||||
PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
|
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||||
new LiteralExpression("1234"), new MaskingPostProcessor());
|
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn("***");
|
||||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision));
|
|
||||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||||
Object result = interceptor.invoke(mockMethodInvocation);
|
Object result = interceptor.invoke(mockMethodInvocation);
|
||||||
|
@ -144,11 +141,10 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
||||||
MethodInvocation mockMethodInvocation = spy(
|
MethodInvocation mockMethodInvocation = spy(
|
||||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||||
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||||
ReactiveAuthorizationManager.class);
|
HandlingReactiveAuthorizationManager.class);
|
||||||
PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
|
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||||
new LiteralExpression("1234"), new MonoMaskingPostProcessor());
|
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***"));
|
||||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision));
|
|
||||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||||
Object result = interceptor.invoke(mockMethodInvocation);
|
Object result = interceptor.invoke(mockMethodInvocation);
|
||||||
|
@ -163,11 +159,10 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
||||||
MethodInvocation mockMethodInvocation = spy(
|
MethodInvocation mockMethodInvocation = spy(
|
||||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
||||||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||||
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||||
ReactiveAuthorizationManager.class);
|
HandlingReactiveAuthorizationManager.class);
|
||||||
PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
|
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||||
new LiteralExpression("1234"), new MonoMaskingPostProcessor());
|
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***"));
|
||||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision));
|
|
||||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||||
Object result = interceptor.invoke(mockMethodInvocation);
|
Object result = interceptor.invoke(mockMethodInvocation);
|
||||||
|
@ -214,21 +209,8 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
||||||
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
||||||
}
|
}
|
||||||
|
|
||||||
static class MaskingPostProcessor implements MethodAuthorizationDeniedHandler {
|
interface HandlingReactiveAuthorizationManager
|
||||||
|
extends ReactiveAuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
|
||||||
@Override
|
|
||||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
|
|
||||||
return "***";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static class MonoMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
|
|
||||||
return Mono.just("***");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1215,6 +1215,42 @@ Spring Security will invoke the given method on that bean for each method invoca
|
||||||
What's nice about this is all your authorization logic is in a separate class that can be independently unit tested and verified for correctness.
|
What's nice about this is all your authorization logic is in a separate class that can be independently unit tested and verified for correctness.
|
||||||
It also has access to the full Java language.
|
It also has access to the full Java language.
|
||||||
|
|
||||||
|
[TIP]
|
||||||
|
In addition to returning a `Boolean`, you can also return `null` to indicate that the code abstains from making a decision.
|
||||||
|
|
||||||
|
If you want to include more information about the nature of the decision, you can instead return a custom `AuthorizationDecision` like this:
|
||||||
|
|
||||||
|
[tabs]
|
||||||
|
======
|
||||||
|
Java::
|
||||||
|
+
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Component("authz")
|
||||||
|
public class AuthorizationLogic {
|
||||||
|
public AuthorizationDecision decide(MethodSecurityExpressionOperations operations) {
|
||||||
|
// ... authorization logic
|
||||||
|
return new MyAuthorizationDecision(false, details);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
Kotlin::
|
||||||
|
+
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
@Component("authz")
|
||||||
|
open class AuthorizationLogic {
|
||||||
|
fun decide(val operations: MethodSecurityExpressionOperations): AuthorizationDecision {
|
||||||
|
// ... authorization logic
|
||||||
|
return MyAuthorizationDecision(false, details)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
======
|
||||||
|
|
||||||
|
Then, you can access the custom details when you <<fallback-values-authorization-denied, customize how the authorization result is handled>>.
|
||||||
|
|
||||||
[[custom-authorization-managers]]
|
[[custom-authorization-managers]]
|
||||||
=== Using a Custom Authorization Manager
|
=== Using a Custom Authorization Manager
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue