From 46cd94b5f4787532dbd1e27cf8b65eb5a4189d45 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Tue, 4 Mar 2025 09:51:32 -0700 Subject: [PATCH] SpEL Propagates Authorization Exceptions Closes gh-16697 --- .../authorization/method/ExpressionUtils.java | 16 ++++++++++++++++ .../method/ExpressionUtilsTests.java | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core/src/main/java/org/springframework/security/authorization/method/ExpressionUtils.java b/core/src/main/java/org/springframework/security/authorization/method/ExpressionUtils.java index e0c0638257..fe6a64d959 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/ExpressionUtils.java +++ b/core/src/main/java/org/springframework/security/authorization/method/ExpressionUtils.java @@ -19,6 +19,7 @@ package org.springframework.security.authorization.method; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; +import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.ExpressionAuthorizationDecision; @@ -43,9 +44,24 @@ final class ExpressionUtils { "SpEL expression must return either a Boolean or an AuthorizationDecision"); } catch (EvaluationException ex) { + AuthorizationDeniedException denied = findAuthorizationException(ex); + if (denied != null) { + throw denied; + } throw new IllegalArgumentException("Failed to evaluate expression '" + expr.getExpressionString() + "'", ex); } } + static AuthorizationDeniedException findAuthorizationException(EvaluationException ex) { + Throwable cause = ex.getCause(); + while (cause != null) { + if (cause instanceof AuthorizationDeniedException denied) { + return denied; + } + cause = cause.getCause(); + } + return null; + } + } diff --git a/core/src/test/java/org/springframework/security/authorization/method/ExpressionUtilsTests.java b/core/src/test/java/org/springframework/security/authorization/method/ExpressionUtilsTests.java index 7dba1e1a41..1f853cd516 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/ExpressionUtilsTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/ExpressionUtilsTests.java @@ -22,9 +22,11 @@ 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.AuthorizationDeniedException; import org.springframework.security.authorization.ExpressionAuthorizationDecision; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; public class ExpressionUtilsTests { @@ -48,10 +50,23 @@ public class ExpressionUtilsTests { assertThat(ExpressionUtils.evaluate(expression, context)).isInstanceOf(ExpressionAuthorizationDecision.class); } + @Test + public void evaluateWhenExpressionThrowsAuthorizationDeniedExceptionThenPropagates() { + SpelExpressionParser parser = new SpelExpressionParser(); + Expression expression = parser.parseExpression("#root.throwException()"); + StandardEvaluationContext context = new StandardEvaluationContext(this); + assertThatExceptionOfType(AuthorizationDeniedException.class) + .isThrownBy(() -> ExpressionUtils.evaluate(expression, context)); + } + public AuthorizationDecision returnDecision() { return new AuthorizationDecisionDetails(false, this.details); } + public Object throwException() { + throw new AuthorizationDeniedException("denied"); + } + public boolean returnResult() { return false; }