-
Notifications
You must be signed in to change notification settings - Fork 1
Spring security, 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. 🤓
In order to secure an endpoint we use @PreAuthorize annotation with hasRole or hasAuthority.
@SecurityRequirement(name = "Bearer Authentication") so the swagger will send the the authentication header.
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);
}Both hasRole and hasAuthority secure our endpoints:
-
hasRoleallows only users with the given role, -
hasAuthorityallows 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.
It is
🔐🔐🔐
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.
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');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')
);Use RoleService and the grantRole function.