Bài viết gốc bạn có thể xem ở đây.

1. Tổng quan

Bài viết này sẽ thảo luận về một phần quan trọng của quy trình đăng ký đó là mã hoá mật khẩu, chứ không đơn giản là lưu trữ mật khẩu gốc.

Đây là một vài cơ chế mã hoá mật khẩu được hỗ trợ bởi Spring Security và trong bài viết này , chúng ta sẽ sử dụng BCrypt, vì hiện tại nó là giải pháp tốt nhất.

Hầu hết các cơ chế khác, như MD5PasswordEncoderShaPasswordEncoder sử dụng thuật toán yếu hơn và hiện tại không được dùng nữa.

2. Định nghĩa mã hoá mật khẩu

Chúng ta bắt đầu bằng định nghĩa BCryptPasswordEncoder đơn giản như một bean trong cấu hình của chúng ta:

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

Ở các cơ chế cũ hơn như SHAPasswordEncoder, yêu cầu người dùng gửi một salt value khi muốn mã hoá mật khẩu.

Tuy nhiên, BCrypt sẽ tự sinh ra một salt value ngẫu nhiên. Đây là điều quan trọng cần hiểu vì vậy mỗi lần chúng ta yêu cầu thì sẽ có một kết quả khác nhau và vì vậy chúng ta chỉ cần mã hoá mật khẩu một lần.

Để salt value tự sinh ra ngẫu nhiên, BCrypt sẽ lưu trữ salt trong chính hash value của nó. Ví dụ, trong hash value dưới đây:

$2a$10$ZLhnHxdpHETcxmtEStgpI./Ri1mksgJ9iDP36FmfMdYyVg9g0b2dq

Đây là ba trường được phân tách bởi $:

  1. “2a” đại diện cho phiên bản thuật toán BCrypt 
  2. “10” đại diện cho độ dài của thuật toán
  3. “ZLhnHxdpHETcxmtEStgpI.” là một phần của chuỗi salt được sinh ra ngẫu nhiên. Về cơ bản, 22 ký tự đầu tiên là salt. Phần còn lại trong trường là phiên bản băm thực tế của văn bản thuần tuý.

Ngoài ra,  lưu ý rằng thuật toán BCrypt tạo ra một chuỗi có độ dài 60 ký tự, vì vậy chúng ta cần chắc chắn rằng mật khẩu sẽ lưu trữ ở trong cột có khả năng chứa nó. Một lỗi thường gặp là tạo một cột có độ dài khác 60 ký tự và sau đó sẽ gặp lỗi Invalid Username or Password trong quá trình xác thực.

3. Mã hoá mật khẩu trong đăng ký

Chúng ta sẽ sử dụng PasswordEncoder trong UserService của chúng ta để băm mật khẩu trong quá trình đăng ký tài khoản:

Ví dụ 3.1. - UserService băm mật khẩu

@Autowired
private PasswordEncoder passwordEncoder;

@Override
public User registerNewUserAccount(UserDto accountDto) throws EmailExistsException {
    if (emailExist(accountDto.getEmail())) {
        throw new EmailExistsException(
          "There is an account with that email adress:" + accountDto.getEmail());
    }
    User user = new User();
    user.setFirstName(accountDto.getFirstName());
    user.setLastName(accountDto.getLastName());
    
    user.setPassword(passwordEncoder.encode(accountDto.getPassword()));
    
    user.setEmail(accountDto.getEmail());
    user.setRole(new Role(Integer.valueOf(1), user));
    return repository.save(user);
}

4. Mã hoá mật khẩu trong xác thực

Bây giờ chúng ta sẽ xử lý một nửa còn lại của quá trình này là mã hoá mật khẩu khi người dùng xác thực.

Đầu tiên, chúng ta cần inject bean mã hoá mật khẩu để sớm xác định authentication provider (nhà cung cấp xác thực) của chúng ta:

@Autowired
private UserDetailsService userDetailsService;

@Bean
public DaoAuthenticationProvider authProvider() {
    DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(userDetailsService);
    authProvider.setPasswordEncoder(encoder());
    return authProvider;
}


Cấu hình bảo mật rất đơn giản:

  • Chúng ta đang inject code của user details service
  • Chúng ta đang định nghĩa nhà cung cấp xác thực tham chiếu đến detail service của chúng ta
  • Chúng ta cũng kích hoạt mã hoá mật khẩu

Và cuối cùng, chúng ta cần tham chiếu đến nhà cung cấp xác thực trong cấu hình XML bảo mật của chúng ta:

<authentication-manager>
    <authentication-provider ref="authProvider" />
</authentication-manager>

Hoặc trong trường hợp này chúng ta sử dụng cấu hình Java:

@Configuration
@ComponentScan(basePackages = { "com.baeldung.security" })
@EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider());
    }
    
    ...
}

5. Tổng kết

Hướng dẫn lần này là tiếp tục của chuỗi bài về Đăng ký, trình bày về cách lưu trữ mật khẩu đúng cách trong cơ sở dữ liệu bằng cách tận dụng BCrypt tuy đơn giản nhưng rất mạnh.

Các đoạn code trên có thể xem ở GitHub này - đây là project ở Eclipse vì vậy nó rất dễ dàng import và chạy nó.