Files
arenax-server/utility/jwt/jwt.go

120 lines
3.0 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
import (
"errors"
"server/utility/ecode"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
var (
// secretKey 是用于签名 JWT 的密钥。应替换为更加安全的密钥并妥善保管。
secretKey = []byte("1a40c1d5b7a1b0f0f835e0b24ca292a6")
// issuer 表示签发者信息,作为 JWT 标准字段的一部分。
issuer = "arenax.com"
)
type (
// TokenIn 表示生成 JWT 所需的输入参数。
//
// 字段:
// - UserId: 用户 ID
// - Role: 权限标识;
// - ExpireTime: token 过期时间(可选)。
TokenIn struct {
UserId int64 // 用户 ID
Role string // 权限标识
ExpireTime time.Duration // 令牌有效期
}
// TokenOut 表示从 JWT 中解析出的用户信息。
//
// 字段:
// - UserId: 用户 ID
// - Role: 权限标识;
// - JTI: JWT 的唯一标识。
TokenOut struct {
UserId int64 // 用户 ID
Role string // 权限标识
JTI string // JWT 唯一标识
}
// jwtClaims 自定义 JWT 的声明体结构,嵌入标准声明字段。
jwtClaims struct {
UserId int64 `json:"user_id"` // 用户 ID
Role string `json:"Role"` // 权限标识
JTI string `json:"jti"` // 唯一标识
jwt.RegisteredClaims
}
)
// GenerateToken 生成带有自定义权限和过期时间的 JWT 字符串。
//
// 参数:
// - in: 包含用户 ID、权限、过期时间等信息的 TokenIn 对象。
//
// 返回:
// - 生成的 JWT 字符串;
// - 若出错则返回错误信息。
func GenerateToken(in *TokenIn) (string, error) {
expire := in.ExpireTime
if expire <= 0 {
expire = 2 * time.Hour
}
claims := jwtClaims{
UserId: in.UserId,
Role: in.Role,
JTI: uuid.NewString(),
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expire)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: issuer,
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secretKey)
}
// ParseToken 解析并验证 JWT 字符串并返回对应的用户信息。
// 自动去除开头的 "Bearer " 前缀。
//
// 参数:
// - tokenString: 需要解析的 JWT 字符串。
//
// 返回:
// - 解析后的 TokenOut 对象;
// - 若失败则返回相应的错误信息。
func ParseToken(tokenString string) (*TokenOut, error) {
if strings.HasPrefix(tokenString, "Bearer ") {
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
}
token, err := jwt.ParseWithClaims(tokenString, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})
if err != nil {
if errors.Is(err, jwt.ErrTokenExpired) {
return nil, ecode.Expire.Sub("token 已过期")
}
return nil, ecode.Fail.Sub("解析 token 出现异常")
}
claims, ok := token.Claims.(*jwtClaims)
if !ok || !token.Valid {
return nil, ecode.InvalidOperation.Sub("无效的 Token")
}
return &TokenOut{
UserId: claims.UserId,
Role: claims.Role,
JTI: claims.JTI,
}, nil
}