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

hoangkimhoangkim
4 min read

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:

    1. Advice chạy trước method (thường @Before hoặc @Around).

    2. Kiểm tra: user có đủ role/permission?

    3. Nếu không => ném AccessDeniedException hoặc tương tự.

    4. Nếu => 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, CheckPermissionannotation 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ánh roleNeeded với role user hiện tại.


3. Ví dụ code

  1. Annotation: (đã nói ở trên)

  2. Aspect: (như phần SecurityAspect)

  3. 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...
         }
     }
    
  4. Khi deleteUser(...) được gọi:

    • AOP proxy → aspect verifyPermission() → kiểm tra role → nếu fail => ném exception, nếu pass => proceed.

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

  1. 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

  2. Rõ ràng tách layer:

    • Authentication (đăng nhập, JWT token) → filter.

    • Authorization (kiểm tra role/permission) → method-level (AOP).

  3. 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:

    1. Thiết lập user “admin” -> SecurityContextHolder.setCurrentUser(mockAdmin).

    2. Gọi userService.deleteUser(123L).

    3. Kiểm tra không ném exception => pass test.

  • Test user “normal”:

    1. SecurityContextHolder.setCurrentUser(normalUserWithoutADMIN).

    2. Gọi deleteUser(...) => Mong đợi AccessDeniedException.

  • 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, Aspect SecurityAspect => 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

  1. Khi nào nên dùng Spring Security @PreAuthorize thay vì custom aspect?

  2. Hạn chế của security aspect so với Spring Security method-level là gì?

0
Subscribe to my newsletter

Read articles from hoangkim directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

hoangkim
hoangkim