Skip to content

Commit 765bdf1

Browse files
committed
SpEL Expressions Support Returning AuthorizationManager
Closes spring-projectsgh-17936
1 parent 25e4131 commit 765bdf1

14 files changed

+84
-4
lines changed

config/src/test/java/org/springframework/security/config/annotation/method/configuration/Authz.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616

1717
package org.springframework.security.config.annotation.method.configuration;
1818

19+
import org.aopalliance.intercept.MethodInvocation;
1920
import reactor.core.publisher.Mono;
2021

2122
import org.springframework.security.authorization.AuthorizationDecision;
23+
import org.springframework.security.authorization.AuthorizationManager;
2224
import org.springframework.security.authorization.AuthorizationResult;
25+
import org.springframework.security.authorization.ReactiveAuthorizationManager;
2326
import org.springframework.security.core.Authentication;
2427
import org.springframework.stereotype.Component;
2528

@@ -55,6 +58,14 @@ public Mono<AuthorizationResult> checkReactiveResult(boolean result) {
5558
return Mono.just(checkResult(result));
5659
}
5760

61+
public AuthorizationManager<MethodInvocation> checkManager(long id) {
62+
return (authentication, context) -> new AuthorizationDecision(check(id));
63+
}
64+
65+
public ReactiveAuthorizationManager<MethodInvocation> checkReactiveManager(long id) {
66+
return (authentication, context) -> checkReactive(id).map(AuthorizationDecision::new);
67+
}
68+
5869
@SuppressWarnings("serial")
5970
public static class AuthzResult extends AuthorizationDecision {
6071

config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ public interface MethodSecurityService {
196196
@HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
197197
String checkCustomResult(boolean result);
198198

199+
@PreAuthorize("@authz.checkManager(#id)")
200+
String checkCustomManager(long id);
201+
199202
class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
200203

201204
@Override

config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ public String checkCustomResult(boolean result) {
203203
return "ok";
204204
}
205205

206+
@Override
207+
public String checkCustomManager(long id) {
208+
return "ok";
209+
}
210+
206211
@Override
207212
public void hasAllRolesUserAdmin() {
208213
}

config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
import org.springframework.security.access.prepost.PreAuthorize;
9393
import org.springframework.security.access.prepost.PreFilter;
9494
import org.springframework.security.authorization.AuthorizationDecision;
95+
import org.springframework.security.authorization.AuthorizationDeniedException;
9596
import org.springframework.security.authorization.AuthorizationEventPublisher;
9697
import org.springframework.security.authorization.AuthorizationManager;
9798
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
@@ -1410,6 +1411,14 @@ void getWhenCustomAdvisorAuthenticationNameNotMatchThenRespondsWithForbidden() t
14101411
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
14111412
}
14121413

1414+
@Test
1415+
void checkCustomManagerWhenInvokedThenUsesBeanToAuthorize() {
1416+
this.spring.register(MethodSecurityServiceConfig.class).autowire();
1417+
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
1418+
service.checkCustomManager(2);
1419+
assertThatExceptionOfType(AuthorizationDeniedException.class).isThrownBy(() -> service.checkCustomManager(1));
1420+
}
1421+
14131422
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
14141423
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
14151424
}

config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.springframework.security.access.prepost.PreAuthorize;
5252
import org.springframework.security.access.prepost.PreFilter;
5353
import org.springframework.security.authentication.TestAuthentication;
54+
import org.springframework.security.authorization.AuthorizationDeniedException;
5455
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
5556
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
5657
import org.springframework.security.authorization.method.AuthorizeReturnObject;
@@ -66,6 +67,7 @@
6667
import org.springframework.security.test.context.support.WithMockUser;
6768

6869
import static org.assertj.core.api.Assertions.assertThat;
70+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
6971
import static org.mockito.ArgumentMatchers.any;
7072
import static org.mockito.Mockito.clearInvocations;
7173
import static org.mockito.Mockito.mock;
@@ -285,6 +287,15 @@ public void prePostMethodWhenExcludeAuthorizationObservationsThenUnobserved() {
285287
verifyNoInteractions(handler);
286288
}
287289

290+
@Test
291+
void checkCustomManagerWhenInvokedThenUsesBeanToAuthorize() {
292+
this.spring.register(WithRolePrefixConfiguration.class, MethodSecurityServiceConfig.class).autowire();
293+
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
294+
service.checkCustomManager(2).block();
295+
assertThatExceptionOfType(AuthorizationDeniedException.class)
296+
.isThrownBy(() -> service.checkCustomManager(1).block());
297+
}
298+
288299
private static Consumer<User.UserBuilder> authorities(String... authorities) {
289300
return (builder) -> builder.authorities(authorities);
290301
}

config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ public interface ReactiveMethodSecurityService {
110110
@HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
111111
Mono<String> checkCustomResult(boolean result);
112112

113+
@PreAuthorize("@authz.checkReactiveManager(#id)")
114+
Mono<String> checkCustomManager(long id);
115+
113116
@PreAuthorize("hasPermission(#kgName, 'read')")
114117
Mono<String> preAuthorizeHasPermission(String kgName);
115118

config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ public Mono<String> checkCustomResult(boolean result) {
100100
return Mono.just("ok");
101101
}
102102

103+
@Override
104+
public Mono<String> checkCustomManager(long id) {
105+
return Mono.just("ok");
106+
}
107+
103108
@Override
104109
public Mono<String> preAuthorizeHasPermission(String kgName) {
105110
return Mono.just("ok");

core/src/main/java/org/springframework/security/authorization/method/ExpressionUtils.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,38 @@
1616

1717
package org.springframework.security.authorization.method;
1818

19+
import java.util.function.Supplier;
20+
1921
import org.jspecify.annotations.Nullable;
2022

2123
import org.springframework.expression.EvaluationContext;
2224
import org.springframework.expression.EvaluationException;
2325
import org.springframework.expression.Expression;
2426
import org.springframework.security.authorization.AuthorizationDeniedException;
27+
import org.springframework.security.authorization.AuthorizationManager;
2528
import org.springframework.security.authorization.AuthorizationResult;
2629
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
30+
import org.springframework.security.core.Authentication;
31+
import org.springframework.util.Assert;
2732

2833
final class ExpressionUtils {
2934

3035
private ExpressionUtils() {
3136
}
3237

3338
static @Nullable AuthorizationResult evaluate(Expression expr, EvaluationContext ctx) {
39+
return evaluate(expr, ctx, () -> null, null);
40+
}
41+
42+
static <T> @Nullable AuthorizationResult evaluate(Expression expr, EvaluationContext ctx,
43+
Supplier<? extends @Nullable Authentication> authentication, @Nullable T context) {
3444
try {
3545
Object result = expr.getValue(ctx);
46+
if (result instanceof AuthorizationManager<?> manager) {
47+
Assert.notNull(authentication, "authentication supplier cannot be null");
48+
Assert.notNull(context, "context cannot be null");
49+
return ((AuthorizationManager<T>) manager).authorize(authentication, context);
50+
}
3651
if (result instanceof AuthorizationResult decision) {
3752
return decision;
3853
}

core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public void setApplicationContext(ApplicationContext context) {
9595
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
9696
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi.getMethodInvocation());
9797
expressionHandler.setReturnObject(mi.getResult(), ctx);
98-
return ExpressionUtils.evaluate(attribute.getExpression(), ctx);
98+
return ExpressionUtils.evaluate(attribute.getExpression(), ctx, authentication, mi);
9999
}
100100

101101
@Override

core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public Mono<AuthorizationResult> authorize(Mono<Authentication> authentication,
9191
return authentication
9292
.map((auth) -> expressionHandler.createEvaluationContext(auth, mi))
9393
.doOnNext((ctx) -> expressionHandler.setReturnObject(result.getResult(), ctx))
94-
.flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx))
94+
.flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx, authentication, result))
9595
.cast(AuthorizationResult.class);
9696
// @formatter:on
9797
}

0 commit comments

Comments
 (0)