Revert "Support SpEL Returning AuthorizationDecision"
This reverts commit 77f2977c55
.
This commit is contained in:
parent
77f2977c55
commit
0a9c482f62
|
@ -19,30 +19,18 @@ 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>
|
final class DeferringObservationAuthorizationManager<T> implements AuthorizationManager<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(() -> {
|
||||||
|
@ -52,12 +40,6 @@ final class DeferringObservationAuthorizationManager<T>
|
||||||
}
|
}
|
||||||
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
|
||||||
|
@ -65,15 +47,4 @@ final class DeferringObservationAuthorizationManager<T>
|
||||||
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,31 +19,19 @@ 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(() -> {
|
||||||
|
@ -53,12 +41,6 @@ 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
|
||||||
|
@ -66,15 +48,4 @@ 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,8 +18,6 @@ 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;
|
||||||
|
|
||||||
|
@ -47,20 +45,4 @@ 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,11 +173,6 @@ 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,14 +28,4 @@ public class MethodSecurityServiceConfig {
|
||||||
return new MethodSecurityServiceImpl();
|
return new MethodSecurityServiceImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
ReactiveMethodSecurityService reactiveService() {
|
|
||||||
return new ReactiveMethodSecurityServiceImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Authz authz() {
|
|
||||||
return new Authz();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,9 +197,4 @@ 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,8 +66,6 @@ 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;
|
||||||
|
@ -94,8 +92,6 @@ 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}.
|
||||||
|
@ -929,23 +925,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -1470,23 +1449,4 @@ 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,8 +47,6 @@ 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;
|
||||||
|
@ -56,14 +54,8 @@ 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
|
||||||
|
@ -73,7 +65,7 @@ public class ReactiveMethodSecurityConfigurationTests {
|
||||||
|
|
||||||
public final SpringTestContext spring = new SpringTestContext(this);
|
public final SpringTestContext spring = new SpringTestContext(this);
|
||||||
|
|
||||||
@Autowired(required = false)
|
@Autowired
|
||||||
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler;
|
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -220,23 +212,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -378,23 +353,4 @@ 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,11 +85,6 @@ 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,9 +82,4 @@ 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-2024 the original author or authors.
|
* Copyright 2002-2016 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,43 +19,12 @@ 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,17 +21,11 @@ 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;
|
||||||
|
@ -42,8 +36,7 @@ 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;
|
||||||
|
|
||||||
|
@ -53,19 +46,9 @@ 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
|
||||||
|
@ -115,15 +98,4 @@ 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,15 +20,9 @@ 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;
|
||||||
|
|
||||||
|
@ -38,8 +32,7 @@ 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;
|
||||||
|
|
||||||
|
@ -47,20 +40,10 @@ 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
|
||||||
|
@ -98,15 +81,4 @@ 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 (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
if (decision 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 (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
if (decision 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 (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
if (decision 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 (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
if (decision 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);
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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,7 +27,6 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,8 +37,7 @@ import org.springframework.security.core.Authentication;
|
||||||
* @author Evgeniy Cheban
|
* @author Evgeniy Cheban
|
||||||
* @since 5.6
|
* @since 5.6
|
||||||
*/
|
*/
|
||||||
public final class PostAuthorizeAuthorizationManager
|
public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
|
||||||
implements AuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
|
|
||||||
|
|
||||||
private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
||||||
|
|
||||||
|
@ -90,18 +88,13 @@ public final class PostAuthorizeAuthorizationManager
|
||||||
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);
|
||||||
return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
|
boolean granted = ExpressionUtils.evaluateAsBoolean(postAuthorizeAttribute.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,7 +24,6 @@ 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;
|
||||||
|
@ -38,7 +37,7 @@ import org.springframework.util.Assert;
|
||||||
* @since 5.8
|
* @since 5.8
|
||||||
*/
|
*/
|
||||||
public final class PostAuthorizeReactiveAuthorizationManager
|
public final class PostAuthorizeReactiveAuthorizationManager
|
||||||
implements ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
|
implements ReactiveAuthorizationManager<MethodInvocationResult> {
|
||||||
|
|
||||||
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
||||||
|
|
||||||
|
@ -83,23 +82,15 @@ 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.evaluate(attribute.getExpression(), ctx))
|
.flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx))
|
||||||
.cast(AuthorizationDecision.class);
|
.map((granted) -> new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(), postAuthorizeAttribute.getPostProcessor()));
|
||||||
// @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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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,7 +27,6 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,8 +37,7 @@ import org.springframework.security.core.Authentication;
|
||||||
* @author Evgeniy Cheban
|
* @author Evgeniy Cheban
|
||||||
* @since 5.6
|
* @since 5.6
|
||||||
*/
|
*/
|
||||||
public final class PreAuthorizeAuthorizationManager
|
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||||
implements AuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
|
|
||||||
|
|
||||||
private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
||||||
|
|
||||||
|
@ -82,15 +80,11 @@ public final class PreAuthorizeAuthorizationManager
|
||||||
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);
|
||||||
return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
|
boolean granted = ExpressionUtils.evaluateAsBoolean(preAuthorizeAttribute.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,7 +24,6 @@ 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;
|
||||||
|
@ -37,8 +36,7 @@ import org.springframework.util.Assert;
|
||||||
* @author Evgeniy Cheban
|
* @author Evgeniy Cheban
|
||||||
* @since 5.8
|
* @since 5.8
|
||||||
*/
|
*/
|
||||||
public final class PreAuthorizeReactiveAuthorizationManager
|
public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager<MethodInvocation> {
|
||||||
implements ReactiveAuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
|
|
||||||
|
|
||||||
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
||||||
|
|
||||||
|
@ -81,19 +79,13 @@ public final class PreAuthorizeReactiveAuthorizationManager
|
||||||
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.evaluate(attribute.getExpression(), ctx))
|
.flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx))
|
||||||
.cast(AuthorizationDecision.class);
|
.map((granted) -> new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(), preAuthorizeAttribute.getHandler()));
|
||||||
// @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,8 +21,6 @@ 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.
|
||||||
|
@ -32,33 +30,6 @@ import org.springframework.security.authorization.ExpressionAuthorizationDecisio
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
|
@ -85,9 +56,9 @@ final class ReactiveExpressionUtils {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> Mono<T> createInvalidReturnTypeMono(Expression expr) {
|
private static Mono<Boolean> createInvalidReturnTypeMono(Expression expr) {
|
||||||
return Mono.error(() -> new IllegalStateException("Expression: '" + expr.getExpressionString()
|
return Mono.error(() -> new IllegalStateException(
|
||||||
+ "' must return boolean, Mono<Boolean>, AuthorizationResult, or Mono<AuthorizationResult>"));
|
"Expression: '" + expr.getExpressionString() + "' must return boolean or Mono<Boolean>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReactiveExpressionUtils() {
|
private ReactiveExpressionUtils() {
|
||||||
|
|
|
@ -1,70 +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.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,15 +19,16 @@ 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;
|
||||||
|
@ -124,10 +125,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"));
|
||||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||||
HandlingReactiveAuthorizationManager.class);
|
ReactiveAuthorizationManager.class);
|
||||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking);
|
given(mockReactiveAuthorizationManager.check(any(), any()))
|
||||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
.will((invocation) -> Mono.just(createDecision(new MaskingPostProcessor())));
|
||||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||||
Object result = interceptor.invoke(mockMethodInvocation);
|
Object result = interceptor.invoke(mockMethodInvocation);
|
||||||
|
@ -143,16 +144,15 @@ 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"));
|
||||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||||
HandlingReactiveAuthorizationManager.class);
|
ReactiveAuthorizationManager.class);
|
||||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer((invocation) -> {
|
given(mockReactiveAuthorizationManager.check(any(), any())).willAnswer((invocation) -> {
|
||||||
MethodInvocationResult argument = invocation.getArgument(0);
|
MethodInvocationResult argument = invocation.getArgument(1);
|
||||||
if (!"john".equals(argument.getResult())) {
|
if ("john".equals(argument.getResult())) {
|
||||||
return monoMasking(invocation);
|
return Mono.just(new AuthorizationDecision(true));
|
||||||
}
|
}
|
||||||
return Mono.just(argument.getResult());
|
return 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);
|
||||||
|
@ -168,10 +168,11 @@ 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"));
|
||||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||||
HandlingReactiveAuthorizationManager.class);
|
ReactiveAuthorizationManager.class);
|
||||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking);
|
PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
|
||||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
new LiteralExpression("1234"), new MaskingPostProcessor());
|
||||||
|
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);
|
||||||
|
@ -186,10 +187,11 @@ 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"));
|
||||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||||
HandlingReactiveAuthorizationManager.class);
|
ReactiveAuthorizationManager.class);
|
||||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::monoMasking);
|
PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
|
||||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
new LiteralExpression("1234"), new MonoMaskingPostProcessor());
|
||||||
|
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);
|
||||||
|
@ -204,10 +206,11 @@ 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"));
|
||||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||||
HandlingReactiveAuthorizationManager.class);
|
ReactiveAuthorizationManager.class);
|
||||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willReturn(null);
|
PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
|
||||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
new LiteralExpression("1234"), new NullPostProcessor());
|
||||||
|
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);
|
||||||
|
@ -235,18 +238,34 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
||||||
verify(mockReactiveAuthorizationManager).check(any(), any());
|
verify(mockReactiveAuthorizationManager).check(any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object masking(InvocationOnMock invocation) {
|
private PostAuthorizeAuthorizationDecision createDecision(MethodAuthorizationDeniedPostProcessor postProcessor) {
|
||||||
MethodInvocationResult result = invocation.getArgument(0);
|
return new PostAuthorizeAuthorizationDecision(false, new LiteralExpression("1234"), postProcessor);
|
||||||
return result.getResult() + "-masked";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object monoMasking(InvocationOnMock invocation) {
|
static class MaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||||
MethodInvocationResult result = invocation.getArgument(0);
|
|
||||||
return Mono.just(result.getResult() + "-masked");
|
@Override
|
||||||
|
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
|
||||||
|
return contextObject.getResult() + "-masked";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HandlingReactiveAuthorizationManager
|
static class MonoMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||||
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,10 +23,12 @@ 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;
|
||||||
|
@ -123,10 +125,11 @@ 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"));
|
||||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
||||||
HandlingReactiveAuthorizationManager.class);
|
ReactiveAuthorizationManager.class);
|
||||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
|
||||||
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn("***");
|
new LiteralExpression("1234"), new MaskingPostProcessor());
|
||||||
|
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);
|
||||||
|
@ -141,10 +144,11 @@ 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"));
|
||||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
||||||
HandlingReactiveAuthorizationManager.class);
|
ReactiveAuthorizationManager.class);
|
||||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
|
||||||
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***"));
|
new LiteralExpression("1234"), new MonoMaskingPostProcessor());
|
||||||
|
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);
|
||||||
|
@ -159,10 +163,11 @@ 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"));
|
||||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
||||||
HandlingReactiveAuthorizationManager.class);
|
ReactiveAuthorizationManager.class);
|
||||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
|
||||||
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***"));
|
new LiteralExpression("1234"), new MonoMaskingPostProcessor());
|
||||||
|
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);
|
||||||
|
@ -209,8 +214,21 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
||||||
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HandlingReactiveAuthorizationManager
|
static class MaskingPostProcessor implements MethodAuthorizationDeniedHandler {
|
||||||
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,42 +1215,6 @@ 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