166 lines
4.3 KiB
Go
166 lines
4.3 KiB
Go
/**
|
||
* package utils
|
||
*
|
||
* JWT Token生成和验证工具包
|
||
*
|
||
* 功能概述:
|
||
* 1. 生成JWT Token(包含用户ID和过期时间)
|
||
* 2. 解析JWT Token(验证签名和过期时间)
|
||
* 3. 验证Token有效性(提取用户ID)
|
||
*
|
||
* 使用场景:
|
||
* - 用户登录后生成Token
|
||
* - API请求时验证Token
|
||
* - 从Token中提取用户信息
|
||
*/
|
||
package utils
|
||
|
||
import (
|
||
"errors"
|
||
"time"
|
||
|
||
"github.com/golang-jwt/jwt/v5"
|
||
)
|
||
|
||
// jwtSecret JWT签名密钥
|
||
// 说明:默认赋一个本地开发用的弱密钥兜底,避免未初始化时为 nil;
|
||
// 生产环境必须通过 InitJWT 从配置/环境变量注入强随机密钥覆盖。
|
||
var jwtSecret = []byte("xk-websocket-secret-key-2025")
|
||
|
||
/**
|
||
* InitJWT
|
||
*
|
||
* 功能:初始化(覆盖)JWT签名密钥
|
||
*
|
||
* 为什么这样写:
|
||
* 原实现用包级 init() 读取 viper 配置,但 init() 会早于 main 中的 initConfig()(viper.ReadInConfig)执行,
|
||
* 导致读到空串而永远落到默认密钥,配置文件里的 jwt.secret 从未生效。
|
||
* 因此改为显式初始化函数,由 main 在配置加载完成后调用,确保真正使用配置/环境变量中的密钥。
|
||
*
|
||
* @param secret 签名密钥(为空时保留默认弱密钥,仅适用于本地开发)
|
||
*/
|
||
func InitJWT(secret string) {
|
||
if secret == "" {
|
||
return // 保留默认密钥,避免线上误清空导致签名不一致
|
||
}
|
||
jwtSecret = []byte(secret)
|
||
}
|
||
|
||
/**
|
||
* Claims
|
||
*
|
||
* JWT Token的载荷结构
|
||
*
|
||
* 字段说明:
|
||
* - UserID: 用户ID(业务数据)
|
||
* - RegisteredClaims: JWT标准声明(过期时间、签发时间等)
|
||
*/
|
||
type Claims struct {
|
||
UserID string `json:"user_id"` // 用户ID
|
||
jwt.RegisteredClaims // JWT标准声明
|
||
}
|
||
|
||
/**
|
||
* GenerateToken
|
||
*
|
||
* 功能:生成JWT Token
|
||
*
|
||
* 步骤:
|
||
* 1. 设置Token过期时间(默认7天)
|
||
* 2. 创建Claims对象,包含用户ID和标准声明
|
||
* 3. 使用HS256算法签名Token
|
||
* 4. 返回Token字符串
|
||
*
|
||
* @param userID 用户ID
|
||
* @returns token字符串和错误
|
||
*/
|
||
func GenerateToken(userID string) (string, error) {
|
||
// 步骤1: 设置Token过期时间(7天后过期)
|
||
expirationTime := time.Now().Add(7 * 24 * time.Hour)
|
||
|
||
// 步骤2: 创建Claims对象,包含用户ID和标准声明
|
||
claims := &Claims{
|
||
UserID: userID, // 业务数据:用户ID
|
||
RegisteredClaims: jwt.RegisteredClaims{
|
||
ExpiresAt: jwt.NewNumericDate(expirationTime), // 过期时间
|
||
IssuedAt: jwt.NewNumericDate(time.Now()), // 签发时间
|
||
NotBefore: jwt.NewNumericDate(time.Now()), // 生效时间(立即生效)
|
||
},
|
||
}
|
||
|
||
// 步骤3: 使用HS256算法创建Token并签名
|
||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||
tokenString, err := token.SignedString(jwtSecret)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// 步骤4: 返回Token字符串
|
||
return tokenString, nil
|
||
}
|
||
|
||
/**
|
||
* ParseToken
|
||
*
|
||
* 功能:解析JWT Token
|
||
*
|
||
* 步骤:
|
||
* 1. 创建空的Claims对象
|
||
* 2. 使用密钥解析Token并验证签名
|
||
* 3. 检查Token是否有效(签名正确、未过期)
|
||
* 4. 返回Claims对象
|
||
*
|
||
* @param tokenString token字符串
|
||
* @returns Claims和错误
|
||
*/
|
||
func ParseToken(tokenString string) (*Claims, error) {
|
||
// 步骤1: 创建空的Claims对象
|
||
claims := &Claims{}
|
||
|
||
// 步骤2: 解析Token并验证签名
|
||
// 使用密钥验证Token的签名是否有效
|
||
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||
return jwtSecret, nil // 返回签名密钥
|
||
})
|
||
|
||
if err != nil {
|
||
return nil, err // 解析失败(签名错误、格式错误等)
|
||
}
|
||
|
||
// 步骤3: 检查Token是否有效(签名正确、未过期)
|
||
if !token.Valid {
|
||
return nil, errors.New("invalid token")
|
||
}
|
||
|
||
// 步骤4: 返回解析后的Claims对象
|
||
return claims, nil
|
||
}
|
||
|
||
/**
|
||
* ValidateToken
|
||
*
|
||
* 功能:验证Token有效性并提取用户ID
|
||
*
|
||
* 步骤:
|
||
* 1. 调用ParseToken解析Token
|
||
* 2. 如果解析成功,从Claims中提取用户ID
|
||
* 3. 返回用户ID
|
||
*
|
||
* 使用场景:
|
||
* - 中间件中验证Token
|
||
* - API处理器中获取当前用户ID
|
||
*
|
||
* @param tokenString token字符串
|
||
* @returns 用户ID和错误
|
||
*/
|
||
func ValidateToken(tokenString string) (string, error) {
|
||
// 步骤1: 解析Token
|
||
claims, err := ParseToken(tokenString)
|
||
if err != nil {
|
||
return "", err // Token无效或已过期
|
||
}
|
||
|
||
// 步骤2: 从Claims中提取用户ID
|
||
return claims.UserID, nil
|
||
}
|