4. Authorization Flow


Hãy đi sâu vào Authorization Flow trong Spring Security. Tôi sẽ giải thích về AccessDecisionManagerVoter trong việc ra quyết định phân quyền, cùng với cách sử dụng Expression-based Authorization (như @PreAuthorizehasRole).


1. AccessDecisionManager và Voter trong việc ra quyết định phân quyền

a. AccessDecisionManager

  • Định nghĩa:

    • AccessDecisionManager là một giao diện trong Spring Security, chịu trách nhiệm quyết định xem một người dùng có được phép truy cập tài nguyên hay không (authorization).

    • Nó nhận thông tin từ Authentication (danh tính người dùng), ConfigAttribute (quy tắc bảo mật, ví dụ: "ROLE_ADMIN"), và đối tượng tài nguyên (secure object) để đưa ra quyết định.

  • Vai trò:

    • Là "trọng tài" trong luồng phân quyền, tổng hợp ý kiến từ các Voter để ra phán quyết cuối cùng.

  • Các triển khai mặc định:

    1. AffirmativeBased (Mặc định):

      • Chỉ cần một Voter đồng ý (vote "ACCESS_GRANTED") là cho phép truy cập.

    2. ConsensusBased:

      • Quyết định dựa trên đa số phiếu từ các Voter.

    3. UnanimousBased:

      • Yêu cầu tất cả Voter đồng ý thì mới cho phép.

  • Ví dụ cấu hình:

    @Bean
    public AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<?>> voters = Arrays.asList(
            new WebExpressionVoter(), // Voter dựa trên biểu thức
            new RoleVoter()          // Voter dựa trên vai trò
        );
        return new AffirmativeBased(voters);
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .accessDecisionManager(accessDecisionManager())
            .antMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated();
    }

b. Voter

  • Định nghĩa:

    • AccessDecisionVoter là các thành phần nhỏ hơn trong AccessDecisionManager, mỗi Voter đưa ra ý kiến (vote) về việc có cho phép truy cập hay không.

    • Kết quả vote có thể là:

      • ACCESS_GRANTED: Đồng ý.

      • ACCESS_DENIED: Từ chối.

      • ACCESS_ABSTAIN: Không ý kiến (bỏ qua).

  • Các Voter phổ biến:

    1. RoleVoter:

      • Kiểm tra vai trò của người dùng (dựa trên GrantedAuthority).

      • Nếu tài nguyên yêu cầu "ROLE_ADMIN" và người dùng có "ROLE_ADMIN", vote ACCESS_GRANTED.

    2. WebExpressionVoter:

      • Đánh giá các biểu thức SpEL (Spring Expression Language) như hasRole(), permitAll(), từ cấu hình authorizeRequests.

    3. AuthenticatedVoter:

      • Kiểm tra trạng thái xác thực (IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_ANONYMOUSLY).

  • Ví dụ custom Voter:

    public class CustomVoter implements AccessDecisionVoter<Object> {
        @Override
        public boolean supports(ConfigAttribute attribute) {
            return attribute.getAttribute() != null && attribute.getAttribute().startsWith("CUSTOM_");
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    
        @Override
        public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
            for (ConfigAttribute attr : attributes) {
                if ("CUSTOM_READ".equals(attr.getAttribute()) && authentication.getAuthorities()
                    .stream().anyMatch(a -> "ROLE_USER".equals(a.getAuthority()))) {
                    return ACCESS_GRANTED;
                }
            }
            return ACCESS_ABSTAIN;
        }
    }
    • Đăng ký Voter này vào AccessDecisionManager để kiểm tra quyền tùy chỉnh.

Luồng hoạt động:

  1. FilterSecurityInterceptor (filter cuối trong Filter Chain) gọi AccessDecisionManager.

  2. AccessDecisionManager gửi Authentication, tài nguyên, và ConfigAttribute đến từng Voter.

  3. Các Voter vote, và AccessDecisionManager tổng hợp kết quả theo chiến lược (Affirmative, Consensus, Unanimous).

  4. Nếu được phép, request tiếp tục; nếu không, ném AccessDeniedException.


2. Expression-based Authorization (@PreAuthorize, hasRole)

a. Tổng quan

  • Expression-based Authorization:

    • Spring Security cho phép định nghĩa quyền truy cập bằng các biểu thức SpEL (Spring Expression Language), thay vì chỉ dựa trên vai trò cố định.

    • Được áp dụng trong cấu hình HttpSecurity hoặc annotation như @PreAuthorize, @PostAuthorize.

  • Ưu điểm:

    • Linh hoạt, có thể kiểm tra quyền theo ngữ cảnh (context), thuộc tính người dùng, hoặc logic phức tạp.

b. Các biểu thức phổ biến

  • hasRole('ROLE_NAME'):

    • Kiểm tra xem người dùng có vai trò cụ thể không.

  • hasAuthority('AUTHORITY'):

    • Tương tự hasRole, nhưng không thêm tiền tố "ROLE_".

  • hasAnyRole('ROLE1', 'ROLE2'):

    • Kiểm tra nếu có ít nhất một trong các vai trò.

  • permitAll(): Cho phép tất cả.

  • denyAll(): Từ chối tất cả.

  • isAuthenticated(): Yêu cầu đã xác thực.

  • authentication: Truy cập đối tượng Authentication.

c. Sử dụng trong HttpSecurity

  • Ví dụ:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
            .antMatchers("/public/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin();
    }
    • WebExpressionVoter sẽ đánh giá các biểu thức này trong Filter Chain.

d. Sử dụng annotation (@PreAuthorize, @PostAuthorize)

  • Cần bật Method Security:

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends GlobalMethodSecurityConfiguration {
    }
  • @PreAuthorize:

    • Kiểm tra quyền trước khi thực thi phương thức.

    • Ví dụ:

      @PreAuthorize("hasRole('ADMIN')")
      public String adminOnly() {
          return "Admin access";
      }
      
      @PreAuthorize("#username == authentication.name")
      public String getMyProfile(String username) {
          return "Profile of " + username;
      }
  • @PostAuthorize:

    • Kiểm tra quyền sau khi phương thức chạy, dựa trên kết quả trả về.

    • Ví dụ:

      @PostAuthorize("returnObject.owner == authentication.name")
      public Document getDocument(Long id) {
          return documentService.findById(id);
      }
  • Kết hợp với tham số:

    • Dùng #param để truy cập tham số phương thức trong biểu thức.

e. Cách hoạt động

  • Khi dùng @PreAuthorize hoặc @PostAuthorize, Spring Security tạo một proxy (AOP) quanh phương thức.

  • MethodSecurityInterceptor gọi AccessDecisionManager để đánh giá biểu thức SpEL, với sự hỗ trợ của WebExpressionVoter.


Tóm tắt

  • AccessDecisionManager và Voter:

    • AccessDecisionManager tổng hợp ý kiến từ các Voter để quyết định phân quyền.

    • Voter (như RoleVoter, WebExpressionVoter) kiểm tra quyền dựa trên vai trò hoặc biểu thức.

  • Expression-based Authorization:

    • Dùng SpEL trong HttpSecurity (hasRole) hoặc annotation (@PreAuthorize) để định nghĩa quyền linh hoạt.

Last updated