feat: 实现 TokenManager(JWT 签发/校验/哈希)

This commit is contained in:
hhs
2026-06-14 17:07:12 +08:00
parent 5e83531d54
commit 20597101f5
3 changed files with 114 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
package auth
import (
"crypto/sha256"
"encoding/hex"
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
// 自定义错误。
var (
ErrInvalidToken = errors.New("invalid or expired token")
)
// Claims JWT 声明。
type Claims struct {
UserID string `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}
// TokenManager JWT 令牌管理器。
type TokenManager struct {
secret []byte
accessTTL time.Duration
refreshTTL time.Duration
}
// NewTokenManager 创建 TokenManager。
// secret: JWT 签名密钥accessTTL/refreshTTL: 令牌有效期。
func NewTokenManager(secret string, accessTTL, refreshTTL time.Duration) *TokenManager {
return &TokenManager{
secret: []byte(secret),
accessTTL: accessTTL,
refreshTTL: refreshTTL,
}
}
// GeneratePair 生成 access + refresh 令牌对。
func (tm *TokenManager) GeneratePair(userID, username string) (access, refresh string, err error) {
now := time.Now()
// access token
accessClaims := &Claims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(tm.accessTTL)),
IssuedAt: jwt.NewNumericDate(now),
Issuer: "camtalk",
},
}
accessTkn := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
access, err = accessTkn.SignedString(tm.secret)
if err != nil {
return "", "", err
}
// refresh token含唯一 token_id 用于 DB 关联)
tokenID := uuid.New().String()
refreshClaims := &Claims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ID: tokenID,
ExpiresAt: jwt.NewNumericDate(now.Add(tm.refreshTTL)),
IssuedAt: jwt.NewNumericDate(now),
Issuer: "camtalk",
},
}
refreshTkn := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
refresh, err = refreshTkn.SignedString(tm.secret)
return
}
// ValidateAccess 校验 access token 并返回 Claims。
func (tm *TokenManager) ValidateAccess(tokenStr string) (*Claims, error) {
return tm.validate(tokenStr)
}
// ValidateRefresh 校验 refresh token 并返回 Claims。
func (tm *TokenManager) ValidateRefresh(tokenStr string) (*Claims, error) {
return tm.validate(tokenStr)
}
// validate 解析并校验 JWT。
func (tm *TokenManager) validate(tokenStr string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, ErrInvalidToken
}
return tm.secret, nil
})
if err != nil {
return nil, ErrInvalidToken
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, ErrInvalidToken
}
return claims, nil
}
// HashToken 对 token 做 SHA256 哈希,用于 DB 存储。
func HashToken(token string) string {
h := sha256.Sum256([]byte(token))
return hex.EncodeToString(h[:])
}