Mặc dù đã đăng ký nhưng người dùng sẽ không thể sử dụng tài khoản ngay, họ sẽ cần trải qua bước kích hoạt tài khoản, bước này thường là để kiểm tra xem email người dùng sử dụng có chính xác không để tránh spam đồng thời cũng là để thu thập email phục vụ cho các hoạt động thông báo, marketing và bán hàng sau này. Sau khi tài khoản được kích hoạt thì người dùng có thể đăng nhập.

Cập nhật sơ đồ lớp AuthenticationController

Chúng ta có thể thiết kế sơ đồ lớp AuthenticationController như sau:

Trong sơ đồ này chúng ta đã bổ sung:

  1. Lớp UserAccessTokenService: Để xử lý nghiệp vụ liên quan đến access token của người dùng
  2. Lớp PasswordEncoder: Sử dụng để kiểm tra mật khẩu của người dùng tại bước đăng nhập.

Thiết kế sơ đồ lớp cho UserAccessTokenService

Trong sơ đồ này chúng ta có:

  1. Lớp UserAccessTokenService xử lý nghiệp vụ.
  2. Giao diện UserAccessTokenRepository để tương tác với cơ sở dữ liệu.
  3. Lớp ModelToEntityConverter để chuyển đổi dữ liệu từ dạng model sang UserAccessToken entity.
  4. Lớp UserAccessToken ánh xạ với bảng access_tokens trong cơ sở dữ liệu.

Mã nguồn cài đặt

Cài đặt giao diện UserAccessTokenRepository

Đầu tiên chúng ta sẽ khai báo lớp UserAccessToken với mã nguồn như sau:

package vn.techmaster.login.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.time.LocalDateTime;

@Getter
@Setter
@ToString
@Entity
@Table(name = "access_tokens")
public class UserAccessToken {
    @Id
    @Column(name = "access_token")
    private String accessToken;

    @Column(name = "user_id")
    private long userId;

    @Column(name = "expired_at")
    private LocalDateTime expiredAt;
}

Chúng ta sẽ sử dụng chính access token làm id luôn, access token này sẽ gắn với một userId và có thời gian hết hạn.
Tiếp theo chúng ta có thể khai báo giao diện UserAccessTokenRepository với mã nguồn như sau:

package vn.techmaster.login.repo;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import vn.techmaster.login.entity.UserAccessToken;

@Repository
public interface UserAccessTokenRepository
    extends JpaRepository<UserAccessToken, String> {}

Ở đây chúng ta đang sử dụng các hàm truy vấn mặc định mà không phải khai báo thêm.

package vn.techmaster.login.service;

import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import vn.techmaster.login.converter.ModelToEntityConverter;
import vn.techmaster.login.entity.UserAccessToken;
import vn.techmaster.login.repo.UserAccessTokenRepository;

import java.util.UUID;

@Service
@AllArgsConstructor
public class UserAccessTokenService {

    private final UserAccessTokenRepository userAccessTokenRepository;
    private final ModelToEntityConverter modelToEntityConverter;

    public String addUserAccessToken(
        long userId
    ) {
        String accessToken = generateAccessToken(userId);
        UserAccessToken entity = modelToEntityConverter.toUserAccessToken(
            userId,
            accessToken
        );
        userAccessTokenRepository.save(entity);
        return accessToken;
    }

    private String generateAccessToken(long userId) {
        return userId + ":" + UUID.randomUUID();
    }
}

Ở đây chúng ta thực hiện những công việc sau:

  1. Tạo ra một đối UserAccessToken chính là bản ghi access token cho người dùng với userId.
  2. Lưu bản ghi access token vào cơ sở dữ liệu.
    Chúng ta có mã nguồn cài đặt cho hàm toUserAccessToken như sau:
public UserAccessToken toUserAccessToken(
    long userId,
    String accessToken
) {
    UserAccessToken entity = new UserAccessToken();
    entity.setUserId(userId);
    entity.setAccessToken(accessToken);
    entity.setExpiredAt(
        LocalDateTime
            .now()
            .plusHours(ACCESS_TOKEN_EXPIRED_IN_HOUR)
    );
    return entity;
}

Cài đặt API xác nhận người dùng

Đầu tiên chúng ta sẽ cần bổ sung đối tượng phụ thuộc userAccessTokenService để xử lý nghiệm vụ liên quan đến token của người dùng vào lớp AuthenticationController như sau:

private final UserAccessTokenService userAccessTokenService;

Tiếp theo chúng ta có thể cài đặt api như sau:

@PostMapping("/users/{username}/validate-access-token")
public ResponseEntity<?> usersUsernameValidateAccessToken(
    @PathVariable("username") String username,
    @RequestParam(value = "activationToken") String activationToken
) {
    long userId = authenticationValidator
        .validateUserActivationTokenAndGetId(
            username,
            activationToken
        );
    userService.activeUser(userId);
    return ResponseEntity.noContent().build();
}

Ở đây chúng ta có:

  1. API xác thực người dùng là: /users/{username}/validate-access-token.
  2. Chúng ta sẽ gọi đến hàm authenticationValidator.validateUserActivationTokenAndGetId để xác nhận người dùng.
  3. Nếu không có lỗi gì gì sẽ kích hoạt người dùng.
    Mã nguồn cài đặt của hàm validateUserActivationTokenAndGetId sẽ như sau:
public long validateUserActivationTokenAndGetId(
    String username,
    String activationToken
) {
    UserModel user = userService.getUserByUsername(
        username
    );
    if (user == null
        || user.getActivationToken() == null
        || !user.getActivationToken().equals(activationToken)
    ) {
        throw new HttpBadRequestException(
            Collections.singletonMap("activationToken", "invalid")
        );
    }
    return user.getId();
}

Ở đây chúng cũng chỉ lấy thông tin người dùng từ cơ sở dữ liệu theo tên đăng nhập, sau đó so sánh token từ yêu cầu với toten của người dùng được lấy từ cơ sở dữ liệu, nếu không khớp thì ném ra exception.

Cài đặt API đăng nhập

Sau khi người dùng đã kích hoạt tài khoản thì họ có thể đăng nhập, vậy nên chúng ta cần cài đặt API đăng nhập với mã nguồn như sau:

@PostMapping("/login")
public UserLoginResponse loginPost(
    HttpServletResponse response,
    @RequestBody LoginRequest request
) {
    long userId = authenticationValidator.validateUserCredential(
        request.getUsername(),
        request.getPassword()
    );
    String accessToken = userAccessTokenService.addUserAccessToken(
        userId
    );
    Cookie cookie = new Cookie(COOKIE_NAME_ACCESS_TOKEN, accessToken);
    cookie.setHttpOnly(true);
    cookie.setMaxAge(ACCESS_TOKEN_EXPIRED_IN_HOUR * 60 * 60);
    response.addCookie(cookie);
    return new UserLoginResponse(accessToken);
}
  1. API của chúng ta có URI là /login.
  2. Đầu tiên chúng ta sẽ kiểm tra thông tin đăng nhập.
  3. Nếu hợp lệ chúng ta sẽ cấp access token cho người dùng.
  4. Chúng ta sẽ set access token vào cả cookie lẫn trả về cho client thông qua response.
    Các hằng số trong hàm bạn có thể khai báo ở lớp LoginConstants với mã nguồn như sau:
public static final int ACCESS_TOKEN_EXPIRED_IN_HOUR = 24;
public static final String COOKIE_NAME_ACCESS_TOKEN = "X-AccessToken";

Mã nguồn cài đặt của hàm authenticationValidator.validateUserCredential sẽ như sau:

public long validateUserCredential(
    String username,
    String password
) {
    UserModel user = userService.getUserByUsername(
        username
    );
    if (user == null
        || user.getStatus() != UserStatus.ACTIVATED
        || !passwordEncoder.matches(password, user.getPassword())
    ) {
        throw new HttpBadRequestException(
            Collections.singletonMap("credential", "invalid")
        );
    }
    return user.getId();
}

Trong hàm này chúng ta:

  1. Lấy ra thông tin người dùng từ cơ sở dữ liệu theo tên đang nhập
  2. Kiểm tra trạng thái người dùng và so khớp mật khẩu.
  3. Nếu mật khẩu không khớp chúng ta sẽ ném ra exception.
    Bạn đừng quên bổ sung phụ thuộc passwordEncoder vào lớp AuthenticationValidator nhé:
private final PasswordEncoder passwordEncoder;

Khởi chạy chương trình

Như vậy là chúng ta đã có đầy đủ mã nguồn bây giờ chúng ta có thể tiếp tục sử dụng lớp SpringBootLoginStartUp để khởi chạy chương trình.
Trong bài trước chúng ta đã đăng ký người dùng với tên đăng nhập là techmaster1, chúng ta có thể truy xuất vào cơ sở dữ liệu bảng users để lấy token kích hoạt, ví dụ của mình là 3269366e-4770-4696-b388-0d22f7133a9e.
Bây giờ mình có thể sử dụng PostMan để gọi API kích hoạt như sau:

Sau đó chúng ta có thể đăng nhập thông qua API login như sau:

Tổng kết

Như vậy chúng ta đã cùng nhau cài đặt các API kích hoạt tài khoản và login.


Cám ơn bạn đã quan tâm đến bài viết|video này. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a