在 Go 中實現 TOTP 認證:實踐指南
時間性一次性密碼(TOTP)已成爲現代應用中實現雙因素認證(2FA)的標準。在本指南中,我們將探討如何在 Go 中使用流行的
github.com/pquerna/otp 庫實現 TOTP。
什麼是 TOTP?
TOTP 生成臨時密碼,這些密碼在短時間內(通常是 30 秒)有效。這項技術是 Google Authenticator、Authy 等認證器應用背後的核心技術。TOTP 基於服務器和客戶端之間的共享密鑰,並結合當前時間生成同步的代碼。
實現指南
讓我們構建一個完整的 TOTP 實現,包括以下功能:
-
生成 TOTP 密鑰
-
創建二維碼以便用戶快速設置
-
驗證 TOTP 代碼
-
一個用戶註冊和驗證的實踐示例
設置項目
首先,讓我們獲取所需的依賴:
go mod init totp-example
go get github.com/pquerna/otp
核心實現
我們創建一個 TOTP 服務來處理所有的 2FA 需求:
package totp
import (
"encoding/base32"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
)
type TOTPService struct {
issuer string
}
func NewTOTPService(issuer string) *TOTPService {
return &TOTPService{
issuer: issuer,
}
}
// GenerateSecret 爲用戶創建一個新的 TOTP 密鑰
func (s *TOTPService) GenerateSecret(username string) (*otp.Key, error) {
key, err := totp.Generate(totp.GenerateOpts{
Issuer: s.issuer,
AccountName: username,
Algorithm: otp.AlgorithmSHA1,
Digits: 6,
Period: 30,
// 你可以手動生成密鑰並在這裏設置。如果未提供密鑰,庫會生成一個隨機密鑰。
// Secret: secret,
})
if err != nil {
return nil, err
}
return key, nil
}
// ValidateCode 驗證提供的代碼是否有效
func (s *TOTPService) ValidateCode(secret string, code string) bool {
valid := totp.Validate(code, secret)
return valid
}
實踐示例
以下是一個完整的示例,展示瞭如何將 TOTP 集成到用戶註冊和登錄流程中:
package main
import (
"fmt"
"log"
)
type User struct {
Username string
TOTPSecret string
TOTPEnabled bool
}
type UserService struct {
totpService *TOTPService
users map[string]*User // 實際應用中使用真正的數據庫
}
func NewUserService() *UserService {
return &UserService{
totpService: NewTOTPService("MyApp"),
users: make(map[string]*User),
}
}
// EnableTOTP 爲用戶設置 2FA
func (s *UserService) EnableTOTP(username string) (string, string, error) {
user, exists := s.users[username]
if !exists {
return"", "", fmt.Errorf("user not found")
}
key, err := s.totpService.GenerateSecret(username)
if err != nil {
return"", "", err
}
// 存儲密鑰
user.TOTPSecret = key.Secret()
// 返回密鑰和二維碼 URL
return key.Secret(), key.URL(), nil
}
// ValidateAndEnableTOTP 驗證並啓用 TOTP
func (s *UserService) ValidateAndEnableTOTP(username, code string) error {
user, exists := s.users[username]
if !exists {
return fmt.Errorf("user not found")
}
if s.totpService.ValidateCode(user.TOTPSecret, code) {
user.TOTPEnabled = true
return nil
}
return fmt.Errorf("invalid code")
}
// Login 在登錄時驗證用戶的 TOTP 代碼
func (s *UserService) Login(username, totpCode string) error {
user, exists := s.users[username]
if !exists {
return fmt.Errorf("user not found")
}
if user.TOTPEnabled {
if !s.totpService.ValidateCode(user.TOTPSecret, totpCode) {
return fmt.Errorf("invalid TOTP code")
}
}
return nil // 登錄成功
}
示例使用
以下是如何使用上述實現的示例:
func main() {
userService := NewUserService()
// 註冊一個新用戶
userService.users["john"] = &User{Username: "john"}
// 爲用戶啓用 TOTP
secret, qrURL, err := userService.EnableTOTP("john")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Secret: %s\n", secret)
fmt.Printf("QR Code URL: %s\n", qrURL)
// 用戶在認證器應用中掃描二維碼並輸入代碼
code := "123456" // 用戶輸入的代碼
err = userService.ValidateAndEnableTOTP("john", code)
if err != nil {
log.Fatal(err)
}
// 之後,在登錄時
loginCode := "654321" // 用戶輸入的登錄代碼
err = userService.Login("john", loginCode)
if err != nil {
log.Fatal(err)
}
}
最佳實踐和安全考慮
密鑰存儲
始終安全地存儲 TOTP 密鑰:
使用靜態加密存儲
對於大規模應用,考慮使用硬件安全模塊(HSM) 不要以明文形式記錄或暴露密鑰
時間同步
確保服務器的時間準確: 使用 NTP 保持服務器同步 考慮允許少量時間偏差(庫會自動處理)
備份代碼
實現備份代碼以便賬戶恢復:
func generateBackupCodes() []string {
codes := make([]string, 8)
for i := range codes {
// 生成隨機的 8 字符代碼
// 將這些代碼安全存儲
}
return codes
}
速率限制
爲 TOTP 驗證嘗試數實現速率限制:
func (s *TOTPService) ValidateCode(secret, code string) bool {
// 在驗證前檢查速率限制
if isRateLimited(secret) {
return false
}
return totp.Validate(code, secret)
}
常見陷阱
-
時間窗口 不要將驗證窗口設置得太大或太小。
-
密鑰長度 始終使用庫的生成函數,不要自己生成密鑰。
-
缺少恢復選項 始終提供備份代碼或恢復方法。
-
缺乏速率限制 實現速率限制以防止暴力破解攻擊。
結論
使用 pquerna/otp 庫在 Go 中實現 TOTP 是簡單且安全的。該庫處理了複雜的加密操作,開發者能夠專注於集成和安全考慮。
記住以下要點:
-
安全地存儲密鑰
-
實現適當的速率限制
-
提供恢復選項
-
保持服務器時間同步
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/f_FWbK8wJussMW807-cg3A