Authorization determines what an authenticated user is allowed to do. Spring Security supports URL-based, method-level, and domain-object security.

URL-Based Authorization

  http.authorizeHttpRequests(auth -> auth
    .requestMatchers("/admin/**").hasRole("ADMIN")
    .requestMatchers("/api/public/**").permitAll()
    .requestMatchers(HttpMethod.GET, "/api/products/**").hasAnyRole("USER", "ADMIN")
    .requestMatchers(HttpMethod.POST, "/api/products/**").hasRole("ADMIN")
    .anyRequest().authenticated()
);
  

Role names are prefixed with ROLE_ internally: hasRole("ADMIN") checks ROLE_ADMIN.

Method Security

  @EnableMethodSecurity
@Configuration
public class MethodSecurityConfig { }
  
  @Service
public class OrderService {
    @PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
    public Order getOrder(Long orderId, Long userId) {
        return orderRepository.findById(orderId).orElseThrow();
    }

    @PreAuthorize("hasAuthority('order:delete')")
    public void deleteOrder(Long orderId) { orderRepository.deleteById(orderId); }

    @PostAuthorize("returnObject.owner == authentication.name")
    public Document getDocument(Long id) { return documentRepository.findById(id).orElseThrow(); }

    @Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
    public void approveExpense(Long id) { /* ... */ }
}
  

Custom Permission Evaluator

  @Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
    @Override
    public boolean hasPermission(Authentication auth, Object target, Object permission) {
        if (auth == null || target == null) return false;
        Order order = (Order) target;
        return order.getUserId().equals(getCurrentUserId(auth))
            || auth.getAuthorities().stream()
                .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
    }

    @Override
    public boolean hasPermission(Authentication auth, Serializable targetId,
                                 String targetType, Object permission) {
        return false;
    }
}
  

Access Decision Managers

Voter Purpose
RoleVoter Checks role-based access
AuthenticatedVoter Checks IS_AUTHENTICATED_*
AffirmativeBased Grants if any voter approves (default)
UnanimousBased Grants only if all voters approve

Domain Object Security (ACL)

For row-level security:

  @PreAuthorize("hasPermission(#id, 'com.example.Order', 'read')")
public Order findById(Long id) { return orderRepository.findById(id).orElseThrow(); }
  

Requires Spring Security ACL module and database tables for ACL entries.

SpEL Expressions

Common expressions in @PreAuthorize:

  @PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAuthority('user:write')")
@PreAuthorize("authentication.name == #username")
@PreAuthorize("@authService.canAccess(#orderId, authentication)")
@PreAuthorize("hasRole('ADMIN') or #entity.owner == authentication.name")
  

Best Practices

  • Apply coarse-grained rules at URL level; fine-grained at method level
  • Use @PreAuthorize over @Secured for SpEL flexibility
  • Prefer permission strings (order:write) over roles for fine-grained control
  • Test authorization rules with @WithMockUser and @WithUserDetails
  • Never rely solely on UI hiding — always enforce on the server