Evaluation of Spring Security expressions programmatically

Well, it is already some time ago I had written something about using the Spring ecosystem. After this (very) long pause, I will try to catch up. In this post we will have a deeper look at the Spring Expression Language (SpEL) and especially in context of Spring Security and its authentication checks.

The goal of this explanation is the answer to the following question respective scenario: How can we evaluate authorisation checks like defined in @PreAuthorize, @PostAuthorize or @Secured on demand?

The complete working demo is available at GitHub. PRs are open in case of errors or additions.

The Goal

Or, in a more specific way questioned: Let’s say we have a controller like this:

This setup would allow requests to /users/1 only if the user does have the permission READ for the given user id. This is a no-brainer when using permission checks.

But how can we call this programmatically? Something like

Of course, the functions supported in this SpEL-context are not unique and there are alternatives which do not require following that question at first. A hasPermission() is implemented by the PermissionEvaluator which you may have exposed via a bean. The hasRole() is tricky: You may think ’that is easy’, just use UserDetails#getAuthorities() and yes, maybe it is enough. However, the standard behavior does not extend the authorities (which includes the roles) when a role hierarchy is defined. The actual interpolation takes place in the evaluation unit therefore the users authorities does not includes inherited roles.

Let’s change the example being a bit more complex:

Technically, this is a complex SpEL (expression). Let’s assume we want to render a link to this endpoint but only if the (current) user fulfills the required permissions (either having the permission DELETE of the user or having the role ADMIN). Obviously, we can rebuild the logic of this permission expression based on actual permission evaluator and checks of the actual user and its roles. However, things getting complex if (security) checks are being located multiple times at different places.

It would be far better if we can use the same expression language. And of top of it: exactly the same expression.

Also, you may have the requirement of having configurable expressions. In this case expression strings are a solution.

We have a goal. And as usual, there is a bonus at the end.

The Nutshell

At first, we should prepare the basics. Given a standard Spring Boot project (at time of writing 2.6.4) with the starters spring-boot-starter-web and spring-boot-starter-security start.spring.io, we have to configure Spring Security enabling method security (for enabling @PreAuthorize). The compile dependency Lombok is also included for having short examples.

For the sake of simplicity in this prototype/demo, we set the standard user’s password to a static value. Alternatively, pick the generated one of the console. We also include a static role for demo purpose.

This enables the following controller setup will work as expected:

After starting the application, we can verify this:

Note: A hasRole() would also work if the (default demo) user has roles configured. In this poc-scenario is one authority ROLE_FAKER defined.

Permission, Sir!

In order of supporting the check of permissions on targets (i.e. hasPermission(…)), we need a bean of type PermissionEvaluator. You may skip this chapter if permissions are not in scope.

Technically, if such a bean does not exist, the internal default will rely to a deny-all functionality.

Note: That’s a bad evaluator. Don’t use it in production. Really. :)

Exposed via a bean in the already mentioned SecurityConfig, the extending GlobalMethodSecurityConfiguration will pickup this and register it automatically in a method-security-expression-handler. The latter one will be important in the over-next chapter.

Excursion: Spring Expression Language (SpEL)

First of all, some bits about SpEL. Basically, it is a limited language for building and evaluating (small) expressions and is used in several places within the Spring ecosystem: in filters of Spring Integration, in conditions of @EventListener, or here in authorize checks.

In context of Spring Security, this is basically these lines:

str, expr and even result are straight forward and do not require more description. Of course, the expression str can be a complex one with arguments or conditions. However, building context is challenging.

At first, we can use the standard context StandardEvaluationContext and define a root object. The context is the “holder” of all relevant things within the (following) evaluation of the expression:

  • root object which expresses which functions are available (like isAuthenticated())
  • additional resolver for accessing objects, i.e. beans or other objects
  • internal objects (we see later, that a more specific context contains the current authentication already)
  • additional variables

Evaluator of SpEL for Spring Security

Now, we need the glue code between Spring Security and the SpEL evaluation: the custom AppPermissionExpressionEvaluator.

As noted above already, the context requires a root and an optional beanResolver for resolving the tokens in the expressions correctly: hasRole, hasPermission, isAuthenticated and so on. As we are speaking of expressions labeled on methods, there is the need of supporting the resolve of method’s arguments like #id also. That’s why internally MethodSecurityEvaluationContext as well as MethodSecurityExpressionRoot is being used. This pair interacts via reflection with the method signature and provide deep inspects about the actual method argument values. Thankfully, this is already done when using MethodSecurityExpressionHandler#createEvaluationContext().

Putting things together we get this:

Calling evaluateExpression("isAuthenticated()") creates the parser and evaluates the given expression using the current authentication. The pseudo type and pseudo method is required as we need a pseudo target for the “method invocation”.

Because we are using an expression handler intended for method invocations, we are faking the actual method call (because not having one and null is not allowed) and the variables must be provided explicitly.

This is a working example of usage:

Finally, we register this evaluator in the already mentioned SecurityConfig. The default expression handler is provided by the GlobalMethodSecurityConfiguration parent class.

And this enables us to use the evaluator at any place, here in a controller:

Part one solved: This evaluator enables us to use the same SpEL as in @PreAuthorize.

At this time, we are now able to check security authentication checks wherever we are in the spring context. Unless the authentication is available (like in a service/daemon context), you may creating a complete different context and/or a custom on-demand “pseudo” authentication object.

Re-use existing defined @PreAuthorize definitions on methods

Now, with the created AppPermissionExpressionEvaluator we still have the problem we have to define the expression multiple times. However, using Java’s reflection api and a tooling function we can archive the goal of having exactly one definition.

Note: The following example takes place only in the UserController which is not required. The only requirement is the controller’s method is visible of the initiating class.

With the following tooling extension in AppPermissionExpressionEvaluator

we have the right tooling using the existing expression of a controller method.

Now we are complete!

Bonus: Custom authorization functions like hasRole or hasPermission

As already mentioned, the functions hasRole(), isAuthenticated, hasPermission() and others are actually placed in the root object. This also means we can extend this root for additional custom functions.

Let’s say we want to have the ability of writing such an expression:

A highly individual check and explicitly bullshit. But you got it.

We start of creating a class AppMethodSecurityExpressionRoot which extends the required SecurityExpressionRoot and provide additional api via implementing MethodSecurityExpressionOperations (because we are still using this in the MethodSecurityExpressionHandler).

We cannot just extend MethodSecurityExpressionRoot because it is packaged protected. So we are in need of copying some stuff…

After this, we can define the actual extension functions:

Finally, we need to instruct having a method-security-expression-handler with a custom root.

In order to use the custom root, we have to create custom method-security-expression-handler. Luckily, in this case we can just override the default one.

The configuration class SecurityConfig extends GlobalMethodSecurityConfiguration which already have the extension point createExpressionHandler() for overriding with a custom handler. However, as we need this handler also for the bean AppPermissionExpressionEvaluator, we have to move out the construction. Please also have in mind, that GlobalMethodSecurityConfiguration is doing some post-init instructions onto the (default) expression handler which we may also want to apply.

The following addition into SecurityConfig defines a sub configuration which applies the same initialization as the default one. The important thing however is the bean public MethodSecurityExpressionHandler methodSecurityExpressionHandler() which creates the AppMethodSecurityExpressionHandler.

Most of the following one is also copied stuff..

Note: IntelliJ IDEA provides a deep inspection into Security-SpEL expression. However, custom functions are not supported yet. Meanwhile, use this workaround for a working validation.

References

The complete working demo is available at GitHub. PRs are open in case of errors or additions.

For more information about Spring, Spring Boot, Spring Security you should have a look at their documentation, reference guides and tutorials at spring.io.