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 io.micrometer.observation.ObservationRegistry;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
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.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 MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
|
||||
DeferringObservationAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
|
||||
AuthorizationManager<T> delegate) {
|
||||
this.delegate = SingletonSupplier.of(() -> {
|
||||
|
@ -40,6 +52,12 @@ final class DeferringObservationAuthorizationManager<T> implements Authorization
|
|||
}
|
||||
return new ObservationAuthorizationManager<>(registry, delegate);
|
||||
});
|
||||
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
|
||||
this.handler = h;
|
||||
}
|
||||
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
|
||||
this.postProcessor = p;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -47,4 +65,15 @@ final class DeferringObservationAuthorizationManager<T> implements Authorization
|
|||
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 io.micrometer.observation.ObservationRegistry;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
|
||||
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.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 MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
|
||||
DeferringObservationReactiveAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
|
||||
ReactiveAuthorizationManager<T> delegate) {
|
||||
this.delegate = SingletonSupplier.of(() -> {
|
||||
|
@ -41,6 +53,12 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
|
|||
}
|
||||
return new ObservationReactiveAuthorizationManager<>(registry, delegate);
|
||||
});
|
||||
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
|
||||
this.handler = h;
|
||||
}
|
||||
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
|
||||
this.postProcessor = p;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -48,4 +66,15 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
|
|||
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 org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
@ -45,4 +47,20 @@ public class Authz {
|
|||
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)
|
||||
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 {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -28,4 +28,14 @@ public class MethodSecurityServiceConfig {
|
|||
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");
|
||||
}
|
||||
|
||||
@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.AuthorizationManagerBeforeMethodInterceptor;
|
||||
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.PrePostTemplateDefaults;
|
||||
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.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link PrePostMethodSecurityConfiguration}.
|
||||
|
@ -925,6 +929,23 @@ public class PrePostMethodSecurityConfigurationTests {
|
|||
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() {
|
||||
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.TargetVisitor;
|
||||
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.core.GrantedAuthorityDefaults;
|
||||
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.context.ReactiveSecurityContextHolder;
|
||||
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.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
|
||||
|
@ -65,7 +73,7 @@ public class ReactiveMethodSecurityConfigurationTests {
|
|||
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
@Autowired(required = false)
|
||||
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler;
|
||||
|
||||
@Test
|
||||
|
@ -212,6 +220,23 @@ public class ReactiveMethodSecurityConfigurationTests {
|
|||
.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) {
|
||||
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)")
|
||||
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 {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -82,4 +82,9 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
|
|||
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");
|
||||
* 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.EvaluationException;
|
||||
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 {
|
||||
|
||||
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) {
|
||||
try {
|
||||
return expr.getValue(ctx, Boolean.class);
|
||||
|
|
|
@ -21,11 +21,17 @@ import java.util.function.Supplier;
|
|||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationConvention;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
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.SpringSecurityMessageSource;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -36,7 +42,8 @@ import org.springframework.util.Assert;
|
|||
* @author Josh Cummings
|
||||
* @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;
|
||||
|
||||
|
@ -46,9 +53,19 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
|
|||
|
||||
private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
|
||||
|
||||
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
|
||||
public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager<T> delegate) {
|
||||
this.registry = registry;
|
||||
this.delegate = delegate;
|
||||
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
|
||||
this.handler = h;
|
||||
}
|
||||
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
|
||||
this.postProcessor = p;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -98,4 +115,15 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
|
|||
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.ObservationRegistry;
|
||||
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
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.util.Assert;
|
||||
|
||||
|
@ -32,7 +38,8 @@ import org.springframework.util.Assert;
|
|||
* @author Josh Cummings
|
||||
* @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;
|
||||
|
||||
|
@ -40,10 +47,20 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
|
|||
|
||||
private ObservationConvention<AuthorizationObservationContext<?>> convention = new AuthorizationObservationConvention();
|
||||
|
||||
private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
|
||||
|
||||
private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
|
||||
|
||||
public ObservationReactiveAuthorizationManager(ObservationRegistry registry,
|
||||
ReactiveAuthorizationManager<T> delegate) {
|
||||
this.registry = registry;
|
||||
this.delegate = delegate;
|
||||
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
|
||||
this.handler = h;
|
||||
}
|
||||
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
|
||||
this.postProcessor = p;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -81,4 +98,15 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
|
|||
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) {
|
||||
if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
||||
return postProcessableDecision.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.fromSupplier(() -> {
|
||||
if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
|
||||
return postProcessableDecision.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) {
|
||||
if (decision instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
return handler.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) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
if (decision instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
|
||||
return handler.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.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
|
@ -37,7 +38,8 @@ import org.springframework.security.core.Authentication;
|
|||
* @author Evgeniy Cheban
|
||||
* @since 5.6
|
||||
*/
|
||||
public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
|
||||
public final class PostAuthorizeAuthorizationManager
|
||||
implements AuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
|
||||
|
||||
private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
||||
|
||||
|
@ -88,13 +90,18 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
|
|||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||
return null;
|
||||
}
|
||||
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
|
||||
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
|
||||
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi.getMethodInvocation());
|
||||
expressionHandler.setReturnObject(mi.getResult(), ctx);
|
||||
boolean granted = ExpressionUtils.evaluateAsBoolean(postAuthorizeAttribute.getExpression(), ctx);
|
||||
return new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(),
|
||||
postAuthorizeAttribute.getPostProcessor());
|
||||
return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
|
||||
}
|
||||
|
||||
@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.prepost.PostAuthorize;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -37,7 +38,7 @@ import org.springframework.util.Assert;
|
|||
* @since 5.8
|
||||
*/
|
||||
public final class PostAuthorizeReactiveAuthorizationManager
|
||||
implements ReactiveAuthorizationManager<MethodInvocationResult> {
|
||||
implements ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
|
||||
|
||||
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
||||
|
||||
|
@ -82,15 +83,23 @@ public final class PostAuthorizeReactiveAuthorizationManager
|
|||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||
return Mono.empty();
|
||||
}
|
||||
PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
|
||||
|
||||
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
|
||||
// @formatter:off
|
||||
return authentication
|
||||
.map((auth) -> expressionHandler.createEvaluationContext(auth, mi))
|
||||
.doOnNext((ctx) -> expressionHandler.setReturnObject(result.getResult(), ctx))
|
||||
.flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx))
|
||||
.map((granted) -> new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(), postAuthorizeAttribute.getPostProcessor()));
|
||||
.flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx))
|
||||
.cast(AuthorizationDecision.class);
|
||||
// @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.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
|
@ -37,7 +38,8 @@ import org.springframework.security.core.Authentication;
|
|||
* @author Evgeniy Cheban
|
||||
* @since 5.6
|
||||
*/
|
||||
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||
public final class PreAuthorizeAuthorizationManager
|
||||
implements AuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
|
||||
|
||||
private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
||||
|
||||
|
@ -80,11 +82,15 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana
|
|||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||
return null;
|
||||
}
|
||||
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
|
||||
EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi);
|
||||
boolean granted = ExpressionUtils.evaluateAsBoolean(preAuthorizeAttribute.getExpression(), ctx);
|
||||
return new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(),
|
||||
preAuthorizeAttribute.getHandler());
|
||||
return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
|
||||
}
|
||||
|
||||
@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.prepost.PreAuthorize;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -36,7 +37,8 @@ import org.springframework.util.Assert;
|
|||
* @author Evgeniy Cheban
|
||||
* @since 5.8
|
||||
*/
|
||||
public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager<MethodInvocation> {
|
||||
public final class PreAuthorizeReactiveAuthorizationManager
|
||||
implements ReactiveAuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
|
||||
|
||||
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
||||
|
||||
|
@ -79,13 +81,19 @@ public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveA
|
|||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||
return Mono.empty();
|
||||
}
|
||||
PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
|
||||
// @formatter:off
|
||||
return authentication
|
||||
.map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi))
|
||||
.flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx))
|
||||
.map((granted) -> new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(), preAuthorizeAttribute.getHandler()));
|
||||
.flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx))
|
||||
.cast(AuthorizationDecision.class);
|
||||
// @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.EvaluationException;
|
||||
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.
|
||||
|
@ -30,6 +32,33 @@ import org.springframework.expression.Expression;
|
|||
*/
|
||||
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) {
|
||||
return Mono.defer(() -> {
|
||||
Object value;
|
||||
|
@ -56,9 +85,9 @@ final class ReactiveExpressionUtils {
|
|||
});
|
||||
}
|
||||
|
||||
private static Mono<Boolean> createInvalidReturnTypeMono(Expression expr) {
|
||||
return Mono.error(() -> new IllegalStateException(
|
||||
"Expression: '" + expr.getExpressionString() + "' must return boolean or Mono<Boolean>"));
|
||||
private static <T> Mono<T> createInvalidReturnTypeMono(Expression expr) {
|
||||
return Mono.error(() -> new IllegalStateException("Expression: '" + expr.getExpressionString()
|
||||
+ "' must return boolean, Mono<Boolean>, AuthorizationResult, or Mono<AuthorizationResult>"));
|
||||
}
|
||||
|
||||
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.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationDeniedException;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -125,10 +124,10 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
|||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.check(any(), any()))
|
||||
.will((invocation) -> Mono.just(createDecision(new MaskingPostProcessor())));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking);
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
|
@ -144,15 +143,16 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
|||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willAnswer((invocation) -> {
|
||||
MethodInvocationResult argument = invocation.getArgument(1);
|
||||
if ("john".equals(argument.getResult())) {
|
||||
return Mono.just(new AuthorizationDecision(true));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer((invocation) -> {
|
||||
MethodInvocationResult argument = invocation.getArgument(0);
|
||||
if (!"john".equals(argument.getResult())) {
|
||||
return monoMasking(invocation);
|
||||
}
|
||||
return Mono.just(createDecision(new MaskingPostProcessor()));
|
||||
return Mono.just(argument.getResult());
|
||||
});
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
|
@ -168,11 +168,10 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
|||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
|
||||
new LiteralExpression("1234"), new MaskingPostProcessor());
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking);
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
|
@ -187,11 +186,10 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
|||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
|
||||
new LiteralExpression("1234"), new MonoMaskingPostProcessor());
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::monoMasking);
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
|
@ -206,11 +204,10 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
|||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
|
||||
new LiteralExpression("1234"), new NullPostProcessor());
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willReturn(null);
|
||||
given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
|
||||
AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
|
@ -238,34 +235,18 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
|
|||
verify(mockReactiveAuthorizationManager).check(any(), any());
|
||||
}
|
||||
|
||||
private PostAuthorizeAuthorizationDecision createDecision(MethodAuthorizationDeniedPostProcessor postProcessor) {
|
||||
return new PostAuthorizeAuthorizationDecision(false, new LiteralExpression("1234"), postProcessor);
|
||||
private Object masking(InvocationOnMock invocation) {
|
||||
MethodInvocationResult result = invocation.getArgument(0);
|
||||
return result.getResult() + "-masked";
|
||||
}
|
||||
|
||||
static class MaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
|
||||
|
||||
@Override
|
||||
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
|
||||
return contextObject.getResult() + "-masked";
|
||||
}
|
||||
|
||||
private Object monoMasking(InvocationOnMock invocation) {
|
||||
MethodInvocationResult result = invocation.getArgument(0);
|
||||
return Mono.just(result.getResult() + "-masked");
|
||||
}
|
||||
|
||||
static class MonoMaskingPostProcessor implements 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;
|
||||
}
|
||||
interface HandlingReactiveAuthorizationManager
|
||||
extends ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -23,12 +23,10 @@ import reactor.core.publisher.Flux;
|
|||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationDeniedException;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -125,11 +123,10 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
|||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
|
||||
new LiteralExpression("1234"), new MaskingPostProcessor());
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn("***");
|
||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
|
@ -144,11 +141,10 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
|||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
|
||||
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
|
||||
new LiteralExpression("1234"), new MonoMaskingPostProcessor());
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***"));
|
||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
|
@ -163,11 +159,10 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
|||
MethodInvocation mockMethodInvocation = spy(
|
||||
new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
|
||||
given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
|
||||
ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
|
||||
ReactiveAuthorizationManager.class);
|
||||
PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
|
||||
new LiteralExpression("1234"), new MonoMaskingPostProcessor());
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision));
|
||||
HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
|
||||
HandlingReactiveAuthorizationManager.class);
|
||||
given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
|
||||
given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***"));
|
||||
AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
|
||||
Pointcut.TRUE, mockReactiveAuthorizationManager);
|
||||
Object result = interceptor.invoke(mockMethodInvocation);
|
||||
|
@ -214,21 +209,8 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
|
|||
verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
|
||||
}
|
||||
|
||||
static class MaskingPostProcessor implements 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("***");
|
||||
}
|
||||
interface HandlingReactiveAuthorizationManager
|
||||
extends ReactiveAuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
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]]
|
||||
=== Using a Custom Authorization Manager
|
||||
|
||||
|
|
Loading…
Reference in New Issue