101 lines
3.1 KiB
Go
101 lines
3.1 KiB
Go
// 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 供 middleware.Auth 校验既有令牌。
|
||
// 将来接入登录时恢复:构造 jwtlib.MapClaims{"uid","username","email","exp"},
|
||
// 再用 jwtlib.NewWithClaims(jwtlib.SigningMethodHS256, claims).SignedString(m.secret) 签名即可。
|
||
|
||
// 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
|
||
}
|