Skip to content

Commit de77e05

Browse files
committed
Default Handler Resolution to Reflection-Based
Closes gh-15496
1 parent 02cca6f commit de77e05

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
@@ -49,7 +49,8 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
4949
.requireUnique(PostAuthorize.class);
5050

5151
PostAuthorizeExpressionAttributeRegistry() {
52-
this.handlerResolver = (clazz) -> this.defaultHandler;
52+
this.handlerResolver = (clazz) -> new ReflectiveMethodAuthorizationDeniedHandler(clazz,
53+
PostAuthorizeAuthorizationManager.class);
5354
}
5455

5556
@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
@@ -49,7 +49,8 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
4949
.requireUnique(PreAuthorize.class);
5050

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

5556
@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;
@@ -156,6 +159,34 @@ public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationE
156159
.isThrownBy(() -> manager.check(authentication, result));
157160
}
158161

162+
@Test
163+
public void checkWhenHandlerDeniedNoApplicationContextThenReflectivelyConstructs() throws Exception {
164+
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
165+
assertThat(handleDeniedInvocationResult("methodOne", manager)).isNull();
166+
assertThatExceptionOfType(IllegalArgumentException.class)
167+
.isThrownBy(() -> handleDeniedInvocationResult("methodTwo", manager));
168+
}
169+
170+
@Test
171+
public void checkWhenHandlerDeniedApplicationContextThenLooksForBean() throws Exception {
172+
GenericApplicationContext context = new GenericApplicationContext();
173+
context.registerBean(NoDefaultConstructorHandler.class, () -> new NoDefaultConstructorHandler(new Object()));
174+
context.refresh();
175+
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
176+
manager.setApplicationContext(context);
177+
assertThat(handleDeniedInvocationResult("methodTwo", manager)).isNull();
178+
assertThatExceptionOfType(IllegalStateException.class)
179+
.isThrownBy(() -> handleDeniedInvocationResult("methodOne", manager));
180+
}
181+
182+
private Object handleDeniedInvocationResult(String methodName, PostAuthorizeAuthorizationManager manager)
183+
throws Exception {
184+
MethodInvocation invocation = new MockMethodInvocation(new UsingHandleDeniedAuthorization(),
185+
UsingHandleDeniedAuthorization.class, methodName);
186+
MethodInvocationResult result = new MethodInvocationResult(invocation, null);
187+
return manager.handleDeniedInvocationResult(result, null);
188+
}
189+
159190
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
160191

161192
public void doSomething() {
@@ -234,4 +265,44 @@ public interface InterfaceAnnotationsThree {
234265

235266
}
236267

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

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;
@@ -137,6 +140,33 @@ public void checkTargetClassAwareWhenInterfaceLevelAnnotationsThenApplies() thro
137140
assertThat(decision.isGranted()).isTrue();
138141
}
139142

143+
@Test
144+
public void checkWhenHandlerDeniedNoApplicationContextThenReflectivelyConstructs() throws Exception {
145+
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
146+
assertThat(handleDeniedInvocationResult("methodOne", manager)).isNull();
147+
assertThatExceptionOfType(IllegalArgumentException.class)
148+
.isThrownBy(() -> handleDeniedInvocationResult("methodTwo", manager));
149+
}
150+
151+
@Test
152+
public void checkWhenHandlerDeniedApplicationContextThenLooksForBean() throws Exception {
153+
GenericApplicationContext context = new GenericApplicationContext();
154+
context.registerBean(NoDefaultConstructorHandler.class, () -> new NoDefaultConstructorHandler(new Object()));
155+
context.refresh();
156+
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
157+
manager.setApplicationContext(context);
158+
assertThat(handleDeniedInvocationResult("methodTwo", manager)).isNull();
159+
assertThatExceptionOfType(IllegalStateException.class)
160+
.isThrownBy(() -> handleDeniedInvocationResult("methodOne", manager));
161+
}
162+
163+
private Object handleDeniedInvocationResult(String methodName, PreAuthorizeAuthorizationManager manager)
164+
throws Exception {
165+
MethodInvocation invocation = new MockMethodInvocation(new UsingHandleDeniedAuthorization(),
166+
UsingHandleDeniedAuthorization.class, methodName);
167+
return manager.handleDeniedInvocation(invocation, null);
168+
}
169+
140170
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
141171

142172
public void doSomething() {
@@ -239,4 +269,44 @@ public void inheritedAnnotations() {
239269

240270
}
241271

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

0 commit comments

Comments
 (0)