Files
2025-12-03 11:00:47 +08:00

167 lines
4.0 KiB
Go
Raw Permalink 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 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"
"github.com/spf13/viper"
)
// jwtSecret JWT签名密钥从配置文件读取
var jwtSecret []byte
/**
* init
*
* 功能初始化JWT签名密钥
*
* 步骤:
* 1. 从配置文件读取JWT密钥
* 2. 如果配置文件中没有,使用默认密钥(仅用于开发环境)
* 3. 将密钥转换为字节数组存储
*
* 注意:生产环境必须使用配置文件中的强随机密钥
*/
func init() {
secret := viper.GetString("jwt.secret")
if secret == "" {
secret = "xk-websocket-secret-key-2025" // 默认密钥,生产环境应使用配置
}
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
}