For a security framework to be of value, it needs flexibility. Security rules are never confined to simple use cases. We have all dealt with customers needing very complex settings for certain operations. Spring Security makes this possible through its special dialect of SpEL or Spring Expression Language.
To get a taste of it, let's augment the images microservice's ImageService.delete() method with an authorization rule:
@PreAuthorize("hasRole('ADMIN') or " + "@imageRepository.findByName(#filename).owner " + "== authentication.name") public Mono<Void> deleteImage(String filename) { ... rest of the method unchanged ... }
This preceding code for deleting images is only different in the new annotation in the following manner:
- The method is flagged with a @PreAuthorize annotation, indicating that the SpEL expression must evaluate to true in order for the method to get called
- hasRole('ADMIN') indicates that a user with ROLE_ADMIN is allowed access
- or @imageRepository.findByName(#filename).owner == authentication.name") indicates that access is also granted if the user's name matches the image's owner property
This authorization rule is just one example of the types of rules we can write. The following table lists the prebuilt rules provided by Spring Security:
SpEL function |
Description |
hasAuthority('ROLE_USER') |
Access is granted if user has ROLE_USER |
hasAnyAuthority('ROLE_USER', 'ROLE_ADMIN') |
Access is granted if user has any of the listed authorities |
hasRole('USER') |
Shorthand for hasAuthority('ROLE_USER') |
hasAnyRole('USER', 'ADMIN') |
Shorthand for hasAnyAuthority('ROLE_USER', 'ROLE_ADMIN') |
principal |
Direct access to the Principal object representing the user |
authentication |
Direct access to the Authentication object obtained from the security context |
permitAll |
Evaluates to true |
denyAll |
Evaluates to false |
isAnonymous() |
Returns true if user is an anonymous user |
isRememberMe() |
Returns true if user is a remember-me user |
isAuthenticated() |
Returns true if user is not anonymous |
isFullyAuthenticated() |
Returns true if user is neither anonymous nor a remember-me user |
It's possible to combine these SpEL functions with and and or.
As we saw demonstrated earlier, we can also write security checks like this:
@PreAuthorize("#contact.name == authentication.name") public void doSomething(Contact contact);
This preceding security check will grab the method's contact argument and compare its name field against the current authentication object's name field, looking for a match.
By the way, these types of parameter-specific rules are great when we want to restrict operations to the owner of the record, a common use case. In essence, if you are logged in and operating on your data, then you can do something.
In addition to all these functions and comparators, we can also invoke beans (another thing shown earlier). Look at the following code, for example:
@PreAuthorize("@imageRepository.findByName(#filename).owner ==
authentication.name")
This last security check will invoke the bean named imageRepository and use its findByName function to look up an image's owner and then compare it against the current authentication object.