自定义Spring PermissionEvaluator:多权限场景下避免hasPermission重复调用咨询
Hey there! Let's work through how to avoid multiple calls to your custom PermissionEvaluator when you need to check multiple permissions in a single @PreAuthorize annotation. Here are three practical, battle-tested approaches:
1. Create a Custom SpEL Function for Batch Permission Checks
Instead of calling hasPermission multiple times, build a dedicated service method that handles all your permission checks in one go. This keeps your annotation clean and minimizes calls to your evaluator.
First, create a service class to wrap the batch logic:
@Service("customPermissionService") public class CustomPermissionService { @Autowired private PermissionEvaluator permissionEvaluator; // Method to check multiple target-permission pairs public boolean hasAllPermissions(Authentication auth, Object[] targets, String[] permissions) { if (targets.length != permissions.length) { throw new IllegalArgumentException("Targets and permissions arrays must be the same length"); } // Run all checks in a single loop for (int i = 0; i < targets.length; i++) { if (!permissionEvaluator.hasPermission(auth, targets[i], permissions[i])) { return false; } } return true; } }
Then use it in your @PreAuthorize annotation like this:
@PreAuthorize("@customPermissionService.hasAllPermissions(authentication, [#foo, #foo2], ['test1', 'test2'])") public void yourProtectedMethod(Foo foo, Foo2 foo2) { // Your business logic here }
This way, you only trigger one call to your evaluator's core logic (via the batch loop) instead of two separate invocations.
2. Extend Your PermissionEvaluator to Support Batch Checks
If you want to keep all permission logic within your existing PermissionEvaluator, add a batch method directly to it, then call that method via SpEL.
Update your custom evaluator:
@Component("customPermissionEvaluator") public class CustomPermissionEvaluator implements PermissionEvaluator { @Override public boolean hasPermission(Authentication auth, Object target, Object permission) { // Your existing single permission check logic return checkSinglePermission(auth, target, permission); } // New batch method public boolean hasAllPermissions(Authentication auth, Object[] targets, String[] permissions) { for (int i = 0; i < targets.length; i++) { if (!checkSinglePermission(auth, targets[i], permissions[i])) { return false; } } return true; } // Extract core logic to reuse across single and batch checks private boolean checkSinglePermission(Authentication auth, Object target, Object permission) { // Your actual permission validation logic goes here // Example: return auth.getAuthorities().contains(new SimpleGrantedAuthority(permission.toString())); } @Override public boolean hasPermission(Authentication auth, Serializable targetId, String targetType, Object permission) { // Handle ID-based checks if needed return false; } }
Then reference it directly in your annotation:
@PreAuthorize("@customPermissionEvaluator.hasAllPermissions(authentication, [#foo, #foo2], ['test1', 'test2'])") public void yourProtectedMethod(Foo foo, Foo2 foo2) { // ... }
This approach keeps all permission-related code in one place, which is great for maintainability.
3. Encapsulate Checks in Your Business Layer
If you prefer to keep security logic closer to your business code, you can handle the batch check before executing your business logic (either in the service or controller).
For example, in your service class:
@Service public class YourBusinessService { @Autowired private PermissionEvaluator permissionEvaluator; public void performProtectedAction(Foo foo, Foo2 foo2) { // Get current authentication from security context Authentication auth = SecurityContextHolder.getContext().getAuthentication(); // Run batch permission check first Map<Object, String> permissionPairs = Map.of(foo, "test1", foo2, "test2"); boolean hasAllPermissions = permissionPairs.entrySet().stream() .allMatch(entry -> permissionEvaluator.hasPermission(auth, entry.getKey(), entry.getValue())); if (!hasAllPermissions) { throw new AccessDeniedException("Insufficient permissions"); } // Your business logic here } }
Then you can either remove the @PreAuthorize annotation (if you handle exceptions properly) or keep a simple check if needed. This is useful when permission checks are tightly coupled to business context.
Each approach has its own merits: the first is flexible for cross-cutting checks, the second keeps evaluator logic unified, and the third integrates security with business logic. Pick the one that fits your project's architecture best!
内容的提问来源于stack exchange,提问作者Gal Sosin

