Unleash the Power of @PreAuthorize in Spring Boot with Custom Handlers — A Magical CRUD Journey
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! 🚀🔐