Skip to content

Commit 34d964e

Browse files
committed
Default Handler Resolution to Reflection-Based
Closes gh-15496
1 parent 35695de commit 34d964e

File tree

5 files changed

+212
-2
lines changed

5 files changed

+212
-2
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
4242
private Function<Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
4343

4444
PostAuthorizeExpressionAttributeRegistry() {
45-
this.handlerResolver = (clazz) -> this.defaultHandler;
45+
this.handlerResolver = (clazz) -> new ReflectiveMethodAuthorizationDeniedHandler(clazz,
46+
PostAuthorizeAuthorizationManager.class);
4647
}
4748

4849
@NonNull

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
4242
private Function<Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
4343

4444
PreAuthorizeExpressionAttributeRegistry() {
45-
this.handlerResolver = (clazz) -> this.defaultHandler;
45+
this.handlerResolver = (clazz) -> new ReflectiveMethodAuthorizationDeniedHandler(clazz,
46+
PreAuthorizeAuthorizationManager.class);
4647
}
4748

4849
@NonNull
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
import org.apache.commons.logging.Log;
21+
import org.apache.commons.logging.LogFactory;
22+
23+
import org.springframework.security.authorization.AuthorizationResult;
24+
25+
final class ReflectiveMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler {
26+
27+
private final Log logger = LogFactory.getLog(getClass());
28+
29+
private final Class<?> targetClass;
30+
31+
private final Class<?> managerClass;
32+
33+
ReflectiveMethodAuthorizationDeniedHandler(Class<?> targetClass, Class<?> managerClass) {
34+
this.logger.debug(
35+
"Will attempt to instantiate handlerClass attributes using reflection since no application context was supplied to "
36+
+ managerClass);
37+
this.targetClass = targetClass;
38+
this.managerClass = managerClass;
39+
}
40+
41+
@Override
42+
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
43+
return constructMethodAuthorizationDeniedHandler().handleDeniedInvocation(methodInvocation,
44+
authorizationResult);
45+
}
46+
47+
@Override
48+
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
49+
AuthorizationResult authorizationResult) {
50+
return constructMethodAuthorizationDeniedHandler().handleDeniedInvocationResult(methodInvocationResult,
51+
authorizationResult);
52+
}
53+
54+
private MethodAuthorizationDeniedHandler constructMethodAuthorizationDeniedHandler() {
55+
try {
56+
return ((MethodAuthorizationDeniedHandler) this.targetClass.getConstructor().newInstance());
57+
}
58+
catch (Exception ex) {
59+
throw new IllegalArgumentException("Failed to construct instance of " + this.targetClass
60+
+ ". Please either add a public default constructor to the class "
61+
+ " or publish an instance of it as a Spring bean. If you publish it as a Spring bean, "
62+
+ " either add `@EnableMethodSecurity` to your configuration or "
63+
+ " provide the `ApplicationContext` directly to " + this.managerClass, ex);
64+
}
65+
}
66+
67+
}

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import java.util.List;
2424
import java.util.function.Supplier;
2525

26+
import org.aopalliance.intercept.MethodInvocation;
2627
import org.junit.jupiter.api.Test;
2728

29+
import org.springframework.context.support.GenericApplicationContext;
2830
import org.springframework.core.annotation.AnnotationConfigurationException;
2931
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
3032
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
@@ -33,6 +35,7 @@
3335
import org.springframework.security.authentication.TestAuthentication;
3436
import org.springframework.security.authentication.TestingAuthenticationToken;
3537
import org.springframework.security.authorization.AuthorizationDecision;
38+
import org.springframework.security.authorization.AuthorizationResult;
3639
import org.springframework.security.core.Authentication;
3740

3841
import static org.assertj.core.api.Assertions.assertThat;
@@ -167,6 +170,34 @@ public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationE
167170
.isThrownBy(() -> manager.check(authentication, result));
168171
}
169172

173+
@Test
174+
public void checkWhenHandlerDeniedNoApplicationContextThenReflectivelyConstructs() throws Exception {
175+
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
176+
assertThat(handleDeniedInvocationResult("methodOne", manager)).isNull();
177+
assertThatExceptionOfType(IllegalArgumentException.class)
178+
.isThrownBy(() -> handleDeniedInvocationResult("methodTwo", manager));
179+
}
180+
181+
@Test
182+
public void checkWhenHandlerDeniedApplicationContextThenLooksForBean() throws Exception {
183+
GenericApplicationContext context = new GenericApplicationContext();
184+
context.registerBean(NoDefaultConstructorHandler.class, () -> new NoDefaultConstructorHandler(new Object()));
185+
context.refresh();
186+
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
187+
manager.setApplicationContext(context);
188+
assertThat(handleDeniedInvocationResult("methodTwo", manager)).isNull();
189+
assertThatExceptionOfType(IllegalStateException.class)
190+
.isThrownBy(() -> handleDeniedInvocationResult("methodOne", manager));
191+
}
192+
193+
private Object handleDeniedInvocationResult(String methodName, PostAuthorizeAuthorizationManager manager)
194+
throws Exception {
195+
MethodInvocation invocation = new MockMethodInvocation(new UsingHandleDeniedAuthorization(),
196+
UsingHandleDeniedAuthorization.class, methodName);
197+
MethodInvocationResult result = new MethodInvocationResult(invocation, null);
198+
return manager.handleDeniedInvocationResult(result, null);
199+
}
200+
170201
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
171202

172203
public void doSomething() {
@@ -237,4 +268,44 @@ public interface InterfaceAnnotationsThree {
237268

238269
}
239270

271+
public static final class UsingHandleDeniedAuthorization {
272+
273+
@HandleAuthorizationDenied(handlerClass = NullHandler.class)
274+
@PostAuthorize("denyAll()")
275+
public String methodOne() {
276+
return "ok";
277+
}
278+
279+
@HandleAuthorizationDenied(handlerClass = NoDefaultConstructorHandler.class)
280+
@PostAuthorize("denyAll()")
281+
public String methodTwo() {
282+
return "ok";
283+
}
284+
285+
}
286+
287+
public static final class NullHandler implements MethodAuthorizationDeniedHandler {
288+
289+
@Override
290+
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
291+
AuthorizationResult authorizationResult) {
292+
return null;
293+
}
294+
295+
}
296+
297+
public static final class NoDefaultConstructorHandler implements MethodAuthorizationDeniedHandler {
298+
299+
private NoDefaultConstructorHandler(Object parameter) {
300+
301+
}
302+
303+
@Override
304+
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
305+
AuthorizationResult authorizationResult) {
306+
return null;
307+
}
308+
309+
}
310+
240311
}

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

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
import java.lang.annotation.RetentionPolicy;
2121
import java.util.function.Supplier;
2222

23+
import org.aopalliance.intercept.MethodInvocation;
2324
import org.junit.jupiter.api.Test;
2425

2526
import org.springframework.aop.TargetClassAware;
27+
import org.springframework.context.support.GenericApplicationContext;
2628
import org.springframework.core.annotation.AnnotationConfigurationException;
2729
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
2830
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
@@ -31,6 +33,7 @@
3133
import org.springframework.security.authentication.TestAuthentication;
3234
import org.springframework.security.authentication.TestingAuthenticationToken;
3335
import org.springframework.security.authorization.AuthorizationDecision;
36+
import org.springframework.security.authorization.AuthorizationResult;
3437
import org.springframework.security.core.Authentication;
3538

3639
import static org.assertj.core.api.Assertions.assertThat;
@@ -147,6 +150,33 @@ public void checkTargetClassAwareWhenInterfaceLevelAnnotationsThenApplies() thro
147150
assertThat(decision.isGranted()).isTrue();
148151
}
149152

153+
@Test
154+
public void checkWhenHandlerDeniedNoApplicationContextThenReflectivelyConstructs() throws Exception {
155+
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
156+
assertThat(handleDeniedInvocationResult("methodOne", manager)).isNull();
157+
assertThatExceptionOfType(IllegalArgumentException.class)
158+
.isThrownBy(() -> handleDeniedInvocationResult("methodTwo", manager));
159+
}
160+
161+
@Test
162+
public void checkWhenHandlerDeniedApplicationContextThenLooksForBean() throws Exception {
163+
GenericApplicationContext context = new GenericApplicationContext();
164+
context.registerBean(NoDefaultConstructorHandler.class, () -> new NoDefaultConstructorHandler(new Object()));
165+
context.refresh();
166+
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
167+
manager.setApplicationContext(context);
168+
assertThat(handleDeniedInvocationResult("methodTwo", manager)).isNull();
169+
assertThatExceptionOfType(IllegalStateException.class)
170+
.isThrownBy(() -> handleDeniedInvocationResult("methodOne", manager));
171+
}
172+
173+
private Object handleDeniedInvocationResult(String methodName, PreAuthorizeAuthorizationManager manager)
174+
throws Exception {
175+
MethodInvocation invocation = new MockMethodInvocation(new UsingHandleDeniedAuthorization(),
176+
UsingHandleDeniedAuthorization.class, methodName);
177+
return manager.handleDeniedInvocation(invocation, null);
178+
}
179+
150180
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
151181

152182
public void doSomething() {
@@ -241,4 +271,44 @@ public void inheritedAnnotations() {
241271

242272
}
243273

274+
public static final class UsingHandleDeniedAuthorization {
275+
276+
@HandleAuthorizationDenied(handlerClass = NullHandler.class)
277+
@PreAuthorize("denyAll()")
278+
public String methodOne() {
279+
return "ok";
280+
}
281+
282+
@HandleAuthorizationDenied(handlerClass = NoDefaultConstructorHandler.class)
283+
@PreAuthorize("denyAll()")
284+
public String methodTwo() {
285+
return "ok";
286+
}
287+
288+
}
289+
290+
public static final class NullHandler implements MethodAuthorizationDeniedHandler {
291+
292+
@Override
293+
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
294+
AuthorizationResult authorizationResult) {
295+
return null;
296+
}
297+
298+
}
299+
300+
public static final class NoDefaultConstructorHandler implements MethodAuthorizationDeniedHandler {
301+
302+
private NoDefaultConstructorHandler(Object parameter) {
303+
304+
}
305+
306+
@Override
307+
public Object handleDeniedInvocation(MethodInvocation methodInvocation,
308+
AuthorizationResult authorizationResult) {
309+
return null;
310+
}
311+
312+
}
313+
244314
}

0 commit comments

Comments
 (0)