Files
backend_v2/internal/pkg/jwt/jwt.go
toom1996 dbee704c95 update
2026-09-13 00:52:35 +08:00

96 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package jwt 封装 JWT 令牌的签发与解析。
//
// Claims 结构(uid / username / email / exp)与签名算法(HS256)保持与原项目一致,
// 因此原后端签发的 token 在本服务中依然有效(相同 secret 前提下)。
package jwt
import (
"errors"
"time"
jwtlib "github.com/golang-jwt/jwt/v5"
)
// ErrInvalidToken 表示令牌缺失、过期或签名不合法。
var ErrInvalidToken = errors.New("invalid token")
// Claims 从令牌中解析出的用户身份信息。
type Claims struct {
UserID uint32
Username string
Email string
// Tier 用户等级 free/vip;详情页据其拼装图片 URL(普通/高清)。
Tier string
}
// Manager 令牌签发器。通过构造函数注入密钥与有效期,避免每次调用都重新读取配置
// (原项目在 genToken/中间件里各自调用 config.Load(),属于重复解析)。
type Manager struct {
secret []byte
expire time.Duration
}
// NewManager 创建令牌签发器。expireHours <= 0 时回落为 7 天。
func NewManager(secret string, expireHours int) *Manager {
if expireHours <= 0 {
expireHours = 168
}
return &Manager{
secret: []byte(secret),
expire: time.Duration(expireHours) * time.Hour,
}
}
// Generate 签发令牌(登录成功后调用)。
//
// 构造与 Parse 对称的 Claims(uid / username / email / tier / exp),用 HS256 签名。
// exp 由 Manager 的 expire 决定(默认 7 天,见 NewManager)。
func (m *Manager) Generate(userID uint32, username, email, tier string) (string, error) {
claims := jwtlib.MapClaims{
"uid": float64(userID),
"username": username,
"email": email,
"tier": tier,
"exp": time.Now().Add(m.expire).Unix(),
}
token := jwtlib.NewWithClaims(jwtlib.SigningMethodHS256, claims)
return token.SignedString(m.secret)
}
// Parse 校验并解析令牌。
func (m *Manager) Parse(tokenStr string) (*Claims, error) {
token, err := jwtlib.Parse(tokenStr, func(t *jwtlib.Token) (any, error) {
// 只接受 HMAC 签名,防止 alg 混淆攻击
if _, ok := t.Method.(*jwtlib.SigningMethodHMAC); !ok {
return nil, jwtlib.ErrSignatureInvalid
}
return m.secret, nil
})
if err != nil || !token.Valid {
return nil, ErrInvalidToken
}
raw, ok := token.Claims.(jwtlib.MapClaims)
if !ok {
return nil, ErrInvalidToken
}
c := &Claims{}
// JSON 数字统一解析为 float64
if uid, ok := raw["uid"].(float64); ok {
c.UserID = uint32(uid)
}
if v, ok := raw["username"].(string); ok {
c.Username = v
}
if v, ok := raw["email"].(string); ok {
c.Email = v
}
// tier 缺省回落 free(兼容未带 tier 的旧令牌)
if v, ok := raw["tier"].(string); ok && v != "" {
c.Tier = v
} else {
c.Tier = "free"
}
return c, nil
}