Add DefaultMethodSecurityExpressionHandler

Closes gh-12356
This commit is contained in:
Josh Cummings 2023-02-21 16:58:08 -07:00
parent 6bf11181ef
commit 35cf52d3bd
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
1 changed files with 105 additions and 0 deletions

View File

@ -104,6 +104,111 @@ should change to:
----
====
=== Use a Custom `@Bean` instead of subclassing `DefaultMethodSecurityExpressionHandler`
As a performance optimization, a new method was introduced to `MethodSecurityExpressionHandler` that takes a `Supplier<Authentication>` instead of an `Authentication`.
This allows Spring Security to defer the lookup of the `Authentication`, and is taken advantage of automatically when you use `@EnableMethodSecurity` instead of `@EnableGlobalMethodSecurity`.
However, let's say that your code extends `DefaultMethodSecurityExpressionHandler` and overrides `createSecurityExpressionRoot(Authentication, MethodInvocation)` to return a custom `SecurityExpressionRoot` instance.
This will no longer work because the arrangement that `@EnableMethodSecurity` sets up calls `createEvaluationContext(Supplier<Authentication>, MethodInvocation)` instead.
Happily, such a level of customization is often unnecessary.
Instead, you can create a custom bean with the authorization methods that you need.
For example, let's say you are wanting a custom evaluation of `@PostAuthorize("hasAuthority('ADMIN')")`.
You can create a custom `@Bean` like this one:
====
.Java
[source,java,role="primary"]
----
class MyAuthorizer {
boolean isAdmin(MethodSecurityExpressionOperations root) {
boolean decision = root.hasAuthority("ADMIN");
// custom work ...
return decision;
}
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
class MyAuthorizer {
fun isAdmin(val root: MethodSecurityExpressionOperations): boolean {
val decision = root.hasAuthority("ADMIN");
// custom work ...
return decision;
}
}
----
====
and then refer to it in the annotation like so:
====
.Java
[source,java,role="primary"]
----
@PreAuthorize("@authz.isAdmin(#root)")
----
.Kotlin
[source,kotlin,role="secondary"]
----
@PreAuthorize("@authz.isAdmin(#root)")
----
====
==== I'd still prefer to subclass `DefaultMethodSecurityExpressionHandler`
If you must continue subclassing `DefaultMethodSecurityExpressionHandler`, you can still do so.
Instead, override the `createEvaluationContext(Supplier<Authentication>, MethodInvocation)` method like so:
====
.Java
[source,java,role="primary"]
----
@Component
class MyExpressionHandler extends DefaultMethodSecurityExpressionHandler {
@Override
public EvaluationContext createEvaluationContext(
Supplier<Authentication> authentication, MethodInvocation mi) {
StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
MySecurityExpressionRoot root = new MySecurityExpressionRoot(authentication, invocation);
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(new AuthenticationTrustResolverImpl());
root.setRoleHierarchy(getRoleHierarchy());
context.setRootObject(root);
return context;
}
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Component
class MyExpressionHandler: DefaultMethodSecurityExpressionHandler {
override fun createEvaluationContext(val authentication: Supplier<Authentication>,
val mi: MethodInvocation): EvaluationContext {
val context = super.createEvaluationContext(authentication, mi) as StandardEvaluationContext;
val root = new MySecurityExpressionRoot(authentication, invocation);
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(new AuthenticationTrustResolverImpl());
root.setRoleHierarchy(getRoleHierarchy());
context.setRootObject(root);
return context;
}
}
----
====
==== Opt-out Steps
If you need to opt-out of these changes, you can use `@EnableGlobalMethodSecurity` instead of `@EnableMethodSecurity`
[[servlet-replace-permissionevaluator-bean-with-methodsecurityexpression-handler]]
=== Publish a `MethodSecurityExpressionHandler` instead of a `PermissionEvaluator`