3. Authentication Flow


Hãy đi sâu vào Authentication Flow trong Spring Security. Tôi sẽ giải thích về AuthenticationManagerAuthenticationProvider, cùng với quy trình xác thực từ khi nhận request đến khi trả về response.


1. AuthenticationManager và AuthenticationProvider

a. AuthenticationManager

  • Định nghĩa:

    • AuthenticationManager là một giao diện trong Spring Security, chịu trách nhiệm xử lý quá trình xác thực (authentication). Nó nhận một đối tượng Authentication chưa xác thực (unauthenticated) và trả về một đối tượng Authentication đã xác thực (authenticated) nếu thành công, hoặc ném ngoại lệ nếu thất bại.

  • Vai trò:

    • Là "người điều phối" chính trong luồng xác thực, quyết định cách xác minh thông tin đăng nhập (credentials) của người dùng.

    • Thường được triển khai bởi lớp ProviderManager, một implementation mặc định của Spring Security.

  • Cách hoạt động:

    • ProviderManager chứa một danh sách các AuthenticationProvider. Nó thử từng provider theo thứ tự cho đến khi một provider xác thực thành công hoặc hết danh sách.

b. AuthenticationProvider

  • Định nghĩa:

    • AuthenticationProvider là giao diện xác định logic xác thực cụ thể. Mỗi provider hỗ trợ một loại xác thực (ví dụ: username/password, JWT, LDAP).

  • Vai trò:

    • Thực hiện việc kiểm tra thông tin đăng nhập (credentials) và trả về đối tượng Authentication đã xác thực.

    • Cho phép tùy chỉnh logic xác thực theo nhu cầu.

  • Ví dụ:

    • DaoAuthenticationProvider: Một provider mặc định, dùng UserDetailsService để lấy thông tin người dùng từ cơ sở dữ liệu và PasswordEncoder để so sánh mật khẩu.

  • Cấu trúc cơ bản:

    public interface AuthenticationProvider {
        Authentication authenticate(Authentication authentication) throws AuthenticationException;
        boolean supports(Class<?> authentication);
    }

Mối quan hệ giữa AuthenticationManager và AuthenticationProvider

  • AuthenticationManager (thường là ProviderManager) ủy thác công việc xác thực cho một hoặc nhiều AuthenticationProvider.

  • Nếu có nhiều provider, ProviderManager sẽ duyệt qua danh sách để tìm provider phù hợp (dựa trên phương thức supports()).


2. Quy trình xác thực từ request đến response

Dưới đây là luồng chi tiết của quá trình xác thực trong Spring Security, từ khi nhận request đến khi trả về response, lấy ví dụ với form login (username/password):

Bước 1: Request đến và Filter Chain bắt đầu

  • Một request (ví dụ: POST /login với username và password) được gửi đến server.

  • FilterChainProxy (Security Filter Chain) bắt đầu xử lý request, và filter liên quan đến xác thực (như UsernamePasswordAuthenticationFilter) được kích hoạt.

Bước 2: UsernamePasswordAuthenticationFilter xử lý

  • Vai trò: Filter này nhận request POST từ form login (mặc định /login).

  • Hoạt động:

    1. Trích xuất usernamepassword từ request.

    2. Tạo đối tượng UsernamePasswordAuthenticationToken (unauthenticated):

      Authentication authRequest = new UsernamePasswordAuthenticationToken(username, password);
    3. Gửi authRequest đến AuthenticationManager để xác thực:

      Authentication authResult = authenticationManager.authenticate(authRequest);

Bước 3: AuthenticationManager xử lý

  • ProviderManager nhận authRequest và duyệt qua danh sách AuthenticationProvider.

  • Giả sử dùng DaoAuthenticationProvider:

    1. Kiểm tra loại xác thực:

      • supports() xác nhận provider này hỗ trợ UsernamePasswordAuthenticationToken.

    2. Tải thông tin người dùng:

      • Gọi UserDetailsService.loadUserByUsername(username) để lấy UserDetails (bao gồm username, password đã mã hóa, roles).

    3. So sánh mật khẩu:

      • Dùng PasswordEncoder để kiểm tra mật khẩu nhập vào với mật khẩu lưu trữ.

      • Nếu khớp, tạo Authentication đã xác thực:

        return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
      • Nếu không khớp, ném BadCredentialsException.

Bước 4: Lưu Authentication vào SecurityContext

  • Sau khi xác thực thành công, UsernamePasswordAuthenticationFilter:

    1. Lưu authResult vào SecurityContextHolder:

      SecurityContextHolder.getContext().setAuthentication(authResult);
    2. Gọi successHandler (mặc định chuyển hướng đến trang chính hoặc URL trước đó).

Bước 5: SecurityContextPersistenceFilter lưu trữ

  • Ở cuối Filter Chain, SecurityContextPersistenceFilter lưu SecurityContext vào HttpSession (dưới key SPRING_SECURITY_CONTEXT) để dùng cho các request sau.

Bước 6: Response trả về

  • Nếu xác thực thành công:

    • Người dùng được chuyển hướng (redirect) hoặc nhận response (trong trường hợp API).

  • Nếu thất bại:

    • ExceptionTranslationFilter bắt ngoại lệ (như AuthenticationException) và gọi failureHandler (mặc định chuyển hướng về form login với thông báo lỗi).

Luồng tổng quát (hình dung):

Request → FilterChainProxy → UsernamePasswordAuthenticationFilter → AuthenticationManager
→ AuthenticationProvider → UserDetailsService + PasswordEncoder → Authentication (authenticated)
→ SecurityContextHolder → HttpSession → Response

Ví dụ cụ thể: Cấu hình Authentication Flow

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .permitAll()
            .and()
            .logout()
            .permitAll();
    }
}
  • Giải thích:

    • AuthenticationManager được cấu hình với DaoAuthenticationProvider (tự động).

    • UserDetailsService cung cấp thông tin người dùng.

    • UsernamePasswordAuthenticationFilter xử lý form login tại /login.


Tùy chỉnh Authentication Flow

  • Custom AuthenticationProvider:

    @Component
    public class CustomAuthenticationProvider implements AuthenticationProvider {
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            String username = authentication.getName();
            String password = authentication.getCredentials().toString();
            if ("user".equals(username) && "pass".equals(password)) {
                return new UsernamePasswordAuthenticationToken(username, password, 
                    Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
            }
            throw new BadCredentialsException("Invalid credentials");
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
        }
    }
    • Đăng ký:

      @Autowired
      private CustomAuthenticationProvider customProvider;
      
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
          auth.authenticationProvider(customProvider);
      }

Tóm tắt

  • AuthenticationManager: Điều phối xác thực, giao việc cho AuthenticationProvider.

  • AuthenticationProvider: Thực hiện logic xác thực cụ thể (ví dụ: kiểm tra username/password).

  • Quy trình:

    1. Filter nhận request → Tạo Authentication chưa xác thực.

    2. AuthenticationManagerAuthenticationProvider xác thực.

    3. Lưu kết quả vào SecurityContext → Trả về response.

Bạn muốn tôi đi sâu hơn vào phần nào (ví dụ: custom provider chi tiết, debug luồng xác thực), hay tiếp tục với phần khác trong roadmap?

Last updated