From 0a9c482f629ea2eead917595ba492a5df02fbe75 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 4 Apr 2024 11:31:45 -0600 Subject: [PATCH] Revert "Support SpEL Returning AuthorizationDecision" This reverts commit 77f2977c55842a717f8cb5c0344a7dd14b39c794. --- ...erringObservationAuthorizationManager.java | 31 +------ ...servationReactiveAuthorizationManager.java | 31 +------ .../method/configuration/Authz.java | 18 ---- .../configuration/MethodSecurityService.java | 5 -- .../MethodSecurityServiceConfig.java | 10 --- .../MethodSecurityServiceImpl.java | 5 -- ...ePostMethodSecurityConfigurationTests.java | 40 --------- ...ctiveMethodSecurityConfigurationTests.java | 46 +--------- .../ReactiveMethodSecurityService.java | 5 -- .../ReactiveMethodSecurityServiceImpl.java | 5 -- .../access/expression/ExpressionUtils.java | 33 +------ .../ObservationAuthorizationManager.java | 30 +------ ...servationReactiveAuthorizationManager.java | 30 +------ ...rizationManagerAfterMethodInterceptor.java | 2 +- ...ManagerAfterReactiveMethodInterceptor.java | 2 +- ...izationManagerBeforeMethodInterceptor.java | 2 +- ...anagerBeforeReactiveMethodInterceptor.java | 2 +- .../PostAuthorizeAuthorizationDecision.java | 41 +++++++++ .../PostAuthorizeAuthorizationManager.java | 17 ++-- ...AuthorizeReactiveAuthorizationManager.java | 17 +--- .../PreAuthorizeAuthorizationDecision.java | 43 ++++++++++ .../PreAuthorizeAuthorizationManager.java | 16 ++-- ...AuthorizeReactiveAuthorizationManager.java | 16 +--- .../method/ReactiveExpressionUtils.java | 35 +------- .../expression/ExpressionUtilsTests.java | 70 --------------- ...erAfterReactiveMethodInterceptorTests.java | 85 ++++++++++++------- ...rBeforeReactiveMethodInterceptorTests.java | 46 +++++++--- .../authorization/method-security.adoc | 36 -------- 28 files changed, 199 insertions(+), 520 deletions(-) create mode 100644 core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationDecision.java create mode 100644 core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationDecision.java delete mode 100644 core/src/test/java/org/springframework/security/access/expression/ExpressionUtilsTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java index 05068d98a9..4d534e5cfb 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java @@ -19,30 +19,18 @@ 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 - implements AuthorizationManager, MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor { +final class DeferringObservationAuthorizationManager implements AuthorizationManager { private final Supplier> delegate; - private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); - - private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor(); - DeferringObservationAuthorizationManager(ObjectProvider provider, AuthorizationManager delegate) { this.delegate = SingletonSupplier.of(() -> { @@ -52,12 +40,6 @@ final class DeferringObservationAuthorizationManager } return new ObservationAuthorizationManager<>(registry, delegate); }); - if (delegate instanceof MethodAuthorizationDeniedHandler h) { - this.handler = h; - } - if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) { - this.postProcessor = p; - } } @Override @@ -65,15 +47,4 @@ final class DeferringObservationAuthorizationManager 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); - } - } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java index 7600925bf2..9061cb64bb 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java @@ -19,31 +19,19 @@ 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 implements ReactiveAuthorizationManager, - MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor { +final class DeferringObservationReactiveAuthorizationManager implements ReactiveAuthorizationManager { private final Supplier> delegate; - private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); - - private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor(); - DeferringObservationReactiveAuthorizationManager(ObjectProvider provider, ReactiveAuthorizationManager delegate) { this.delegate = SingletonSupplier.of(() -> { @@ -53,12 +41,6 @@ final class DeferringObservationReactiveAuthorizationManager implements React } return new ObservationReactiveAuthorizationManager<>(registry, delegate); }); - if (delegate instanceof MethodAuthorizationDeniedHandler h) { - this.handler = h; - } - if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) { - this.postProcessor = p; - } } @Override @@ -66,15 +48,4 @@ final class DeferringObservationReactiveAuthorizationManager 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); - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/Authz.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/Authz.java index 145f344d12..9a9ff57da4 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/Authz.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/Authz.java @@ -18,8 +18,6 @@ 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; @@ -47,20 +45,4 @@ public class Authz { return message != null && message.contains(authentication.getName()); } - public AuthorizationResult checkResult(boolean result) { - return new AuthzResult(result); - } - - public Mono checkReactiveResult(boolean result) { - return Mono.just(checkResult(result)); - } - - public static class AuthzResult extends AuthorizationDecision { - - public AuthzResult(boolean granted) { - super(granted); - } - - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java index f8e1889c38..c3162debb3 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java @@ -173,11 +173,6 @@ 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 diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java index a5c78f6d96..ee664f5a45 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java @@ -28,14 +28,4 @@ public class MethodSecurityServiceConfig { return new MethodSecurityServiceImpl(); } - @Bean - ReactiveMethodSecurityService reactiveService() { - return new ReactiveMethodSecurityServiceImpl(); - } - - @Bean - Authz authz() { - return new Authz(); - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java index 4fd5b7fe1a..e44e9e048c 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java @@ -197,9 +197,4 @@ public class MethodSecurityServiceImpl implements MethodSecurityService { return new UserRecordWithEmailProtected("username", "useremail@example.com"); } - @Override - public String checkCustomResult(boolean result) { - return "ok"; - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java index f9b16350b6..72981bbd83 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java @@ -66,8 +66,6 @@ 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; @@ -94,8 +92,6 @@ 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}. @@ -929,23 +925,6 @@ 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 disallowBeanOverriding() { 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; - } - - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java index 1a2fc446a7..60c54195bf 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java @@ -47,8 +47,6 @@ 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; @@ -56,14 +54,8 @@ 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 @@ -73,7 +65,7 @@ public class ReactiveMethodSecurityConfigurationTests { public final SpringTestContext spring = new SpringTestContext(this); - @Autowired(required = false) + @Autowired DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler; @Test @@ -220,23 +212,6 @@ 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 authorities(String... 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; - } - - } - } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java index 66c83348cb..836340a7ee 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java @@ -85,11 +85,6 @@ public interface ReactiveMethodSecurityService { @Mask(expression = "@myMasker.getMask(returnObject)") Mono postAuthorizeWithMaskAnnotationUsingBean(); - @PreAuthorize(value = "@authz.checkReactiveResult(#result)", handlerClass = MethodAuthorizationDeniedHandler.class) - @PostAuthorize(value = "@authz.checkReactiveResult(!#result)", - postProcessorClass = MethodAuthorizationDeniedPostProcessor.class) - Mono checkCustomResult(boolean result); - class StarMaskingHandler implements MethodAuthorizationDeniedHandler { @Override diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java index ce6a0204b6..7fb421585a 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java @@ -82,9 +82,4 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity return Mono.just("ok"); } - @Override - public Mono checkCustomResult(boolean result) { - return Mono.just("ok"); - } - } diff --git a/core/src/main/java/org/springframework/security/access/expression/ExpressionUtils.java b/core/src/main/java/org/springframework/security/access/expression/ExpressionUtils.java index 1dff67ec98..5296a3eacb 100644 --- a/core/src/main/java/org/springframework/security/access/expression/ExpressionUtils.java +++ b/core/src/main/java/org/springframework/security/access/expression/ExpressionUtils.java @@ -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"); * 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.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); diff --git a/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java index 3f0050b0c4..943e46dce3 100644 --- a/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java @@ -21,17 +21,11 @@ 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; @@ -42,8 +36,7 @@ import org.springframework.util.Assert; * @author Josh Cummings * @since 6.0 */ -public final class ObservationAuthorizationManager implements AuthorizationManager, MessageSourceAware, - MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor { +public final class ObservationAuthorizationManager implements AuthorizationManager, MessageSourceAware { private final ObservationRegistry registry; @@ -53,19 +46,9 @@ public final class ObservationAuthorizationManager implements AuthorizationMa private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); - - private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor(); - public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager delegate) { this.registry = registry; this.delegate = delegate; - if (delegate instanceof MethodAuthorizationDeniedHandler h) { - this.handler = h; - } - if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) { - this.postProcessor = p; - } } @Override @@ -115,15 +98,4 @@ public final class ObservationAuthorizationManager 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); - } - } diff --git a/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java index d84aa7e996..83f4cb0609 100644 --- a/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java @@ -20,15 +20,9 @@ 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; @@ -38,8 +32,7 @@ import org.springframework.util.Assert; * @author Josh Cummings * @since 6.0 */ -public final class ObservationReactiveAuthorizationManager implements ReactiveAuthorizationManager, - MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor { +public final class ObservationReactiveAuthorizationManager implements ReactiveAuthorizationManager { private final ObservationRegistry registry; @@ -47,20 +40,10 @@ public final class ObservationReactiveAuthorizationManager implements Reactiv private ObservationConvention> convention = new AuthorizationObservationConvention(); - private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); - - private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor(); - public ObservationReactiveAuthorizationManager(ObservationRegistry registry, ReactiveAuthorizationManager 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,15 +81,4 @@ public final class ObservationReactiveAuthorizationManager 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); - } - } diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java index d5ca8b5f16..005d644320 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java @@ -184,7 +184,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori } private Object postProcess(MethodInvocationResult mi, AuthorizationDecision decision) { - if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) { + if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) { return postProcessableDecision.postProcessResult(mi, decision); } return this.defaultPostProcessor.postProcessResult(mi, decision); diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java index c1e6a297b1..477dc290be 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java @@ -159,7 +159,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements return Mono.just(methodInvocationResult.getResult()); } return Mono.fromSupplier(() -> { - if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) { + if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) { return postProcessableDecision.postProcessResult(methodInvocationResult, decision); } return this.defaultPostProcessor.postProcessResult(methodInvocationResult, decision); diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java index 371c8a2c1d..aa96c2fb34 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java @@ -257,7 +257,7 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author } private Object handle(MethodInvocation mi, AuthorizationDecision decision) { - if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { + if (decision instanceof MethodAuthorizationDeniedHandler handler) { return handler.handle(mi, decision); } return this.defaultHandler.handle(mi, decision); diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java index 6b1bab5f9d..93bfb0d4a0 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java @@ -162,7 +162,7 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement private Mono postProcess(AuthorizationDecision decision, MethodInvocation mi) { return Mono.fromSupplier(() -> { - if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { + if (decision instanceof MethodAuthorizationDeniedHandler handler) { return handler.handle(mi, decision); } return this.defaultHandler.handle(mi, decision); diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationDecision.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationDecision.java new file mode 100644 index 0000000000..136542cebe --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationDecision.java @@ -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); + } + +} diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java index 02022d2ccc..cd4f406544 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java @@ -27,7 +27,6 @@ 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; /** @@ -38,8 +37,7 @@ import org.springframework.security.core.Authentication; * @author Evgeniy Cheban * @since 5.6 */ -public final class PostAuthorizeAuthorizationManager - implements AuthorizationManager, MethodAuthorizationDeniedPostProcessor { +public final class PostAuthorizeAuthorizationManager implements AuthorizationManager { private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); @@ -90,18 +88,13 @@ public final class PostAuthorizeAuthorizationManager 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); - 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); + boolean granted = ExpressionUtils.evaluateAsBoolean(postAuthorizeAttribute.getExpression(), ctx); + return new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(), + postAuthorizeAttribute.getPostProcessor()); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java index 4c988c0593..65b91ec81c 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java @@ -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.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; @@ -38,7 +37,7 @@ import org.springframework.util.Assert; * @since 5.8 */ public final class PostAuthorizeReactiveAuthorizationManager - implements ReactiveAuthorizationManager, MethodAuthorizationDeniedPostProcessor { + implements ReactiveAuthorizationManager { private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); @@ -83,23 +82,15 @@ 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.evaluate(attribute.getExpression(), ctx)) - .cast(AuthorizationDecision.class); + .flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx)) + .map((granted) -> new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(), postAuthorizeAttribute.getPostProcessor())); // @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); - } - } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationDecision.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationDecision.java new file mode 100644 index 0000000000..f41e70b05f --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationDecision.java @@ -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); + } + +} diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java index fdab49b340..fc0a00857b 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java @@ -27,7 +27,6 @@ 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; /** @@ -38,8 +37,7 @@ import org.springframework.security.core.Authentication; * @author Evgeniy Cheban * @since 5.6 */ -public final class PreAuthorizeAuthorizationManager - implements AuthorizationManager, MethodAuthorizationDeniedHandler { +public final class PreAuthorizeAuthorizationManager implements AuthorizationManager { private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry(); @@ -82,15 +80,11 @@ public final class PreAuthorizeAuthorizationManager if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) { return null; } + PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute; EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi); - 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); + boolean granted = ExpressionUtils.evaluateAsBoolean(preAuthorizeAttribute.getExpression(), ctx); + return new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(), + preAuthorizeAttribute.getHandler()); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java index b9b9b2cc6e..9a640926d1 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java @@ -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.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; @@ -37,8 +36,7 @@ import org.springframework.util.Assert; * @author Evgeniy Cheban * @since 5.8 */ -public final class PreAuthorizeReactiveAuthorizationManager - implements ReactiveAuthorizationManager, MethodAuthorizationDeniedHandler { +public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager { private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry(); @@ -81,19 +79,13 @@ public final class PreAuthorizeReactiveAuthorizationManager 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.evaluate(attribute.getExpression(), ctx)) - .cast(AuthorizationDecision.class); + .flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx)) + .map((granted) -> new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(), preAuthorizeAttribute.getHandler())); // @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); - } - } diff --git a/core/src/main/java/org/springframework/security/authorization/method/ReactiveExpressionUtils.java b/core/src/main/java/org/springframework/security/authorization/method/ReactiveExpressionUtils.java index 6c80c0d364..2675bb96dc 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/ReactiveExpressionUtils.java +++ b/core/src/main/java/org/springframework/security/authorization/method/ReactiveExpressionUtils.java @@ -21,8 +21,6 @@ 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. @@ -32,33 +30,6 @@ import org.springframework.security.authorization.ExpressionAuthorizationDecisio */ final class ReactiveExpressionUtils { - static Mono 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 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 evaluateAsBoolean(Expression expr, EvaluationContext ctx) { return Mono.defer(() -> { Object value; @@ -85,9 +56,9 @@ final class ReactiveExpressionUtils { }); } - private static Mono createInvalidReturnTypeMono(Expression expr) { - return Mono.error(() -> new IllegalStateException("Expression: '" + expr.getExpressionString() - + "' must return boolean, Mono, AuthorizationResult, or Mono")); + private static Mono createInvalidReturnTypeMono(Expression expr) { + return Mono.error(() -> new IllegalStateException( + "Expression: '" + expr.getExpressionString() + "' must return boolean or Mono")); } private ReactiveExpressionUtils() { diff --git a/core/src/test/java/org/springframework/security/access/expression/ExpressionUtilsTests.java b/core/src/test/java/org/springframework/security/access/expression/ExpressionUtilsTests.java deleted file mode 100644 index d54e1d32fb..0000000000 --- a/core/src/test/java/org/springframework/security/access/expression/ExpressionUtilsTests.java +++ /dev/null @@ -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; - } - - } - -} diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java index cef3ce38b7..bf93e7351c 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java @@ -19,15 +19,16 @@ 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; @@ -124,10 +125,10 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests { MethodInvocation mockMethodInvocation = spy( new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux"))); given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking); - given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); + ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( + ReactiveAuthorizationManager.class); + given(mockReactiveAuthorizationManager.check(any(), any())) + .will((invocation) -> Mono.just(createDecision(new MaskingPostProcessor()))); AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -143,16 +144,15 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests { MethodInvocation mockMethodInvocation = spy( new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux"))); given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer((invocation) -> { - MethodInvocationResult argument = invocation.getArgument(0); - if (!"john".equals(argument.getResult())) { - return monoMasking(invocation); + ReactiveAuthorizationManager 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)); } - return Mono.just(argument.getResult()); + return Mono.just(createDecision(new MaskingPostProcessor())); }); - given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -168,10 +168,11 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests { MethodInvocation mockMethodInvocation = spy( new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking); - given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); + ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( + ReactiveAuthorizationManager.class); + PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false, + new LiteralExpression("1234"), new MaskingPostProcessor()); + given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision)); AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -186,10 +187,11 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests { MethodInvocation mockMethodInvocation = spy( new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::monoMasking); - given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); + ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( + ReactiveAuthorizationManager.class); + PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false, + new LiteralExpression("1234"), new MonoMaskingPostProcessor()); + given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision)); AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -204,10 +206,11 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests { MethodInvocation mockMethodInvocation = spy( new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willReturn(null); - given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); + ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( + ReactiveAuthorizationManager.class); + PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false, + new LiteralExpression("1234"), new NullPostProcessor()); + given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision)); AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -235,18 +238,34 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests { verify(mockReactiveAuthorizationManager).check(any(), any()); } - private Object masking(InvocationOnMock invocation) { - MethodInvocationResult result = invocation.getArgument(0); - return result.getResult() + "-masked"; + private PostAuthorizeAuthorizationDecision createDecision(MethodAuthorizationDeniedPostProcessor postProcessor) { + return new PostAuthorizeAuthorizationDecision(false, new LiteralExpression("1234"), postProcessor); } - private Object monoMasking(InvocationOnMock invocation) { - MethodInvocationResult result = invocation.getArgument(0); - return Mono.just(result.getResult() + "-masked"); + static class MaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { + + @Override + public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) { + return contextObject.getResult() + "-masked"; + } + } - interface HandlingReactiveAuthorizationManager - extends ReactiveAuthorizationManager, MethodAuthorizationDeniedPostProcessor { + 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; + } } diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java index 54dfcd6ed8..d45bd42fa7 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java @@ -23,10 +23,12 @@ 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; @@ -123,10 +125,11 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests { MethodInvocation mockMethodInvocation = spy( new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); - given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn("***"); + ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( + ReactiveAuthorizationManager.class); + PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false, + new LiteralExpression("1234"), new MaskingPostProcessor()); + given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision)); AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -141,10 +144,11 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests { MethodInvocation mockMethodInvocation = spy( new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); - given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***")); + ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( + ReactiveAuthorizationManager.class); + PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false, + new LiteralExpression("1234"), new MonoMaskingPostProcessor()); + given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision)); AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -159,10 +163,11 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests { MethodInvocation mockMethodInvocation = spy( new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux"))); given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); - HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( - HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); - given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***")); + ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( + ReactiveAuthorizationManager.class); + PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false, + new LiteralExpression("1234"), new MonoMaskingPostProcessor()); + given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision)); AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -209,8 +214,21 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests { verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation)); } - interface HandlingReactiveAuthorizationManager - extends ReactiveAuthorizationManager, MethodAuthorizationDeniedHandler { + 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("***"); + } } diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index 2a6be2454d..6bdd1ca4e9 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc @@ -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. 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 <>. - [[custom-authorization-managers]] === Using a Custom Authorization Manager