Skip to content

Spring security, roles and permissions

Marcin Słupczyński edited this page May 24, 2024 · 17 revisions

Security FAQ

What is a difference between roles and permissions?

In our understanding, a permission is user's property that allows the user to take certain action(s). For example, ADD_PRODUCT is a permission that allows the user to add new products by accessing POST endpoint.

A role on the other hand is a set of permissions, for example if user has a role MODERATOR which contains ADD_PRODUCT and REMOVE_OPINION, it means that they can perform actions within the scope of these permissions in our case -- add new products and remove opinions.

In our system we assign permissions to user by assigning a role(s). We call it role granting. There is no mechanism that supports adding a permission directly to the given user.

User may have more than one role assigned, e.g. moderator has USER and MODERATOR roles, it means that the resultant permissions set is defined by the union of permissions in scope of these roles.

🤓 It is important to note that these definitions are valid only within our application and our code. Spring security has different definition of a permission. 🤓

How to secure an endpoint?

In order to secure an endpoint we use @PreAuthorize annotation with hasRole or hasAuthority.

‼️ Remember to add @SecurityRequirement(name = "Bearer Authentication") so the swagger will send the the authentication header. ‼️ Without the authorization header, user jwt token will not be added to the request which means that no authorization will take place and the response from the secured endpoint will be always forbidden.

Here is an example:

    @Operation(summary = "Add a product to the database",
        description = "Add a product to the database",
        security = {@SecurityRequirement(name = "Bearer Authentication")})
    @PreAuthorize("hasAuthority('ADD_PRODUCT')")
    @PostMapping
    @ResponseBody
    public ResponseEntity<Product> addProduct(@RequestBody Product product) {
        Product savedProduct = productService.addProduct(product);
        return new ResponseEntity<>(savedProduct, HttpStatus.CREATED);
    }

What is a difference between hasRole and hasAuthority? Which one should be used?

Both hasRole and hasAuthority secure our endpoints:

  • hasRole allows only users with the given role,
  • hasAuthority allows only users with the given permission.

🙀 In spring security hasRole('USER') is equivalent of hasAuthority('ROLE_USER'). 🙀

❗️ We should use hasAuthority with the proper permission instead of hasRole ❗️, since accessing the given endpoint is an action itself, so by our definitions it should be assigned to a permission not a role. The hasRole is added mainly for convenience when testing on production.

How to access secured endpoint on frontend or in swagger?

It is ‼️EXTREMELY‼️ important to note that if our endpoint is secured, we cannot access it with request that doesn't contain Authorization header -- for swagger see How to secure an endpoint? section.

🔐🔐🔐

You can use mocked user account with USER role:

  • username: user,
  • password: user,

or with mocked moderator with USER and MODERATOR role:

  • username: mod,
  • password: mod.

🔐🔐🔐

Both accounts are added to our liquibase migration script.

How to add new roles and permissions?

To add new roles and permissions we need to use liquibase💧 migration script. Don't use the explicit IDs, since it will collide with JPA, here is an example

INSERT INTO roles (name) VALUES ('USER'), ('MODERATOR');

INSERT INTO permissions (name, description)
VALUES ('REMOVE_USER_OPINION', 'Allows removing user opinions'),
       ('REMOVE_PRODUCT', 'Allows removing products'),
       ('ADD_PRODUCT', 'Allows adding new products'),
       ('REPORT_USER_OPINION', 'Allows reporting user opinions');

INSERT INTO roles_permissions
    (role_id, permission_id)
SELECT r.id, p.id
FROM roles r
    CROSS JOIN permissions p
WHERE (r.name = 'MODERATOR' AND p.name IN ('REMOVE_USER_OPINION', 'REMOVE_PRODUCT', 'ADD_PRODUCT'))
   OR (r.name = 'USER' AND p.name = 'REPORT_USER_OPINION');

How to grant role using liquibase migration script?

Use liquibase💧 migration script. Don't use explicit indices, since it conflicts with JPA. Here is an example

INSERT INTO auth_infos_roles
    (auth_info_id, role_id)
VALUES (
    (SELECT id FROM auth_info WHERE username = 'mod'),
    (SELECT id FROM roles WHERE name = 'MODERATOR')
);

How to grant role in our application?

Use RoleService and the grantRole function.