diff --git a/core/src/main/java/org/springframework/security/authorization/method/ExpressionMethodAuthorizationDeniedHandler.java b/core/src/main/java/org/springframework/security/authorization/method/ExpressionMethodAuthorizationDeniedHandler.java new file mode 100644 index 00000000000..170549c7e30 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/method/ExpressionMethodAuthorizationDeniedHandler.java @@ -0,0 +1,52 @@ +/* + * 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.expression.ExpressionParser; +import org.springframework.security.authorization.AuthorizationResult; +import org.springframework.util.Assert; + +/** + * {@link MethodAuthorizationDeniedHandler} implementation, that return authorization + * result, based on SpEL expression. + * + * @author Max Batischev + * @since 6.3 + */ +final class ExpressionMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { + + private final String expression; + + private final ExpressionParser expressionParser; + + ExpressionMethodAuthorizationDeniedHandler(String expression, ExpressionParser expressionParser) { + Assert.notNull(expressionParser, "expressionParser cannot be null"); + Assert.notNull(expression, "expression cannot be null"); + this.expressionParser = expressionParser; + this.expression = expression; + } + + @Override + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + Expression expression = this.expressionParser.parseExpression(this.expression); + return expression.getValue(); + } + +} diff --git a/core/src/main/java/org/springframework/security/authorization/method/HandleAuthorizationDenied.java b/core/src/main/java/org/springframework/security/authorization/method/HandleAuthorizationDenied.java index 7a28e9324ef..c7bc326e961 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/HandleAuthorizationDenied.java +++ b/core/src/main/java/org/springframework/security/authorization/method/HandleAuthorizationDenied.java @@ -47,4 +47,9 @@ */ Class handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class; + /** + * @return SpEL expression to be evaluated when handling denied authorization + */ + String handlerExpression() default ""; + } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java index 2bfe20a9324..ac04d5cc03e 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java @@ -28,6 +28,7 @@ import org.springframework.expression.Expression; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * For internal use only, as this contract is likely to change. @@ -64,6 +65,10 @@ private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class .withDefaults(HandleAuthorizationDenied.class); HandleAuthorizationDenied deniedHandler = lookup.apply(method); if (deniedHandler != null) { + if (StringUtils.hasText(deniedHandler.handlerExpression())) { + return new ExpressionMethodAuthorizationDeniedHandler(deniedHandler.handlerExpression(), + getExpressionHandler().getExpressionParser()); + } return this.handlerResolver.apply(deniedHandler.handlerClass()); } deniedHandler = lookup.apply(targetClass(method, targetClass)); diff --git a/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java index 6b8153ba618..673935b59c9 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java @@ -21,6 +21,7 @@ import java.util.function.Supplier; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.aop.TargetClassAware; import org.springframework.core.annotation.AnnotationConfigurationException; @@ -31,6 +32,7 @@ import org.springframework.security.authentication.TestAuthentication; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; import static org.assertj.core.api.Assertions.assertThat; @@ -156,6 +158,16 @@ public void checkRequiresUserWhenMethodsFromInheritThenApplies() throws Exceptio assertThat(decision.isGranted()).isTrue(); } + @Test + public void handleDeniedInvocationWhenHandlerExpressionIsPresentThenReturnEvaluatedValue() throws Exception { + MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, + "doSomethingString", new Class[] { String.class }, new Object[] { "deny" }); + AuthorizationResult authorizationResult = Mockito.mock(AuthorizationResult.class); + PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager(); + Object decision = manager.handleDeniedInvocation(methodInvocation, authorizationResult); + assertThat(decision).isEqualTo("deny"); + } + @PreAuthorize("hasRole('USER')") public static class PreAuthorizeClass extends ParentClass { @@ -176,6 +188,7 @@ public void doSomething() { } @PreAuthorize("#s == 'grant'") + @HandleAuthorizationDenied(handlerExpression = "'deny'") public String doSomethingString(String s) { return s; }