Spring Security and Roles Authorization

Authentication or authorization? What are the differences? Which one to choose? And how to implement them in a Spring Boot application?

In this article I will explain the difference between Authentication and Authorization. I will also explain how to implement the Authorization pattern with Spring Security. For that, I will use the @PreAuthorize annotation.

Content

  • Authentication vs Authorization
  • Spring Security & Method Security
  • PreAuthorize
  • GrantedAuthority
  • PostAuthorize

More details can be found in this video.

If you’re looking for the code of the article, it’s available in this repository.

Authentication vs Authorization

I’ve already talked about authentication in some other videos. I’ve created some articles where i’ve implemented multiple ways a request can be authenticated: JWT, cookie or session. The authentication is the action that validates that a user who makes a request is allowed to talk with my application.

Authentication

But not all the parts of my application are accessible to anybody authenticated. I may have different people who are trying to access my application: admins, regular users, editors, B2B systems and more. Not everybody has the same privileges. The regular users must not be able to change some configuration parameters. Or the editors may not be authorized to update the prices of external services.

Authentication fails when different parts requires different privileges

Here comes the authorization. Even if a user is authenticated, it may not be able to access everywhere. It’s like entering a secure building. I may be able to enter with an authorization card, but only some floors are accessible with it. With Spring Security, I can define some access roles and assign those access roles to the users. They will define the actions each user is authorized to perform.

Users must have assigned roles to access their respective platforms

Spring Security & Method Security

I already have configured the application with the annotation @EnabledWebSecurity, but with authorities this i need a more fine-tuned security. Let’s adapt this.

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

The annotation @EnableGlobalMethodSecurity says that the security will be defined at the method level. I’ve indicated the prePostEnabled to true because this is the solution that I will use. I have two other possibilities: securedEnabled and jsr250Enabled. securityEnabled allows me to use the annotation @Secured but this comes from older versions of Spring Security. jsr250Enabled allows me to use the annotations @RolesAllowed which belongs to javax. The three solutions work very similar, but I’ve chosen prePostEnabled which belongs to newer versions of Spring Security, because I can use some SpEL expressions.

PreAuthorize

    @PreAuthorize("permitAll")
    @PostMapping("/signIn")
    public ResponseEntity<UserDto> signIn(@AuthenticationPrincipal UserDto user) {
        ...
    }

    @PreAuthorize("isAuthenticated()")
    @PostMapping("/signOut")
    public ResponseEntity<Void> signOut(@AuthenticationPrincipal UserDto user) {
        ...
    }

Within the @PreAuthorize annotation, I can use SpEL expressions and some built-in expressions too. The permitAll means that all the requests are allowed to this endpoint, even without any authenticated user. Using isAuthenticated requires at least that the user is authenticated to request this endpoint. I’ve used the annotation in the definition of endpoints, but I could have used them in the definition of the methods in the service layer.

    @PreAuthorize("hasRole('ROLE_VIEWER')")
    @GetMapping("/images")
    public ResponseEntity<List<ImageDto>> getCommunityImages(
        ...
    }

    @PreAuthorize("hasAnyRole('ROLE_EDITOR', 'ROLE_VIEWER')")
    @PostMapping("/images")
    public ResponseEntity<ImageDto> postImage(@RequestParam MultipartFile file,
                                              @RequestParam(value = "title") String title) {
        ...
    }

This time, I use the built-in expression hasRole with the name of the role. This means that to request those endpoints, the user must not only be authenticated but it must also have the indicated role to be authorized to request this endpoint. For the last endpoint, I’ve used a different built-in expression which accepts multiple roles. But I could have used an SpEL expression.

    @PreAuthorize("hasRole('ROLE_EDITOR') or hasRole('ROLE_VIEWER')")

GrantedAuthority

Another point, all the roles must start by the ROLE_ prefix. Let’s now enrich the user details entity with the roles, with the authorities.

    public Authentication validateCredentials(CredentialsDto credentialsDto) {
        UserDto user = authenticationService.authenticate(credentialsDto);
        return new UsernamePasswordAuthenticationToken(
                user,
                null,
                user.getAuthorities().stream().map(authority -> (GrantedAuthority) () -> authority).collect(Collectors.toList()));
    }

The UserDTO object contains the list of the roles associated with the given user. I must get them and map them into a GrantedAuthority object. From now, the user details in the Spring Security session will contain the authorities for the current session. So, for each request, for each method annotated with annotation @PreAuthorize, Spring Security will compare the indicated roles with the roles of the user object.

PostAuthorize

Until now I’ve used the @PreAuthorize annotation, but I also have the @PostAuthorize annotation.

    @PostAuthorize("hasRole('ROLE_VIEWER')")
    @GetMapping("/images")
    public ResponseEntity<List<ImageDto>> getCommunityImages(
       ...
    }

This way, the check done by Spring Security is done after the method execution. This has absolutely no difference with the @PreAuthorize annotation, which checks before entering in the method. The database transactions are managed the same way, when exiting the method the database transactions are committed. Nevertheless, if I have some HTTP communication with external services, they will be done even if the user isn’t authorized. So while choosing the @PostAuthorize annotation? With the @PostAuthorize annotation I can make some more checks with the returned value.

    @PostAuthorize("hasRole('ROLE_VIEWER') and returnObject.body.size() < 10")
    @GetMapping("/images")
    public ResponseEntity<List<ImageDto>> getCommunityImages(
       ...
    }

I check if the body of the returned object has a size lower than 10. The returned object is a ResponseEntity, and the body of the ResponseEntity is the list of images. I check if the application returns less than 10 images. I can make some more checks with the returned value. I can use the SpEL expressions as I want.

Conclusion

  • I’ve used the annotation @EnableGlobalMethodSecurity.
  • I’ve enabled the prePostEnabled solution.
  • I’ve added the @PreAuthorize annotation at the methods I want to protect.
  • I’ve used some built-in methods as permitAll or isAuthenticated.
  • I’ve used the hasRole method with the accepted role for the method.
  • I’ve enriched the user details with GrantedAuthority of the Spring Security session with authorities of the authenticated user.
  • I’ve used the @PostAuthorize annotation when I want to also perform some checks against the result of the method.

References

Repository

My New ebook, How to Master Git With 20 Commands, is available now.

Leave a comment

A WordPress.com Website.