Security isn’t just about authentication — it’s about who can access what and when. That’s where Role-Based Access Control (RBAC) comes in. By mapping users to roles and restricting access to resources based on those roles, you can design flexible and scalable authorization systems.

In this guide, we’ll explore how to implement advanced RBAC in Spring Boot using annotations, custom permission evaluators, and dynamic role hierarchies. You’ll learn how to enforce policies at both API and method levels, and integrate seamlessly with token-based authentication systems like JWT.


What Is Role-Based Access Control?

RBAC is an access control strategy that assigns permissions to roles, and roles to users.

Example:

  • User: alice@example.com
  • Roles: ADMIN, MANAGER
  • Permissions: read:users, create:reports, delete:accounts

RBAC makes it easier to:

  • Apply consistent policies
  • Audit access levels
  • Reduce complexity in authorization logic

Basic Spring Security Role Setup

First, define authorities in your authentication system (database, LDAP, etc.) or use token claims (e.g., in JWT).

Spring Security uses roles as authorities prefixed with ROLE_ by convention.

http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/manager/**").hasAnyRole("MANAGER", "ADMIN")
.anyRequest().authenticated()
);

Use @EnableMethodSecurity to enforce fine-grained access at the method level.


Method-Level Access Control

Secure services and controller methods using annotations:

@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}

Use @PreAuthorize, @PostAuthorize, @Secured, or @RolesAllowed based on your use case.

For multiple roles:

@PreAuthorize("hasAnyRole('MANAGER', 'ADMIN')")

Working with JWT and Authorities

When using JWT tokens, ensure they include roles/authorities as claims.

Example claim:

{
"sub": "jane",
"roles": ["USER", "ADMIN"]
}

Create a custom converter to extract roles:

public class JwtRoleConverter implements Converter<Jwt, AbstractAuthenticationToken> {
@Override
public AbstractAuthenticationToken convert(Jwt jwt) {
Collection<GrantedAuthority> authorities = jwt.getClaimAsStringList("roles").stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
return new JwtAuthenticationToken(jwt, authorities);
}
}

Register this in your SecurityFilterChain.


Dynamic Role Hierarchies

Sometimes, roles inherit other roles. For example:

  • ADMIN ⟶ MANAGER ⟶ USER

Define role hierarchy:

@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_MANAGER \n ROLE_MANAGER > ROLE_USER");
return hierarchy;
}

Spring will automatically resolve nested role permissions.


Fine-Grained Permission Checks

For more dynamic logic, use @PreAuthorize with SpEL expressions:

@PreAuthorize("#user.id == authentication.principal.id or hasRole('ADMIN')")
public User getUser(User user) {
return user;
}

Or implement a custom PermissionEvaluator:

@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication auth, Object target, Object permission) {
// implement logic to check if user has 'permission' on 'target'
return true;
}
}

Use with:

@PreAuthorize("hasPermission(#account, 'read')")

Protecting REST APIs with Annotations

Secure your REST endpoints declaratively:

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PreAuthorize("hasRole('ADMIN')")
    @DeleteMapping("/{id}")
    public ResponseEntity<?> deleteUser(@PathVariable Long id) {
        userService.deleteById(id);
        return ResponseEntity.ok().build();
    }

    @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
    @GetMapping("/me")
    public ResponseEntity<UserDTO> getProfile() {
        return ResponseEntity.ok(userService.getCurrentUser());
    }
}

Role vs Permission: Which Should You Use?

Feature Roles Permissions
Granularity Coarse (e.g., ADMIN) Fine-grained (e.g., read:invoice)
Flexibility Low High
Best For Simple apps, team access Enterprise apps, data-level security

Combine both when possible: assign permissions to roles, then check for permissions in code.


Best Practices

  • Use @PreAuthorize for readable security logic
  • Keep roles and permissions in a centralized store (DB, Identity Provider)
  • Include roles in JWT or OAuth2 token claims
  • Use role hierarchy to reduce duplication
  • Audit access violations and permission checks

Conclusion

Role-Based Access Control (RBAC) is essential for building secure and maintainable APIs. With Spring Boot, you can combine annotations, JWT-based claims, and custom permission evaluators to enforce fine-grained, scalable authorization logic.

Implementing RBAC right improves your application’s security posture and makes your access rules more transparent and testable.