From ab44d8042dc27c826ad1885930b796e8cafacc00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=8A=B9=ED=98=84?= Date: Thu, 24 Sep 2026 18:19:40 +0900 Subject: [PATCH] Add nullable contract to method security filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: μ•ˆμŠΉν˜„ --- ...efaultMethodSecurityExpressionHandler.java | 2 +- .../MethodSecurityExpressionHandler.java | 4 +-- ...hodSecurityExpressionHandlerKotlinTests.kt | 32 ++++++++++++++----- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java index 9c37a1b4126..cda0beead2c 100644 --- a/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java @@ -129,7 +129,7 @@ private MethodSecurityExpressionOperations createSecurityExpressionRoot( * {@link Stream} */ @Override - public Object filter(@Nullable Object filterTarget, Expression filterExpression, EvaluationContext ctx) { + public @Nullable Object filter(@Nullable Object filterTarget, Expression filterExpression, EvaluationContext ctx) { MethodSecurityExpressionOperations rootObject = (MethodSecurityExpressionOperations) ctx.getRootObject() .getValue(); Assert.notNull(rootObject, "rootObject cannot be null"); diff --git a/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionHandler.java index 1afe7c25dda..19ceb940bb6 100644 --- a/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionHandler.java @@ -40,9 +40,9 @@ public interface MethodSecurityExpressionHandler extends SecurityExpressionHandl * returned collection * @param ctx the current evaluation context (as created through a call to * {@link #createEvaluationContext(org.springframework.security.core.Authentication, Object)} - * @return the filtered collection or array + * @return the filtered collection or array, or {@code null} */ - Object filter(@Nullable Object filterTarget, Expression filterExpression, EvaluationContext ctx); + @Nullable Object filter(@Nullable Object filterTarget, Expression filterExpression, EvaluationContext ctx); /** * Used to inform the expression system of the return object for the given evaluation diff --git a/core/src/test/kotlin/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerKotlinTests.kt b/core/src/test/kotlin/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerKotlinTests.kt index f766c6aea01..1c16810a1d8 100644 --- a/core/src/test/kotlin/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerKotlinTests.kt +++ b/core/src/test/kotlin/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerKotlinTests.kt @@ -66,7 +66,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { "key3" to "value3", ) - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ nonEmptyMap, /* filterExpression = */ expression, /* ctx = */ context, @@ -89,7 +89,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { ) val emptyMap: Map = emptyMap() - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ emptyMap, /* filterExpression = */ expression, /* ctx = */ context, @@ -114,7 +114,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { "string1", ) - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ nonEmptyCollection, /* filterExpression = */ expression, /* ctx = */ context, @@ -136,7 +136,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { ) val emptyCollection: Collection = emptyList() - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ emptyCollection, /* filterExpression = */ expression, /* ctx = */ context, @@ -161,7 +161,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { "string1", ) - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ nonEmptyArray, /* filterExpression = */ expression, /* ctx = */ context, @@ -183,7 +183,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { ) val emptyArray: Array = emptyArray() - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ emptyArray, /* filterExpression = */ expression, /* ctx = */ context, @@ -208,7 +208,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { "string1", ).stream() - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ nonEmptyStream, /* filterExpression = */ expression, /* ctx = */ context, @@ -230,7 +230,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { ) val emptyStream: Stream = emptyList().stream() - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ emptyStream, /* filterExpression = */ expression, /* ctx = */ context, @@ -241,4 +241,20 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { val result = (filtered as Stream).toList() assertThat(result).hasSize(0) } + + @Test + fun `allows null filter results`() { + val handler = NullReturningMethodSecurityExpressionHandler() + val expression: Expression = handler.expressionParser.parseExpression("filterObject eq 'string2'") + val context: EvaluationContext = handler.createEvaluationContext( + /* authentication = */ authentication, + /* invocation = */ methodInvocation, + ) + + assertThat(handler.filter(null, expression, context)).isNull() + } + + private class NullReturningMethodSecurityExpressionHandler : DefaultMethodSecurityExpressionHandler() { + override fun filter(filterTarget: Any?, filterExpression: Expression, ctx: EvaluationContext): Any? = null + } }