Người viết: Ngô Công Tuyền - Học viên lớp Java 09
Email: tuyenemotion@gmail.com

Một trong những mục tiêu cốt lõi của bất kỳ framework nào là xác thực người dùng. Xác thực là quá trình kiểm tra thông tin đăng nhập và xác nhận quyền của người dùng. Trong bài viết này, ta sẽ đề cập đến quá trình xác thực trong Springboot và các thành phần của nó.
Hãy xem xét các quy trình làm việc cơ bản của các lớp và bộ lọc trong quá trình xác thực.
Spring Security Diagram
Spring security luôn kết hợp với bộ lọc bảo mật. Mỗi yêu cầu từ người dùng đến máy chủ đều phải thông qua bộ lọc này. Dựa trên quá trình xử lý logic, yêu cầu có thể bị từ chối hoặc đi tiếp, chúng ta hãy xem xét các quá trình xác thực cụ thể.

1.Yêu cầu từ HTTP (Incoming HTTP Request).

Mỗi một yêu cầu tới máy chủ sẽ đều phải thông qua một chuỗi quá trình lọc và phân quyền. Quan sát bước 1 của sơ đồ trên, mỗi yêu cầu đều thông qua một chuỗi các bộ lọc bảo mật. Hãy chú ý đến các đặc điểm sau:

  1. Mỗi yêu cầu sẽ tìm đến các bộ lọc đến khi nó tìm thấy bộ lọc liên quan.
  2. Mỗi yêu cầu sẽ đi qua các tập hợp bộ lọc riêng biệt. Chúng ta có thể cấu hình bộ lọc dựa trên mẫu yêu cầu (request patterns) hoặc phần đầu của yêu cầu (request headers).
  3. Trong sơ đồ trên, yêu cầu đăng nhập thông qua chuỗi bộ lọc trên và đến với UsernamePasswordAuthenticationFilter.
  4. Thông thường, một yêu cầu xác thực đơn giản sẽ đi qua chuỗi bộ lọc đến khi nó tìm được BasicAuthenticationFilter.

2.Xác thực token dựa trên thông tin xác thực của người dùng.

Khi người dùng đăng nhập và yêu cầu xác thưc thông qua bộ lọc, nó sẽ lấy ra tên đăng nhập và mật khẩu từ nội dung của yêu cầu (request payload). Spring security sẽ tạo ra một đối tượng xác thực dựa trên tên đăng nhập và mật khẩu. Ví dụ:
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password);

3.Trình quản lý xác thực (Authentication manager).

Trình quản lý xác thực là một phần cơ bản của quá trình xác thực trong Spring. AuthenticationManager là một API xác định cách mà bộ lọc Spring thực hiện xác thực.

@Override
protected AuthenticationManager authenticationManager() throws Exception {
    return super.authenticationManager();
}

Những điểm đáng chú ý của AuthenticationManager:

  1. Bên trong AuthenticationProvider có trách nhiêm thực hiện quá trình xác thực.
  2. ProviderManager quản lý cách triển khai thông dụng nhất của AuthenticationProvider.
  3. ProviderManger ủy quyển vào danh sách các AuthenticationProvider.

4.AuthenticationProviders.

Các AuthenticationProvider có trách nhiệm xử lý yêu cầu và thực hiện xác thực cụ thể. Nó cung cấp cơ chế lấy thông tin của người dùng mà chúng ta có thể thực hiện xác thực. Ví dụ về AuthenticationProvider:

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

AuthenticationProvider không thể thực thi một cách trực tiếp nhưng chúng ta có thể cấu hình chúng thông qua nhiều provider khác nhau kế thừa AuthenticationProvider.
Authentication Providers Diagram
Dưới đây là một số OOTB authentication providers:

  1. DaoAuthenticationProvider.
  2. JAAS Authentication
  3. OpenID Authentication
  4. X509 Authentication
  5. SAML 2.0
  6. OAuth 2.0
  7. RememberMeAuthenticationProvider
  8. LdapAuthenticationProvider

5.Custom Authentication Provider.

Với các ứng dụng thực tế, chúng ta có thể custom lại authentication provider. Chúng ta dùng từ khóa implements để kế thừa AuhtenticationProvider interface:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication auth) throws AuthenticationException {
      String username = authentication.getName();
      String pwd = authentication.getCredentials().toString();
      if ("javadevjournal".equals(username) && "pass".equals(pwd)) {
            return new UsernamePasswordAuthenticationToken(username, password, Collections.emptyList());
       } else {
            throw new BadCredentialsException("User authentication failed!!!!");
       }
    }
    @Override
    public boolean supports(Class<?>auth) {
        return auth.equals(UsernamePasswordAuthenticationToken.class);
    }
}

Bước cuối cùng là cấu hình lại class custom authentication provider với Spring security. Chúng ta tạo ra một lớp cấu hình và mở rộng WebSecurityConfigureAdapter.

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    CustomAuthenticationProvider customAuthenticationProvider;

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        //configuring our custom auth provider
        auth.authenticationProvider(customAuthProvider);       
    }
}

6.Spring Security UserDetailsService.

Một số authentication provider có thể sẽ cần UserDetailsService để lấy thông tin uẻ từ database bởi username. Hầu hết các ứng dụng web có thể sử dụng UserDetailSevice để lấy thông tin user trong quá trình login.
Sau đây là trường hợp phổ biến khi sử dụng UserDetailsService.

package org.springframework.security.core.userdetails;

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

7.Authentication và Authentication Exception.

Trong quá trình xác thực, nếu xác thực người dùng thành công, chúng ta sẽ gửi lại một đối tượng Authentication được khởi tạo đầy đủ. Ngược lại , khi xác thực thất bại AuthentiactionException sẽ được đưa ra. Hầu hết các đối tượng xác thực sẽ mang các thông tin sau:

  • Thông tin đăng nhập của người dùng.
  • Danh sách các cấp có thẩm quyền truy cập.
  • Các flag xác thực.
public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

8.Cấu hình xác thực bảo mật.

Bước cuối cùng trong quá trình cài đặt đối tượng xác thực trong SecurityContext. Nó bọc SecurityContext thông qua SecurityContextHolder. Có những điểm chú ý sau:

  • SecurityContextHolder là nơi SpringSecurity lưu trữ thông tin xác thực.
  • SecuritySecurity sẽ không xác thực cách tạo SecurityContextHolder.
  • Khi nó tìm thấy các giá trị trong SecurityContextHolder, nó giả định rằng người dùng hiện tại là người dùng được xác thực.
    Một ví dụ về SecurityContextHolder:
SecurityContext context = //get the context from security holder
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password);
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);

Tổng kết lại, chúng ta cần biết những điều sau đây về SpringSecurity:

  • Kiến trúc tổng thể cho quá trình xác thực là gì?
  • Các bộ lọc xác thực làm việc với nhau như thế nào?
  • Mục đích của việc quản lý xác thực.
  • Tổng quan về authentication providers.
  • Tầm quan trọng của SecurityContextHolder

Các link tham khảo: