Spring AOP, or Aspect-Oriented Programming, is a framework within the Spring Framework that enables modularization of cross-cutting concerns in Java applications. It allows developers to separate concerns like logging, security, and transaction management from the core business logic. AOP achieves this by introducing aspects, which are modules encapsulating cross-cutting concerns, and weaving them into the application at specified points. This helps in achieving better code modularity, reusability, and maintainability by reducing code duplication and promoting a cleaner architecture.
In this article we will show you how to utilize AOP for authorization.
This is a database table which contains products:
ID | Title | Quantity | TenantID |
1 | Product 1 | 40 | 2 |
2 | Product 2 | 50 | 2 |
3 | Product 3 | 20 | 10 |
ProductService class contains a method for product deletion:
1 2 3 |
public void delete(Integer id) { productRepository.deleteById(id); } |
This service method is exposed trough REST API call which accepts ID parameter and calls the method which deletes product.
JWT of authenticated user contains its own tenant ID which is 2 in current example. So user should only be allowed to delete product 1 and product 2. Deletion attempt of product 3 should generate 403 forbidden.
We will create an annotation which should be placed before each protected method.
1 2 3 4 |
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ProductPermission { } |
Next class we need is an aspect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@Aspect @Component public class ProductPermissionAspect { @Autowired private ProductRepository productRepository; @Autowired private AuthService authService; @Before("@annotation(com.your.package.aspect.annotation.ProductPermission)") public void check(JoinPoint joinPoint) { java.lang.Object〚〛 args = joinPoint.getArgs(); Integer id = (Integer)args〚0〛; if(!isOwner(id, authService.getTenantId())) { throw new CustomException("Permission denied", HttpStatus.FORBIDDEN); } } private boolean isOwner(Integer id, Integer tenantId) { Product product = productRepository.findById(id) .orElseThrow(() -> new CustomException("This product doesn't exists!", HttpStatus.NOT_FOUND )); return product.getTenantId().intValue() == tenantId.intValue(); } } |
Finally we are going to annotate a protected method from service
1 2 3 4 |
@ProductPermission public void delete(Integer id) { productRepository.deleteById(id); } |
Order of actions
- @ProductPermission annotation triggers ProductPermissionAspect.check method execution before each call of annotated method
- ProductPermissionAspect grabs first parameter of a target method call which is product ID
- ProductPermissionAspect checks if the product belongs to tenant of an authenticated user
- ProductPermissionAspect generates 403 forbidden if the product doesn’t belong to the user
If you want to deep dive into more details:
https://docs.spring.io/spring-framework/reference/core/aop.html