Role-Based Access Control (RBAC)
RBAC restricts access based on user roles — admins can do everything, regular users have limited access.
Enable Method Security
@Configuration
@EnableMethodSecurity
public class SecurityConfig { }
@PreAuthorize — SpEL Expressions
@RestController
@RequestMapping("/api/books")
public class BookController {
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
bookService.delete(id);
return ResponseEntity.noContent().build();
}
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
@PostMapping
public ResponseEntity<BookResponse> create(@Valid @RequestBody CreateBookRequest request) {
return ResponseEntity.created(URI.create("/api/books/1"))
.body(bookService.create(request));
}
@PreAuthorize("hasAuthority('BOOK_READ')")
@GetMapping("/{id}")
public BookResponse getById(@PathVariable Long id) {
return bookService.findById(id);
}
// Access current user
@PreAuthorize("#username == authentication.principal.username or hasRole('ADMIN')")
@GetMapping("/user/{username}")
public List<BookResponse> getByUser(@PathVariable String username) {
return bookService.findByUser(username);
}
}
@Secured and @RolesAllowed
@Secured("ROLE_ADMIN")
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) { }
@RolesAllowed({"ADMIN", "MANAGER"})
@PostMapping
public BookResponse create(@RequestBody CreateBookRequest request) { }
| Annotation | SpEL Support | Prefix Required |
|---|---|---|
@PreAuthorize |
✅ | No (hasRole('ADMIN')) |
@Secured |
❌ | Yes (ROLE_ADMIN) |
@RolesAllowed |
❌ | No |
Use
@PreAuthorize— most flexible.
Roles vs Authorities
// Roles = coarse-grained
UserDetails admin = User.builder()
.username("admin")
.password(encoder.encode("pass"))
.roles("ADMIN") // stored as ROLE_ADMIN
.build();
// Authorities = fine-grained permissions
UserDetails editor = User.builder()
.username("editor")
.password(encoder.encode("pass"))
.authorities("BOOK_READ", "BOOK_WRITE", "BOOK_DELETE")
.build();
@PreAuthorize("hasRole('ADMIN')") // checks ROLE_ADMIN
@PreAuthorize("hasAuthority('BOOK_WRITE')") // checks BOOK_WRITE
Service-Level Security
@Service
public class BookService {
@PreAuthorize("hasAuthority('BOOK_WRITE')")
public BookResponse create(CreateBookRequest request) { ... }
@PreAuthorize("hasAuthority('BOOK_DELETE') or hasRole('ADMIN')")
public void delete(Long id) { ... }
@PostAuthorize("returnObject.author() == authentication.principal.username or hasRole('ADMIN')")
public BookResponse findById(Long id) { ... }
}
@PostAuthorize checks after the method executes — useful for filtering based on the return value.
Accessing Current User
@GetMapping("/me")
public UserResponse getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
return userService.findByUsername(userDetails.getUsername());
}
// Or via SecurityContextHolder
public String getCurrentUsername() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return auth.getName();
}
Role Hierarchy
@Bean
public RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.fromHierarchy("""
ROLE_ADMIN > ROLE_MANAGER
ROLE_MANAGER > ROLE_USER
ROLE_USER > ROLE_GUEST
""");
}
Now ADMIN automatically has all MANAGER, USER, and GUEST permissions.
Key Takeaways
@EnableMethodSecurity+@PreAuthorize= method-level security.- Roles are coarse-grained (
ADMIN,USER); authorities are fine-grained (BOOK_WRITE). @PreAuthorizesupports SpEL — the most flexible option.- Use
@AuthenticationPrincipalto access the current user. - Role hierarchy simplifies permissions — ADMIN inherits all lower roles.
What’s Next?
Next: Spring Boot Actuator — health checks, metrics, and monitoring.