Enterprise Java

Fine-Grained Authorization with Spring Security and @PreAuthorize Annotations

Spring Security provides powerful tools to secure your Java applications—but one of its most underrated features is method-level authorization using @PreAuthorize annotations. These annotations allow you to apply security constraints directly on service methods using SpEL (Spring Expression Language).

In this post, we’ll walk through how to implement fine-grained access control using @PreAuthorize and SpEL, with real-world examples.

Enable Method-Level Security

First, make sure you’ve enabled method security in your configuration:

import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@Configuration
@EnableMethodSecurity
public class SecurityConfig {
  // Your security config
}
  

The @EnableMethodSecurity annotation (or @EnableGlobalMethodSecurity in older versions) allows Spring to intercept method calls and apply security rules.

Basic Use of @PreAuthorize

Let’s say you want to restrict access to a method based on a user’s role:

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

This ensures that only users with the ADMIN role can invoke deleteUser().

Using SpEL for Custom Checks

Want to allow access based on dynamic conditions? Here’s where SpEL shines.

Example: Check if user owns the resource

@PreAuthorize("#id == authentication.principal.id")
public User getUserProfile(Long id) {
    return userService.getById(id);
}
  

This ensures users can only access their own profiles. The expression authentication.principal.id pulls the currently authenticated user’s ID.

Example: Role OR Ownership Access

@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public void updateUser(Long id, UserDto userDto) {
    userService.update(id, userDto);
}
  

In this case, an ADMIN can update any user, but regular users can only update their own profile.

Calling Secure Methods Internally? Watch Out!

Spring Security AOP proxies only work on external calls to a secured bean. Internal method calls won’t trigger the security check.

public void processUser(Long id) {
    // This will NOT trigger @PreAuthorize on getUserProfile
    getUserProfile(id);
}
  

Solution: Inject the bean into itself or refactor to avoid internal calls on secured methods.

Custom Permission Evaluator

You can also plug in custom logic using a PermissionEvaluator. Example expression:

@PreAuthorize("hasPermission(#document, 'edit')")
public void editDocument(Document document) {
    documentService.edit(document);
}
  

You’ll need to register a custom PermissionEvaluator in your security config. Docs here.

Testing @PreAuthorize

Use @WithMockUser or @WithUserDetails to simulate roles in unit tests:

@WithMockUser(username = "admin", roles = {"ADMIN"})
@Test
void testDeleteUserAsAdmin() {
    userService.deleteUser(1L);
}
  

Final Thoughts

Method-level security with @PreAuthorize gives you clean, centralized access control without cluttering business logic. It’s ideal for:

  • ✅ Role-based access
  • ✅ Ownership checks
  • ✅ Complex authorization rules with SpEL

For more details, visit the Spring Security documentation.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button