Skip to content

Commit c42d175

Browse files
Max BatischevMax Batischev
Max Batischev
authored and
Max Batischev
committed
Add support expressions in MethodAuthorizationDeniedHandler
Closes gh-14857
1 parent bf478d9 commit c42d175

File tree

4 files changed

+80
-3
lines changed

4 files changed

+80
-3
lines changed

Diff for: core/src/main/java/org/springframework/security/access/prepost/PreAuthorize.java

+6
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,10 @@
4242
*/
4343
String value();
4444

45+
/**
46+
* @return Spring-EL expression to be evaluated when handling denied authorization
47+
* @since 6.3
48+
*/
49+
String handlerExpression() default "";
50+
4551
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.authorization.method;
18+
19+
import org.aopalliance.intercept.MethodInvocation;
20+
21+
import org.springframework.expression.Expression;
22+
import org.springframework.expression.ExpressionParser;
23+
import org.springframework.security.authorization.AuthorizationResult;
24+
import org.springframework.util.Assert;
25+
26+
/**
27+
* {@link MethodAuthorizationDeniedHandler} implementation, that return authorization
28+
* result, based on SpEL expression.
29+
*
30+
* @author Max Batischev
31+
* @since 6.3
32+
*/
33+
final class ExpressionMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler {
34+
35+
private final String expression;
36+
37+
private final ExpressionParser expressionParser;
38+
39+
ExpressionMethodAuthorizationDeniedHandler(String expression, ExpressionParser expressionParser) {
40+
Assert.notNull(expressionParser, "expressionParser cannot be null");
41+
Assert.notNull(expression, "expression cannot be null");
42+
this.expressionParser = expressionParser;
43+
this.expression = expression;
44+
}
45+
46+
@Override
47+
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
48+
Expression expression = this.expressionParser.parseExpression(this.expression);
49+
return expression.getValue();
50+
}
51+
52+
}

Diff for: core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java

+9-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.expression.Expression;
2929
import org.springframework.security.access.prepost.PreAuthorize;
3030
import org.springframework.util.Assert;
31+
import org.springframework.util.StringUtils;
3132

3233
/**
3334
* For internal use only, as this contract is likely to change.
@@ -55,11 +56,13 @@ ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
5556
return ExpressionAttribute.NULL_ATTRIBUTE;
5657
}
5758
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(preAuthorize.value());
58-
MethodAuthorizationDeniedHandler handler = resolveHandler(method, targetClass);
59+
MethodAuthorizationDeniedHandler handler = resolveHandler(method, targetClass,
60+
preAuthorize.handlerExpression());
5961
return new PreAuthorizeExpressionAttribute(expression, handler);
6062
}
6163

62-
private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) {
64+
private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass,
65+
String handlerExpression) {
6366
Function<AnnotatedElement, HandleAuthorizationDenied> lookup = AuthorizationAnnotationUtils
6467
.withDefaults(HandleAuthorizationDenied.class);
6568
HandleAuthorizationDenied deniedHandler = lookup.apply(method);
@@ -70,6 +73,10 @@ private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?>
7073
if (deniedHandler != null) {
7174
return this.handlerResolver.apply(deniedHandler.handlerClass());
7275
}
76+
if (StringUtils.hasText(handlerExpression)) {
77+
return new ExpressionMethodAuthorizationDeniedHandler(handlerExpression,
78+
getExpressionHandler().getExpressionParser());
79+
}
7380
return this.defaultHandler;
7481
}
7582

Diff for: core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.function.Supplier;
2222

2323
import org.junit.jupiter.api.Test;
24+
import org.mockito.Mockito;
2425

2526
import org.springframework.aop.TargetClassAware;
2627
import org.springframework.core.annotation.AnnotationConfigurationException;
@@ -31,6 +32,7 @@
3132
import org.springframework.security.authentication.TestAuthentication;
3233
import org.springframework.security.authentication.TestingAuthenticationToken;
3334
import org.springframework.security.authorization.AuthorizationDecision;
35+
import org.springframework.security.authorization.AuthorizationResult;
3436
import org.springframework.security.core.Authentication;
3537

3638
import static org.assertj.core.api.Assertions.assertThat;
@@ -156,6 +158,16 @@ public void checkRequiresUserWhenMethodsFromInheritThenApplies() throws Exceptio
156158
assertThat(decision.isGranted()).isTrue();
157159
}
158160

161+
@Test
162+
public void handleDeniedInvocationWhenHandlerExpressionIsPresentThenReturnEvaluatedValue() throws Exception {
163+
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
164+
"doSomethingString", new Class[] { String.class }, new Object[] { "deny" });
165+
AuthorizationResult authorizationResult = Mockito.mock(AuthorizationResult.class);
166+
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
167+
Object decision = manager.handleDeniedInvocation(methodInvocation, authorizationResult);
168+
assertThat(decision).isEqualTo("deny");
169+
}
170+
159171
@PreAuthorize("hasRole('USER')")
160172
public static class PreAuthorizeClass extends ParentClass {
161173

@@ -175,7 +187,7 @@ public void doSomething() {
175187

176188
}
177189

178-
@PreAuthorize("#s == 'grant'")
190+
@PreAuthorize(value = "#s == 'grant'", handlerExpression = "'deny'")
179191
public String doSomethingString(String s) {
180192
return s;
181193
}

0 commit comments

Comments
 (0)