Skip to content

Commit 6930987

Browse files
committed
Consider handler bean reference for HandleAuthorizationDenied
Closes spring-projectsgh-16622 Signed-off-by: Evgeniy Cheban <[email protected]>
1 parent 39b195c commit 6930987

File tree

5 files changed

+89
-15
lines changed

5 files changed

+89
-15
lines changed

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

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030
* thrown during method invocation
3131
*
3232
* @author Marcus da Coregio
33+
* @author Evgeniy Cheban
3334
* @since 6.3
3435
* @see AuthorizationManagerAfterMethodInterceptor
3536
* @see AuthorizationManagerBeforeMethodInterceptor
@@ -47,4 +48,13 @@
4748
*/
4849
Class<? extends MethodAuthorizationDeniedHandler> handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class;
4950

51+
/**
52+
* Specifies a {@link MethodAuthorizationDeniedHandler} bean name to be used to handle
53+
* denied method invocation.
54+
* @return the {@link MethodAuthorizationDeniedHandler} bean name to be used to handle
55+
* denied method invocation
56+
* @since 6.5
57+
*/
58+
String handler() default "";
59+
5060
}

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

+10-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import java.lang.reflect.Method;
2020
import java.util.Arrays;
21-
import java.util.function.Function;
21+
import java.util.function.BiFunction;
2222

2323
import reactor.util.annotation.NonNull;
2424

@@ -29,6 +29,7 @@
2929
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
3030
import org.springframework.security.core.annotation.SecurityAnnotationScanners;
3131
import org.springframework.util.Assert;
32+
import org.springframework.util.StringUtils;
3233

3334
/**
3435
* For internal use only, as this contract is likely to change.
@@ -44,13 +45,13 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
4445
private final SecurityAnnotationScanner<HandleAuthorizationDenied> handleAuthorizationDeniedScanner = SecurityAnnotationScanners
4546
.requireUnique(HandleAuthorizationDenied.class);
4647

47-
private Function<Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
48+
private BiFunction<String, Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
4849

4950
private SecurityAnnotationScanner<PostAuthorize> postAuthorizeScanner = SecurityAnnotationScanners
5051
.requireUnique(PostAuthorize.class);
5152

5253
PostAuthorizeExpressionAttributeRegistry() {
53-
this.handlerResolver = (clazz) -> new ReflectiveMethodAuthorizationDeniedHandler(clazz,
54+
this.handlerResolver = (beanName, clazz) -> new ReflectiveMethodAuthorizationDeniedHandler(clazz,
5455
PostAuthorizeAuthorizationManager.class);
5556
}
5657

@@ -70,7 +71,7 @@ private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?>
7071
Class<?> targetClassToUse = targetClass(method, targetClass);
7172
HandleAuthorizationDenied deniedHandler = this.handleAuthorizationDeniedScanner.scan(method, targetClassToUse);
7273
if (deniedHandler != null) {
73-
return this.handlerResolver.apply(deniedHandler.handlerClass());
74+
return this.handlerResolver.apply(deniedHandler.handler(), deniedHandler.handlerClass());
7475
}
7576
return this.defaultHandler;
7677
}
@@ -87,15 +88,18 @@ private PostAuthorize findPostAuthorizeAnnotation(Method method, Class<?> target
8788
*/
8889
void setApplicationContext(ApplicationContext context) {
8990
Assert.notNull(context, "context cannot be null");
90-
this.handlerResolver = (clazz) -> resolveHandler(context, clazz);
91+
this.handlerResolver = (beanName, clazz) -> resolveHandler(context, beanName, clazz);
9192
}
9293

9394
void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
9495
this.postAuthorizeScanner = SecurityAnnotationScanners.requireUnique(PostAuthorize.class, templateDefaults);
9596
}
9697

97-
private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context,
98+
private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context, String beanName,
9899
Class<? extends MethodAuthorizationDeniedHandler> handlerClass) {
100+
if (StringUtils.hasText(beanName)) {
101+
return context.getBean(beanName, MethodAuthorizationDeniedHandler.class);
102+
}
99103
if (handlerClass == this.defaultHandler.getClass()) {
100104
return this.defaultHandler;
101105
}

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

+10-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import java.lang.reflect.Method;
2020
import java.util.Arrays;
21-
import java.util.function.Function;
21+
import java.util.function.BiFunction;
2222

2323
import org.springframework.context.ApplicationContext;
2424
import org.springframework.expression.Expression;
@@ -28,6 +28,7 @@
2828
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
2929
import org.springframework.security.core.annotation.SecurityAnnotationScanners;
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.
@@ -43,13 +44,13 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
4344
private final SecurityAnnotationScanner<HandleAuthorizationDenied> handleAuthorizationDeniedScanner = SecurityAnnotationScanners
4445
.requireUnique(HandleAuthorizationDenied.class);
4546

46-
private Function<Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
47+
private BiFunction<String, Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
4748

4849
private SecurityAnnotationScanner<PreAuthorize> preAuthorizeScanner = SecurityAnnotationScanners
4950
.requireUnique(PreAuthorize.class);
5051

5152
PreAuthorizeExpressionAttributeRegistry() {
52-
this.handlerResolver = (clazz) -> new ReflectiveMethodAuthorizationDeniedHandler(clazz,
53+
this.handlerResolver = (beanName, clazz) -> new ReflectiveMethodAuthorizationDeniedHandler(clazz,
5354
PreAuthorizeAuthorizationManager.class);
5455
}
5556

@@ -69,7 +70,7 @@ private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?>
6970
Class<?> targetClassToUse = targetClass(method, targetClass);
7071
HandleAuthorizationDenied deniedHandler = this.handleAuthorizationDeniedScanner.scan(method, targetClassToUse);
7172
if (deniedHandler != null) {
72-
return this.handlerResolver.apply(deniedHandler.handlerClass());
73+
return this.handlerResolver.apply(deniedHandler.handler(), deniedHandler.handlerClass());
7374
}
7475
return this.defaultHandler;
7576
}
@@ -86,15 +87,18 @@ private PreAuthorize findPreAuthorizeAnnotation(Method method, Class<?> targetCl
8687
*/
8788
void setApplicationContext(ApplicationContext context) {
8889
Assert.notNull(context, "context cannot be null");
89-
this.handlerResolver = (clazz) -> resolveHandler(context, clazz);
90+
this.handlerResolver = (beanName, clazz) -> resolveHandler(context, beanName, clazz);
9091
}
9192

9293
void setTemplateDefaults(AnnotationTemplateExpressionDefaults defaults) {
9394
this.preAuthorizeScanner = SecurityAnnotationScanners.requireUnique(PreAuthorize.class, defaults);
9495
}
9596

96-
private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context,
97+
private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context, String beanName,
9798
Class<? extends MethodAuthorizationDeniedHandler> handlerClass) {
99+
if (StringUtils.hasText(beanName)) {
100+
return context.getBean(beanName, MethodAuthorizationDeniedHandler.class);
101+
}
98102
if (handlerClass == this.defaultHandler.getClass()) {
99103
return this.defaultHandler;
100104
}

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

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import org.aopalliance.intercept.MethodInvocation;
2727
import org.junit.jupiter.api.Test;
2828

29+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2930
import org.springframework.context.support.GenericApplicationContext;
3031
import org.springframework.core.annotation.AnnotationConfigurationException;
3132
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
@@ -179,6 +180,27 @@ public void checkWhenHandlerDeniedApplicationContextThenLooksForBean() throws Ex
179180
.isThrownBy(() -> handleDeniedInvocationResult("methodOne", manager));
180181
}
181182

183+
@Test
184+
public void checkWhenHandlerDeniedApplicationContextHandlerSpecifiedThenLooksForBean() throws Exception {
185+
GenericApplicationContext context = new GenericApplicationContext();
186+
context.registerBean("deniedHandler", NoDefaultConstructorHandler.class,
187+
() -> new NoDefaultConstructorHandler(new Object()));
188+
context.refresh();
189+
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
190+
manager.setApplicationContext(context);
191+
assertThat(handleDeniedInvocationResult("methodThree", manager)).isNull();
192+
}
193+
194+
@Test
195+
public void checkWhenHandlerDeniedApplicationContextHandlerSpecifiedThenLooksForBeanNotFound() {
196+
GenericApplicationContext context = new GenericApplicationContext();
197+
context.refresh();
198+
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
199+
manager.setApplicationContext(context);
200+
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
201+
.isThrownBy(() -> handleDeniedInvocationResult("methodThree", manager));
202+
}
203+
182204
private Object handleDeniedInvocationResult(String methodName, PostAuthorizeAuthorizationManager manager)
183205
throws Exception {
184206
MethodInvocation invocation = new MockMethodInvocation(new UsingHandleDeniedAuthorization(),
@@ -279,6 +301,12 @@ public String methodTwo() {
279301
return "ok";
280302
}
281303

304+
@HandleAuthorizationDenied(handler = "deniedHandler")
305+
@PostAuthorize("denyAll()")
306+
public String methodThree() {
307+
return "ok";
308+
}
309+
282310
}
283311

284312
public static final class NullHandler implements MethodAuthorizationDeniedHandler {

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

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
import org.junit.jupiter.api.Test;
2525

2626
import org.springframework.aop.TargetClassAware;
27+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2728
import org.springframework.context.support.GenericApplicationContext;
2829
import org.springframework.core.annotation.AnnotationConfigurationException;
2930
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
@@ -160,6 +161,27 @@ public void checkWhenHandlerDeniedApplicationContextThenLooksForBean() throws Ex
160161
.isThrownBy(() -> handleDeniedInvocationResult("methodOne", manager));
161162
}
162163

164+
@Test
165+
public void checkWhenHandlerDeniedApplicationContextHandlerSpecifiedThenLooksForBean() throws Exception {
166+
GenericApplicationContext context = new GenericApplicationContext();
167+
context.registerBean("deniedHandler", NoDefaultConstructorHandler.class,
168+
() -> new NoDefaultConstructorHandler(new Object()));
169+
context.refresh();
170+
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
171+
manager.setApplicationContext(context);
172+
assertThat(handleDeniedInvocationResult("methodThree", manager)).isNull();
173+
}
174+
175+
@Test
176+
public void checkWhenHandlerDeniedApplicationContextHandlerSpecifiedThenLooksForBeanNotFound() {
177+
GenericApplicationContext context = new GenericApplicationContext();
178+
context.refresh();
179+
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
180+
manager.setApplicationContext(context);
181+
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
182+
.isThrownBy(() -> handleDeniedInvocationResult("methodThree", manager));
183+
}
184+
163185
private Object handleDeniedInvocationResult(String methodName, PreAuthorizeAuthorizationManager manager)
164186
throws Exception {
165187
MethodInvocation invocation = new MockMethodInvocation(new UsingHandleDeniedAuthorization(),
@@ -283,6 +305,12 @@ public String methodTwo() {
283305
return "ok";
284306
}
285307

308+
@HandleAuthorizationDenied(handler = "deniedHandler")
309+
@PreAuthorize("denyAll()")
310+
public String methodThree() {
311+
return "ok";
312+
}
313+
286314
}
287315

288316
public static final class NullHandler implements MethodAuthorizationDeniedHandler {

0 commit comments

Comments
 (0)