Skip to content

Commit 44d8c3e

Browse files
Feedback changes
1 parent 6197405 commit 44d8c3e

File tree

7 files changed

+122
-28
lines changed

7 files changed

+122
-28
lines changed

core/src/main/java/org/springframework/security/authorization/AuthorizationDeniedException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class AuthorizationDeniedException extends AccessDeniedException {
3232
public AuthorizationDeniedException(String msg, AuthorizationResult authorizationResult) {
3333
super(msg);
3434
Assert.notNull(authorizationResult, "authorizationResult cannot be null");
35-
Assert.state(!authorizationResult.isGranted(), "Granted authorization results are not supported");
35+
Assert.isTrue(!authorizationResult.isGranted(), "Granted authorization results are not supported");
3636
this.result = authorizationResult;
3737
}
3838

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
import org.aopalliance.intercept.MethodInvocation;
2727

28-
import org.springframework.context.ApplicationContext;
2928
import org.springframework.core.MethodClassKey;
3029
import org.springframework.lang.NonNull;
3130
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
@@ -46,8 +45,6 @@ abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute
4645

4746
private PrePostTemplateDefaults defaults;
4847

49-
private ApplicationContext applicationContext;
50-
5148
/**
5249
* Returns an {@link ExpressionAttribute} for the {@link MethodInvocation}.
5350
* @param mi the {@link MethodInvocation} to use
@@ -93,14 +90,6 @@ void setTemplateDefaults(PrePostTemplateDefaults defaults) {
9390
this.defaults = defaults;
9491
}
9592

96-
void setApplicationContext(ApplicationContext applicationContext) {
97-
this.applicationContext = applicationContext;
98-
}
99-
100-
ApplicationContext getApplicationContext() {
101-
return this.applicationContext;
102-
}
103-
10493
/**
10594
* Subclasses should implement this method to provide the non-null
10695
* {@link ExpressionAttribute} for the method and the target class.

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ public void setTemplateDefaults(PrePostTemplateDefaults defaults) {
6161
this.registry.setTemplateDefaults(defaults);
6262
}
6363

64+
/**
65+
* Invokes
66+
* {@link PostAuthorizeExpressionAttributeRegistry#setApplicationContext(ApplicationContext)}
67+
* with the provided {@link ApplicationContext}.
68+
* @param context the {@link ApplicationContext}
69+
* @since 6.3
70+
* @see PreAuthorizeExpressionAttributeRegistry#setApplicationContext(ApplicationContext)
71+
*/
6472
public void setApplicationContext(ApplicationContext context) {
6573
this.registry.setApplicationContext(context);
6674
}

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

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
import reactor.util.annotation.NonNull;
2525

2626
import org.springframework.aop.support.AopUtils;
27+
import org.springframework.context.ApplicationContext;
2728
import org.springframework.expression.Expression;
2829
import org.springframework.security.access.prepost.PostAuthorize;
30+
import org.springframework.util.Assert;
2931

3032
/**
3133
* For internal use only, as this contract is likely to change.
@@ -38,6 +40,12 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
3840

3941
private final MethodAuthorizationDeniedPostProcessor defaultPostProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
4042

43+
private Function<Class<? extends MethodAuthorizationDeniedPostProcessor>, MethodAuthorizationDeniedPostProcessor> postProcessorResolver;
44+
45+
PostAuthorizeExpressionAttributeRegistry() {
46+
this.postProcessorResolver = (clazz) -> this.defaultPostProcessor;
47+
}
48+
4149
@NonNull
4250
@Override
4351
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
@@ -47,7 +55,8 @@ ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
4755
return ExpressionAttribute.NULL_ATTRIBUTE;
4856
}
4957
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value());
50-
MethodAuthorizationDeniedPostProcessor postProcessor = resolvePostProcessor(postAuthorize.postProcessorClass());
58+
MethodAuthorizationDeniedPostProcessor postProcessor = this.postProcessorResolver
59+
.apply(postAuthorize.postProcessorClass());
5160
return new PostAuthorizeExpressionAttribute(expression, postProcessor);
5261
}
5362

@@ -57,23 +66,30 @@ private PostAuthorize findPostAuthorizeAnnotation(Method method, Class<?> target
5766
return (postAuthorize != null) ? postAuthorize : lookup.apply(targetClass(method, targetClass));
5867
}
5968

60-
private MethodAuthorizationDeniedPostProcessor resolvePostProcessor(
69+
/**
70+
* Uses the provided {@link ApplicationContext} to resolve the
71+
* {@link MethodAuthorizationDeniedPostProcessor} from {@link PostAuthorize}
72+
* @param context the {@link ApplicationContext} to use
73+
*/
74+
void setApplicationContext(ApplicationContext context) {
75+
Assert.notNull(context, "context cannot be null");
76+
this.postProcessorResolver = (postProcessorClass) -> resolvePostProcessor(context, postProcessorClass);
77+
}
78+
79+
private MethodAuthorizationDeniedPostProcessor resolvePostProcessor(ApplicationContext context,
6180
Class<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass) {
62-
if (getApplicationContext() == null) {
63-
return this.defaultPostProcessor;
64-
}
6581
if (postProcessorClass == this.defaultPostProcessor.getClass()) {
6682
return this.defaultPostProcessor;
6783
}
68-
String[] beanNames = getApplicationContext().getBeanNamesForType(postProcessorClass);
84+
String[] beanNames = context.getBeanNamesForType(postProcessorClass);
6985
if (beanNames.length == 0) {
7086
throw new IllegalStateException("Could not find a bean of type " + postProcessorClass.getName());
7187
}
7288
if (beanNames.length > 1) {
7389
throw new IllegalStateException("Expected to find a single bean of type " + postProcessorClass.getName()
7490
+ " but found " + Arrays.toString(beanNames));
7591
}
76-
return getApplicationContext().getBean(beanNames[0], postProcessorClass);
92+
return context.getBean(beanNames[0], postProcessorClass);
7793
}
7894

7995
}

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
import reactor.util.annotation.NonNull;
2525

2626
import org.springframework.aop.support.AopUtils;
27+
import org.springframework.context.ApplicationContext;
2728
import org.springframework.expression.Expression;
2829
import org.springframework.security.access.prepost.PreAuthorize;
30+
import org.springframework.util.Assert;
2931

3032
/**
3133
* For internal use only, as this contract is likely to change.
@@ -38,6 +40,12 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
3840

3941
private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
4042

43+
private Function<Class<? extends MethodAuthorizationDeniedHandler>, MethodAuthorizationDeniedHandler> handlerResolver;
44+
45+
PreAuthorizeExpressionAttributeRegistry() {
46+
this.handlerResolver = (clazz) -> this.defaultHandler;
47+
}
48+
4149
@NonNull
4250
@Override
4351
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
@@ -47,7 +55,7 @@ ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
4755
return ExpressionAttribute.NULL_ATTRIBUTE;
4856
}
4957
Expression expression = getExpressionHandler().getExpressionParser().parseExpression(preAuthorize.value());
50-
MethodAuthorizationDeniedHandler handler = resolveHandler(preAuthorize.handlerClass());
58+
MethodAuthorizationDeniedHandler handler = this.handlerResolver.apply(preAuthorize.handlerClass());
5159
return new PreAuthorizeExpressionAttribute(expression, handler);
5260
}
5361

@@ -57,23 +65,30 @@ private PreAuthorize findPreAuthorizeAnnotation(Method method, Class<?> targetCl
5765
return (preAuthorize != null) ? preAuthorize : lookup.apply(targetClass(method, targetClass));
5866
}
5967

60-
private MethodAuthorizationDeniedHandler resolveHandler(
68+
/**
69+
* Uses the provided {@link ApplicationContext} to resolve the
70+
* {@link MethodAuthorizationDeniedHandler} from {@link PreAuthorize}.
71+
* @param context the {@link ApplicationContext} to use
72+
*/
73+
void setApplicationContext(ApplicationContext context) {
74+
Assert.notNull(context, "context cannot be null");
75+
this.handlerResolver = (clazz) -> resolveHandler(context, clazz);
76+
}
77+
78+
private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context,
6179
Class<? extends MethodAuthorizationDeniedHandler> handlerClass) {
62-
if (getApplicationContext() == null) {
63-
return this.defaultHandler;
64-
}
6580
if (handlerClass == this.defaultHandler.getClass()) {
6681
return this.defaultHandler;
6782
}
68-
String[] beanNames = getApplicationContext().getBeanNamesForType(handlerClass);
83+
String[] beanNames = context.getBeanNamesForType(handlerClass);
6984
if (beanNames.length == 0) {
7085
throw new IllegalStateException("Could not find a bean of type " + handlerClass.getName());
7186
}
7287
if (beanNames.length > 1) {
7388
throw new IllegalStateException("Expected to find a single bean of type " + handlerClass.getName()
7489
+ " but found " + Arrays.toString(beanNames));
7590
}
76-
return getApplicationContext().getBean(beanNames[0], handlerClass);
91+
return context.getBean(beanNames[0], handlerClass);
7792
}
7893

7994
}

docs/modules/ROOT/pages/servlet/authorization/method-security.adoc

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ Consider learning about the following use cases:
4444
* Understanding <<method-security-architecture,how method security works>> and reasons to use it
4545
* Comparing <<request-vs-method,request-level and method-level authorization>>
4646
* Authorizing methods with <<use-preauthorize,`@PreAuthorize`>> and <<use-postauthorize,`@PostAuthorize`>>
47+
* Providing <<fallback-values-authorization-denied,fallback values when authorization is denied>>
4748
* Filtering methods with <<use-prefilter,`@PreFilter`>> and <<use-postfilter,`@PostFilter`>>
4849
* Authorizing methods with <<use-jsr250,JSR-250 annotations>>
4950
* Authorizing methods with <<use-aspectj,AspectJ expressions>>
5051
* Integrating with <<weave-aspectj,AspectJ byte-code weaving>>
5152
* Coordinating with <<changing-the-order,@Transactional and other AOP-based annotations>>
5253
* Customizing <<customizing-expression-handling,SpEL expression handling>>
5354
* Integrating with <<custom-authorization-managers,custom authorization systems>>
54-
* Providing <<fallback-values-authorization-denied,fallback values when authorization is denied>>
5555

5656
[[method-security-architecture]]
5757
== How Method Security Works
@@ -2215,7 +2215,7 @@ You can also add the Spring Boot property `spring.jackson.default-property-inclu
22152215
There are some scenarios where you may not wish to throw an `AccessDeniedException` when a method is invoked without the required permissions.
22162216
Instead, you might wish to return a post-processed result, like a masked result, or a default value in cases where access denied happened before invoking the method.
22172217

2218-
Spring Security provides support for handling and post-processing method access denied with the `@PreAuthorize` and `@PostAuthorize` annotations respectively.
2218+
Spring Security provides support for handling and post-processing method access denied with the <<authorizing-with-annotations,`@PreAuthorize` and `@PostAuthorize` annotations>> respectively.
22192219
The `@PreAuthorize` annotation works with implementations of `MethodAuthorizationDeniedHandler` while the `@PostAuthorize` annotation works with implementations of `MethodAuthorizationDeniedPostProcessor`.
22202220

22212221
=== Using with `@PreAuthorize`
@@ -2596,6 +2596,71 @@ fun barWhenDeniedThenReturnQuestionMarks() {
25962596
----
25972597
======
25982598

2599+
=== Combining with Meta Annotation Support
2600+
2601+
Some authorization expressions may be long enough that it can become hard to read or to maintain.
2602+
For example, consider the following `@PreAuthorize` expression:
2603+
2604+
[tabs]
2605+
======
2606+
Java::
2607+
+
2608+
[source,java,role="primary"]
2609+
----
2610+
@PreAuthorize(value = "@myAuthorizationBean.check()", handlerClass = NullAuthorizationDeniedHandler.class)
2611+
public String myMethod() {
2612+
// ...
2613+
}
2614+
----
2615+
2616+
Kotlin::
2617+
+
2618+
[source,kotlin,role="secondary"]
2619+
----
2620+
@PreAuthorize(value = "@myAuthorizationBean.check()", handlerClass = NullAuthorizationDeniedHandler::class)
2621+
fun myMethod(): String {
2622+
// ...
2623+
}
2624+
----
2625+
======
2626+
2627+
The way it is, it is somewhat hard to read it, but we can do better.
2628+
By using the <<meta-annotations,meta annotation support>>, we can simplify it to:
2629+
2630+
[tabs]
2631+
======
2632+
Java::
2633+
+
2634+
[source,java,role="primary"]
2635+
----
2636+
@Target({ ElementType.METHOD, ElementType.TYPE })
2637+
@Retention(RetentionPolicy.RUNTIME)
2638+
@PreAuthorize(value = "@myAuthorizationBean.check()", handlerClass = NullAuthorizationDeniedHandler.class)
2639+
public @interface NullDenied {}
2640+
2641+
@NullDenied
2642+
public String myMethod() {
2643+
// ...
2644+
}
2645+
----
2646+
2647+
Kotlin::
2648+
+
2649+
[source,kotlin,role="secondary"]
2650+
----
2651+
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
2652+
@Retention(AnnotationRetention.RUNTIME)
2653+
@PreAuthorize(value = "@myAuthorizationBean.check()", handlerClass = NullAuthorizationDeniedHandler::class)
2654+
annotation class NullDenied
2655+
2656+
@NullDenied
2657+
fun myMethod(): String {
2658+
// ...
2659+
}
2660+
----
2661+
======
2662+
2663+
Make sure to read the <<meta-annotations,Meta Annotations Support>> section for more details on the usage.
25992664

26002665
[[migration-enableglobalmethodsecurity]]
26012666
== Migrating from `@EnableGlobalMethodSecurity`

docs/modules/ROOT/pages/whats-new.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Below are the highlights of the release.
1212

1313
- https://github.com/spring-projects/spring-security/issues/14596[gh-14596] - xref:servlet/authorization/method-security.adoc[docs] - Add Programmatic Proxy Support for Method Security
1414
- https://github.com/spring-projects/spring-security/issues/14597[gh-14597] - xref:servlet/authorization/method-security.adoc[docs] - Add Securing of Return Values
15+
- https://github.com/spring-projects/spring-security/issues/14601[gh-14601] - xref:servlet/authorization/method-security.adoc#fallback-values-authorization-denied[docs] - Add Authorization Denied Handlers for Method Security
1516

1617
== Configuration
1718

0 commit comments

Comments
 (0)