Careeroza
Role Hierarchy
Why a Hierarchy?
An admin should be able to do everything a seller can, and a seller everything a customer can. Without a hierarchy you must write hasAnyRole("CUSTOMER", "SELLER", "ADMIN") everywhere. A RoleHierarchy bean says it once:
ROLE_ADMIN | implies v ROLE_SELLER | implies v ROLE_CUSTOMER
Configuring It
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
@Configuration
public class RoleConfig {
// static: lets method security pick it up early
@Bean
static RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.withDefaultRolePrefix()
.role("ADMIN").implies("SELLER")
.role("SELLER").implies("CUSTOMER")
.build();
}
}Since Spring Security 6.3, a RoleHierarchy bean is used automatically by both authorizeHttpRequests and @PreAuthorize. Now .hasRole("CUSTOMER") also lets sellers and admins in.
How the Expansion Works
Example
Spring expands the user's authorities with everything they imply, then checks the rule. Here is the same idea in plain Java:
import java.util.*;
public class Main {
static final Map<String, String> IMPLIES = Map.of(
"ROLE_ADMIN", "ROLE_SELLER",
"ROLE_SELLER", "ROLE_CUSTOMER");
static Set<String> reachable(Collection<String> granted) {
Set<String> all = new TreeSet<>(granted);
Deque<String> todo = new ArrayDeque<>(granted);
while (!todo.isEmpty()) {
String next = IMPLIES.get(todo.pop());
if (next != null && all.add(next)) todo.push(next);
}
return all;
}
public static void main(String[] args) {
System.out.println("asha -> " + reachable(List.of("ROLE_CUSTOMER")));
System.out.println("ravi -> " + reachable(List.of("ROLE_SELLER", "product:write")));
System.out.println("meera -> " + reachable(List.of("ROLE_ADMIN", "order:refund")));
System.out.println("meera hasRole(CUSTOMER)? " + reachable(List.of("ROLE_ADMIN")).contains("ROLE_CUSTOMER"));
}
}Output
asha -> [ROLE_CUSTOMER] ravi -> [ROLE_CUSTOMER, ROLE_SELLER, product:write] meera -> [ROLE_ADMIN, ROLE_CUSTOMER, ROLE_SELLER, order:refund] meera hasRole(CUSTOMER)? true