Unleash the Power of @PreAuthorize in Spring Boot with Custom Handlers — A Magical CRUD Journey

Mayank Yaduvanshi
3 min readDec 31, 2023

--

Welcome to the world of Spring Boot, where security is not just a feature, but a way of life. In this blog post, we’ll explore the powerful @PreAuthorize annotation and its dance partner, CustomPermissionEvaluator. Together, they form a dynamic duo that enables fine-grained access control in your Spring Boot applications.

Step 1: Setting the Stage

What is @PreAuthorize?

The @PreAuthorize annotation is like the bouncer at the club entrance—it decides who gets in and who doesn't. It's a part of the Spring Security framework and allows you to express access control rules directly in your code.

Meet CustomPermissionEvaluator

CustomPermissionEvaluator is the brains behind the operation. It implements the PermissionEvaluator interface and customizes how Spring Security checks permissions. It's your go-to guy for deciding whether a user has the green light to perform a specific action.

Step 2: The CustomPermissionEvaluator Unveiled

Let’s break down the key parts of CustomPermissionEvaluator.

@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
// Autowired for supercharged functionality
private RolePermissionService rolePermissionService;

// Implementation of hasPermission for non-identifiable targets
// (e.g., hasPermission('Candidates', 'CREATE'))
@Override
public boolean hasPermission(Authentication auth, Object target, Object permission) {
// Implementation details here...
}

// Implementation of hasPermission for identifiable targets
// (e.g., hasPermission(auth, targetId, targetType, 'CREATE'))
@Override
public boolean hasPermission(Authentication auth, Serializable targetId, String targetType, Object permission) {
// Implementation details here...
}

// Private helper method to check if a user has a specific privilege
private boolean hasPrivilege(Authentication auth, String target, String permission) {
// Implementation details here...
}

// Private helper method to extract roles from Authentication
private List<String> extractRoles(Authentication auth) {
// Implementation details here...
}
}

Deep dive — CustomPermissionEvaluator

Providing this implementation of PermissionEvaluator that can fit most of the use cases.

@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {

@Autowired
private RolePermissionService rolePermissionService;

@Override
public boolean hasPermission(Authentication auth, Object target, Object permission) {
if ((auth == null) || (target == null) || !(permission instanceof String)) {
return false;
}

if (!(target instanceof String)) {
return false;
}

return hasPrivilege(auth, (String) target, permission.toString().toUpperCase());
}

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

if ((auth == null) || (targetType == null) || (targetId == null) || !(permission instanceof String)) {
return false;
}

var privilege = hasPrivilege(auth, targetType.toUpperCase(), permission.toString().toUpperCase());

return privilege;
}

private boolean hasPrivilege(Authentication auth, String target, String permission) {

var rolePermissions = rolePermissionService.getPermissions(extractRoles(auth), target);

// all roles don't have any privileges on this target
if (rolePermissions.isEmpty()) {
return false;
}

for (RolePermission rolePermission : rolePermissions) {

var isGranted = switch (permission) {
case "READ":
yield rolePermission.getRead();
case "CREATE":
yield rolePermission.getCreate();
case "UPDATE":
yield rolePermission.getUpdate();
case "DELETE":
yield rolePermission.getDelete();
default:
yield false;
};

if (Boolean.TRUE.equals(isGranted)) {
return true;
}
}

return false;
}


private List<String> extractRoles(Authentication auth) {
return auth.getAuthorities().stream().map(a -> a.getAuthority()).toList();
}
}

Step 3: Meet the Sidekick — RolePermissionService

RolePermissionService is the trusty sidekick, assisting CustomPermissionEvaluator in fetching role permissions from the repository.

@Service
@AllArgsConstructor
public class RolePermissionService {
private final RolePermissionRepository repository;

// Fetch permissions based on roles and target
public List<RolePermission> getPermissions(List<String> roles, String name) {
// Implementation details here...
}
}

Step 4: Enter the Hero — ContactsController

Now, let’s see ContactsController in action, where @PreAuthorize steals the spotlight.

@RestController
@CrossOrigin
@AllArgsConstructor
public class ContactsController extends BaseController {

// Autowiring services for ultimate power
private final ContactService contactService;
private final ModelMapper modelMapper;

// The magic line that ties it all together
@PreAuthorize("hasPermission('Candidates', 'CREATE')")
@PostMapping(value = "/contacts")
public ResponseEntity<ContactDto> createContacts(
@RequestBody @Valid CreateContactRequest request
) {
// Implementation details here...
}
}

Step 5: The Finale

And that’s it! With a sprinkle of @PreAuthorize and the wisdom of CustomPermissionEvaluator, you've elevated your Spring Boot application's security game.

Now, if you’re hungry for more knowledge:

  • Dive deeper into Spring Security documentation.
  • Explore the wonders of ModelMapper.
  • Polish your Spring Boot skills with Baeldung.

Remember, security is not just a feature; it’s your application’s bodyguard. So, go ahead, lock those doors, and let only the authorized guests in! 🚀🔐

--

--

Mayank Yaduvanshi

Full Stack Software Developer, Experienced in Angular, Flutter, Spring boot, NodeJs, MySQL, MongoDB, AWS, Google Cloud, Docker, Git, JIRA.