Home/Learn/Spring Boot/Method-Level Security

Method-Level Security

Advanced
Security

@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.

Java — @EnableMethodSecurity and @PreAuthorize expressions
@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.

Java — @PostAuthorize, @PostFilter, custom PermissionEvaluator
@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 Aria
1

What annotation enables method-level security in Spring Boot?

EasyTCS
2

What is the difference between @PreAuthorize and @PostAuthorize?

MediumAmazon
3

How do you access method arguments inside a @PreAuthorize SpEL expression?

MediumThoughtWorks
4

What is returnObject in @PostAuthorize and when is it useful?

MediumAtlassian
5

How would you implement a PermissionEvaluator for custom hasPermission() expressions?

HardGoldman Sachs

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.

Loading discussion…