2. Security Context


Hãy đi sâu vào Security Context trong Spring Security. Tôi sẽ giải thích về SecurityContextHolder, các chiến lược lưu trữ (ThreadLocalInheritableThreadLocal), và cách thông tin người dùng được truyền qua các request.


1. SecurityContextHolder và các chiến lược lưu trữ

a. SecurityContextHolder là gì?

  • Định nghĩa:

    • SecurityContextHolder là một lớp tĩnh trong Spring Security, đóng vai trò như một "người giữ" trung tâm để lưu trữ và truy xuất thông tin bảo mật (security context) của request hiện tại.

    • Nó chứa SecurityContext, mà trong đó lưu trữ đối tượng Authentication – thông tin về người dùng đã xác thực (username, roles, v.v.).

  • Vai trò:

    • Cho phép truy cập thông tin người dùng từ bất kỳ đâu trong ứng dụng (controller, service, v.v.) mà không cần truyền tham số thủ công.

  • Ví dụ cơ bản:

    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    String username = auth.getName(); // Lấy username
    Collection<? extends GrantedAuthority> authorities = auth.getAuthorities(); // Lấy roles

b. Các chiến lược lưu trữ

  • SecurityContextHolder hỗ trợ nhiều chiến lược lưu trữ để quản lý SecurityContext giữa các thread và request. Các chiến lược này được cấu hình qua SecurityContextHolder.setStrategyName() hoặc mặc định trong Spring Security.

  1. MODE_THREADLOCAL (ThreadLocal):

    • Mô tả:

      • Sử dụng ThreadLocal để lưu trữ SecurityContext riêng biệt cho mỗi thread.

      • Mỗi request trong ứng dụng web thường được xử lý bởi một thread riêng, nên SecurityContext chỉ tồn tại trong phạm vi thread đó.

    • Ưu điểm:

      • Đơn giản, hiệu quả cho ứng dụng web truyền thống.

      • Không bị rò rỉ thông tin giữa các request.

    • Nhược điểm:

      • Không hoạt động tốt với các tác vụ bất đồng bộ (async) hoặc thread pool, vì ThreadLocal không được truyền sang thread con.

    • Mặc định: Đây là chiến lược mặc định của Spring Security.

    • Ví dụ:

      SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_THREADLOCAL);
  2. MODE_INHERITABLETHREADLOCAL (InheritableThreadLocal):

    • Mô tả:

      • Sử dụng InheritableThreadLocal để lưu trữ SecurityContext, cho phép thread con kế thừa context từ thread cha.

    • Ưu điểm:

      • Hữu ích trong ứng dụng có xử lý bất đồng bộ hoặc thread pool (như @Async trong Spring), vì thông tin bảo mật được truyền sang thread con.

    • Nhược điểm:

      • Có thể gây rò rỉ thông tin nếu thread con được tái sử dụng cho request khác.

    • Ví dụ:

      SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    • Ứng dụng thực tế:

      • Khi dùng @Async:

        @Service
        public class MyService {
            @Async
            public void asyncTask() {
                Authentication auth = SecurityContextHolder.getContext().getAuthentication();
                System.out.println("User in async: " + auth.getName()); // Vẫn truy cập được
            }
        }
  3. MODE_GLOBAL:

    • Mô tả:

      • Lưu trữ SecurityContext trong một biến tĩnh toàn cục, chung cho tất cả thread.

    • Ưu điểm:

      • Đơn giản, dùng trong ứng dụng không yêu cầu đa luồng.

    • Nhược điểm:

      • Không an toàn trong ứng dụng web, vì tất cả request chia sẻ cùng context.

    • Ít dùng: Chủ yếu cho mục đích test hoặc ứng dụng đơn luồng.

  • Cấu hình chiến lược:

    • Trong Spring Boot, thêm vào application.properties:

      spring.security.strategy=INHERITABLETHREADLOCAL
    • Hoặc trong mã:

      @PostConstruct
      public void init() {
          SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
      }

2. Cách thông tin người dùng được truyền qua các request

  • Quy trình truyền thông tin:

    1. Xác thực ban đầu:

      • Khi người dùng đăng nhập (qua UsernamePasswordAuthenticationFilter chẳng hạn), Spring Security tạo đối tượng Authentication và đặt vào SecurityContext.

      • Ví dụ:

        Authentication auth = new UsernamePasswordAuthenticationToken("user", null, 
            Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
        SecurityContextHolder.getContext().setAuthentication(auth);
    2. Lưu trữ giữa các request:

      • SecurityContextPersistenceFilter (filter đầu tiên trong Filter Chain) chịu trách nhiệm:

        • Lấy SecurityContext từ HttpSession (nếu có) ở đầu request.

        • Lưu lại SecurityContext vào HttpSession ở cuối request (trước khi response được gửi).

      • Điều này đảm bảo thông tin người dùng (như Authentication) được duy trì qua nhiều request mà không cần xác thực lại.

    3. Truy xuất trong ứng dụng:

      • Bất kỳ đâu trong mã, bạn có thể lấy thông tin:

        @GetMapping("/profile")
        public String getProfile() {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            return "Hello, " + auth.getName();
        }
    4. Xóa khi đăng xuất:

      • LogoutFilter xóa SecurityContext khỏi HttpSessionSecurityContextHolder khi người dùng đăng xuất.

  • Cơ chế lưu trữ với HttpSession:

    • Mặc định, SecurityContext được lưu trong HttpSession dưới key SPRING_SECURITY_CONTEXT.

    • Khi request mới đến, SecurityContextPersistenceFilter khôi phục context từ session, đặt vào SecurityContextHolder để sử dụng trong thread hiện tại.

  • Trường hợp đặc biệt: Stateless (JWT):

    • Nếu dùng SessionCreationPolicy.STATELESS (ví dụ với JWT):

      http.sessionManagement()
          .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    • SecurityContext không được lưu trong session. Thay vào đó, thông tin người dùng được lấy từ token trong mỗi request (qua custom filter như JwtAuthenticationFilter), và SecurityContextHolder chỉ giữ context trong phạm vi request đó.


Tóm tắt

  • SecurityContextHolder:

    • Lớp trung tâm để lưu trữ và truy xuất SecurityContext.

    • Chiến lược lưu trữ:

      • ThreadLocal: Mặc định, phù hợp với ứng dụng web cơ bản.

      • InheritableThreadLocal: Hỗ trợ truyền context sang thread con (async).

      • Global: Ít dùng, không an toàn cho web.

  • Truyền thông tin qua request:

    • Thông qua HttpSessionSecurityContextPersistenceFilter trong ứng dụng stateful.

    • Qua token và custom filter trong ứng dụng stateless.

Last updated