Method-Level Security
Advanced@PreAuthorize with SpEL expressions enforces access control at the method level — inside the service, after the URL check. Use it for ownership checks, fine-grained permissions, and multi-tenant isolation.
Overview
Method security is enabled with @EnableMethodSecurity. @PreAuthorize runs before the method — if the expression returns false, AccessDeniedException is thrown. @PostAuthorize runs after and can inspect the return value. @PreFilter and @PostFilter filter collections. SpEL expressions have access to the SecurityContext via the authentication variable and to method arguments. Method security complements URL authorization — URL rules protect broad access, method security enforces fine-grained invariants.
@PreAuthorize — Pre-Invocation Access Control
Enable method security with @EnableMethodSecurity on a @Configuration class. @PreAuthorize evaluates a SpEL expression before the method runs. The authentication variable provides the current principal; #paramName accesses method arguments.
@Configuration
@EnableMethodSecurity // enables @PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter
public class SecurityConfig { ... }
@Service
public class CourseService {
// Only ADMIN can publish courses
@PreAuthorize("hasRole('ADMIN')")
public void publishCourse(String courseId) { ... }
// Only ADMIN or INSTRUCTOR roles
@PreAuthorize("hasAnyRole('ADMIN', 'INSTRUCTOR')")
public CourseDto createCourse(CreateCourseRequest req) { ... }
// User can only access their own enrollment
// #userId method arg must match the principal (or be admin)
@PreAuthorize("#userId == authentication.principal or hasRole('ADMIN')")
public List<EnrollmentDto> getEnrollments(String userId) { ... }
// Check a property on the principal object
// (when principal is a UserPrincipal with .isPro() method)
@PreAuthorize("authentication.principal.pro or hasRole('ADMIN')")
public List<CertificationDto> getPremiumCertifications() { ... }
// Free method — no restriction
public List<CourseDto> getPublishedCourses() { ... }
}@PostAuthorize and @PostFilter
@PostAuthorize runs after the method and can use the returnObject variable to inspect the result — useful for enforcing that a user can only receive their own data. @PostFilter filters a returned collection.
@Service
public class SubmissionService {
// Verify the returned object belongs to the caller
@PostAuthorize("returnObject.userId == authentication.principal")
public SubmissionDto getSubmission(String submissionId) {
return submissionRepository.findById(submissionId)
.map(submissionMapper::toDto)
.orElseThrow();
// If submission.userId != principal → AccessDeniedException after return
}
// Filter the collection — remove items not owned by the caller
@PostFilter("filterObject.userId == authentication.principal or hasRole('ADMIN')")
public List<SubmissionDto> getRecentSubmissions() {
return submissionRepository.findTop50ByOrderByCreatedAtDesc()
.stream().map(submissionMapper::toDto).toList();
// Spring removes items where filterObject.userId != principal
}
}
// Custom permission evaluator for complex expressions
@Component
public class CoursePermissionEvaluator implements PermissionEvaluator {
private final EnrollmentRepository enrollmentRepository;
@Override
public boolean hasPermission(Authentication auth, Object targetId,
String permission) {
String userId = (String) auth.getPrincipal();
if ("ACCESS_COURSE".equals(permission)) {
return enrollmentRepository.existsByUserIdAndCourseId(
userId, (String) targetId);
}
return false;
}
}
// Usage with custom evaluator
@PreAuthorize("hasPermission(#courseId, 'ACCESS_COURSE')")
public LessonDto getLesson(String courseId, String lessonId) { ... }Key Points to Remember
- 1@EnableMethodSecurity must be on a @Configuration class to activate @PreAuthorize and others.
- 2@PreAuthorize evaluates before the method — AccessDeniedException is thrown if the expression is false.
- 3authentication.principal in SpEL is whatever your JWT filter set as the principal (String userId or UserPrincipal).
- 4#paramName in SpEL refers to a method argument by name — requires -parameters compiler flag or parameter names preserved.
- 5@PostAuthorize is expensive for large datasets — use @PostFilter only on small collections.
- 6Implement PermissionEvaluator for complex permission logic that cannot be expressed in inline SpEL.
Interview Questions
Sign in to ask AriaWhat annotation enables method-level security in Spring Boot?
What is the difference between @PreAuthorize and @PostAuthorize?
How do you access method arguments inside a @PreAuthorize SpEL expression?
What is returnObject in @PostAuthorize and when is it useful?
How would you implement a PermissionEvaluator for custom hasPermission() expressions?
Ask Aria about Method-Level Security
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.