Bài 9: Bảo mật và kiểm soát truy cập với AOP

Mục tiêu bài học
Biết cách sử dụng AOP để xử lý security concern (kiểm tra role, permission) ngay tại service method hoặc bất cứ chỗ nào cần.
So sánh custom AOP security với Spring Security method-level (
@PreAuthorize
,@Secured
).Học cách tạo custom annotation (e.g.
@CheckPermission
) để kiểm soát truy cập linh hoạt và cách test logic security bằng JUnit + Mockito.
Nội dung chính
1. Security Aspect
Trong nhiều dự án, ta cần xác thực/kiểm tra quyền (role, permission) trước khi thực thi một số method nhạy cảm (xóa user, thay đổi cấu hình…).
Thay vì viết if (!user.hasRole(…)) ở đầu mỗi method, ta dùng AOP “chen” logic security:
Advice chạy trước method (thường
@Before
hoặc@Around
).Kiểm tra: user có đủ role/permission?
Nếu không => ném
AccessDeniedException
hoặc tương tự.Nếu có => cho proceed.
Ví dụ (pseudo-code):
@Aspect
@Component
public class SecurityAspect {
@Pointcut("@annotation(com.example.security.CheckPermission)")
private void checkPermissionAnnotation() {}
@Before("checkPermissionAnnotation() && @annotation(annotation)")
public void verifyPermission(JoinPoint joinPoint, CheckPermission annotation) {
// Lấy role cần thiết từ annotation
String requiredRole = annotation.roleNeeded();
// Lấy user hiện tại (từ SecurityContextHolder hoặc session)
User currentUser = SecurityContextHolder.getCurrentUser();
if (!currentUser.hasRole(requiredRole)) {
throw new AccessDeniedException("User " + currentUser.getUsername()
+ " does not have role " + requiredRole);
}
// Nếu đủ quyền => method proceed bình thường
}
}
- Ở đây,
CheckPermission
là annotation tùy biến, ta sẽ giới thiệu ngay sau.
2. Custom Annotation @CheckPermission
Ta tạo annotation để “đánh dấu” method nào cần kiểm tra role.
Ví dụ:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckPermission {
String roleNeeded(); // role mà user phải có
}
Sau đó, method nào cần check, ta gắn
@CheckPermission(roleNeeded="ADMIN")
:@Service public class UserService { @CheckPermission(roleNeeded="ADMIN") public void deleteUser(Long userId) { // Logic xóa user // ... } }
Như vậy, AOP aspect
SecurityAspect
sẽ bắt method có @CheckPermission, so sánhroleNeeded
với role user hiện tại.
3. Ví dụ code
Annotation: (đã nói ở trên)
Aspect: (như phần SecurityAspect)
Method cần kiểm tra:
@Service public class UserService { @CheckPermission(roleNeeded="ADMIN") public void deleteUser(Long userId) { System.out.println("Deleting user " + userId); // Thực thi xóa database... } }
Khi
deleteUser(...)
được gọi:- AOP proxy → aspect
verifyPermission()
→ kiểm tra role → nếu fail => ném exception, nếu pass => proceed.
- AOP proxy → aspect
4. Tích hợp Spring Security
Spring Security có sẵn method-level security:
@PreAuthorize("hasRole('ADMIN')")
@Secured("ROLE_ADMIN")
Thực chất, Spring Security cũng dùng AOP + proxy.
So sánh approach AOP custom vs. Spring Security:
Ưu điểm AOP custom: Linh hoạt, “tự mình” xử lý logic role, context. Dễ thêm logic logging, thong báo…
Nhược điểm: Ta phải viết code security logic, annotation, aspect. Spring Security method-level “có sẵn” và chuẩn hoá, có integration filter.
Best practice: Thường, Spring Security method-level (@PreAuthorize) đủ tiện lợi. Lúc đặc thù (chẳng hạn, logic role “ngoài luồng” phức tạp), ta có thể xài custom.
5. Best Practices for Security
Không nên dồn toàn bộ logic security vào AOP, vì có thể cồng kềnh. Kết hợp web filter, GlobalMethodSecurity…
Rõ ràng tách layer:
Authentication (đăng nhập, JWT token) → filter.
Authorization (kiểm tra role/permission) → method-level (AOP).
Cân nhắc xung đột:
Lệnh check role lặp ở custom AOP + Spring Security?
Nên duy trì 1 cơ chế chính, 1 cơ chế backup.
6. Test Security Aspect
JUnit + Mockito: ta có thể “mock” user/role:
Thiết lập user “admin” ->
SecurityContextHolder.setCurrentUser(mockAdmin)
.Gọi
userService.deleteUser(123L)
.Kiểm tra không ném exception => pass test.
Test user “normal”:
SecurityContextHolder.setCurrentUser(normalUserWithoutADMIN)
.Gọi
deleteUser(...)
=> Mong đợiAccessDeniedException
.
Ví dụ:
@SpringBootTest
class SecurityAspectTest {
@Autowired
UserService userService;
@Test
void testDeleteUserWithAdminRole() {
SecurityContextHolder.setCurrentUser(new User("admin", "ADMIN"));
Assertions.assertDoesNotThrow(() -> userService.deleteUser(123L));
}
@Test
void testDeleteUserWithoutAdminRole() {
SecurityContextHolder.setCurrentUser(new User("bob", "USER"));
Assertions.assertThrows(AccessDeniedException.class, () -> {
userService.deleteUser(123L);
});
}
}
Tóm tắt
Sử dụng AOP để kiểm tra role/permission ngay trước khi method quan trọng chạy.
Tạo annotation
@CheckPermission
, AspectSecurityAspect
=> ném exception nếu user thiếu quyền.So sánh custom security AOP với
@PreAuthorize
của Spring Security – tuỳ trường hợp mà áp dụng.Bài 10 tiếp theo: Giám sát hiệu năng, đo thời gian method (performance monitoring) bằng AOP.
Câu hỏi thảo luận
Khi nào nên dùng Spring Security
@PreAuthorize
thay vì custom aspect?Hạn chế của security aspect so với Spring Security method-level là gì?
Subscribe to my newsletter
Read articles from hoangkim directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
