Trong nhiều hệ thống, để đảm bảo an toàn cho tài khoản người dùng, chúng ta thường yêu cầu xác thực thêm một lớp bảo mật sau khi đăng nhập thành công. Đây được gọi là Xác thực Hai Yếu Tố (Two-Factor Authentication - 2FA). Ví dụ như mật khẩu giao dịch trong game, hoặc mã xác thực hai bước trên các nền tảng mạng xã hội. Bài viết này sẽ hướng dẫn cách triển khai chức năng 2FA cho một thư viện xác thực tự xây dựng bằng Go.
Cách tiếp cận
Đối tượng của quá trình xác thực 2FA là một token đăng nhập đã tồn tại. Để linh hoạt cho các nghiệp vụ khác nhau, chúng ta cần thêm một định danh cho nghiệp vụ (service) và thời gian hiệu lực của việc xác thực. Việc xác thực không thể tồn tại vĩnh viễn, vì vậy thời gian hiệu lực là một yếu tố quan trọng.
Về mặt nghiệp vụ, chúng ta cần bốn phương thức chính:
- Kích hoạt xác thực 2FA cho một token.
- Kiểm tra xem xác thực 2FA có đang hiệu lực hay không.
- Lấy thời gian còn lại của việc xác thực 2FA.
- Hủy xác thực 2FA (làm cho nó hết hiệu lực).
Về mặt triển khai mã nguồn:
- Kích hoạt xác thực: Cần lưu trữ thông tin về token và service.
- Kiểm tra hiệu lực: Chỉ cần kiểm tra xem thông tin token-service đã được lưu trữ hay chưa.
- Lấy thời gian còn lại: Lấy thời gian hết hạn của dữ liệu đã lưu trữ.
- Hủy xác thực: Xóa thủ công thông tin token-service. Thường được gọi khi người dùng đăng xuất.
Các thao tác lưu trữ và xóa sẽ được thực hiện thông qua một Adapter (hoặc Storage) để đảm bảo tính linh hoạt.
Triển khai mã nguồn
Dựa trên ý tưởng trên, việc triển khai sẽ tập trung vào các thao tác lưu trữ, kiểm tra sự tồn tại và xóa dữ liệu.
Kích hoạt xác thực 2FA
Trước khi kích hoạt, cần kiểm tra xem người dùng đã đăng nhập thành công hay chưa. Sau đó, lưu trữ thông tin token và service vào bộ nhớ, ghi log, và cung cấp một điểm mở rộng (extension point) thông qua watcher.
func (m *AuthManager) Enable2FA(userToken string, action string, duration int64) error {
if duration == 0 {
return nil // Không thiết lập thời gian, bỏ qua
}
// Kiểm tra xem người dùng đã đăng nhập chưa
if !m.IsLoggedIn(userToken) {
return errors.New("user is not logged in")
}
// Lưu trữ thông tin xác thực 2FA
key := m.generate2FAKey(userToken, action)
err := m.storage.Set(key, "active", duration)
if err != nil {
return err
}
// Gọi watcher để mở rộng chức năng
if m.observer != nil {
m.observer.On2FAEnabled(m.authType, userToken, action, duration)
}
return nil
}
Kiểm tra xem xác thực 2FA có đang hiệu lực
Kiểm tra xem thông tin xác thực 2FA cho một token và service cụ thể có tồn tại trong bộ nhớ hay không.
func (m *AuthManager) Is2FAActive(userToken string, action string) bool {
if userToken == "" {
return false
}
key := m.generate2FAKey(userToken, action)
value := m.storage.Get(key)
return value != ""
}
Lấy thời gian còn lại của xác thực 2FA
func (m *AuthManager) Get2FAExpiry(userToken string, action string) int64 {
if userToken == "" {
return 0
}
key := m.generate2FAKey(userToken, action)
return m.storage.GetExpiry(key)
}
Hủy xác thực 2FA
Xóa thông tin xác thực 2FA khỏi bộ nhớ, thường được gọi khi người dùng đăng xuất.
func (m *AuthManager) Disable2FA(userToken string, action string) error {
if userToken == "" {
return nil
}
key := m.generate2FAKey(userToken, action)
err := m.storage.Delete(key)
if err != nil {
return err
}
// Gọi watcher để mở rộng chức năng
if m.observer != nil {
m.observer.On2FADisabled(m.authType, userToken, action)
}
return nil
}
Viết test
Các bài test giúp đảm bảo logic hoạt động chính xác.
func TestAuthManager_2FA(t *testing.T) {
err, authMgr, _ := NewTestAuthManager(t)
if err != nil {
t.Fatalf("Không thể khởi tạo AuthManager: %v", err)
}
// Giả lập đăng nhập
sessionToken, err := authMgr.Login("user123")
if err != nil {
t.Fatalf("Đăng nhập thất bại: %v", err)
}
testAction := "transaction"
// Kích hoạt 2FA cho 10 phút (600 giây)
err = authMgr.Enable2FA(sessionToken, testAction, 600)
if err != nil {
t.Fatalf("Không thể kích hoạt 2FA: %v", err)
}
// Kiểm tra xem 2FA đã được kích hoạt
if !authMgr.Is2FAActive(sessionToken, testAction) {
t.Fatalf("2FA không được kích hoạt như mong đợi")
}
// Lấy thời gian hết hạn
expiry := authMgr.Get2FAExpiry(sessionToken, testAction)
t.Logf("Thời gian hết hạn của 2FA: %d giây", expiry)
// Hủy 2FA
err = authMgr.Disable2FA(sessionToken, testAction)
if err != nil {
t.Fatalf("Không thể hủy 2FA: %v", err)
}
// Kiểm tra xem 2FA đã bị hủy
if authMgr.Is2FAActive(sessionToken, testAction) {
t.Fatalf("2FA vẫn còn hiệu lực sau khi hủy")
}
}