You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/libs/jwt/jwt.go

155 lines
3.9 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

//
// Copyright (C) 2024 veypi <i@veypi.com>
// 2025-03-04 16:08:06
// Distributed under terms of the MIT license.
//
package jwt
import (
"errors"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/veypi/vbase/cfg"
)
var (
ErrInvalidToken = errors.New("invalid token")
ErrExpiredToken = errors.New("token expired")
ErrTokenRevoked = errors.New("token revoked")
)
// Claims JWT声明精简版仅存鉴权必需字段
type Claims struct {
jwt.RegisteredClaims
UserID string `json:"uid"`
Type string `json:"type"` // "access" / "refresh"
SessionID string `json:"sid"` // 归属的会话 ID
Version int64 `json:"ver"` // 会话内 token 版本号,用于撤销
}
// TokenPair Token对
type TokenPair struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
}
// GenerateTokenPair 生成token对
func GenerateTokenPair(userID, sessionID string, version int64) (*TokenPair, error) {
accessToken, err := GenerateAccessToken(userID, sessionID, version)
if err != nil {
return nil, err
}
refreshToken, err := GenerateRefreshToken(userID, sessionID, version)
if err != nil {
return nil, err
}
return &TokenPair{
AccessToken: accessToken,
RefreshToken: refreshToken,
TokenType: "Bearer",
ExpiresIn: int(cfg.Global.JWT.AccessExpiry.Seconds()),
}, nil
}
// GenerateAccessToken 生成访问令牌
func GenerateAccessToken(userID, sessionID string, version int64) (string, error) {
now := time.Now()
claims := Claims{
RegisteredClaims: jwt.RegisteredClaims{
ID: uuid.New().String(), // jti
Issuer: cfg.Global.JWT.Issuer,
Subject: userID,
Audience: jwt.ClaimStrings{"vbase"},
IssuedAt: jwt.NewNumericDate(now),
NotBefore: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(cfg.Global.JWT.AccessExpiry)),
},
UserID: userID,
Type: "access",
SessionID: sessionID,
Version: version,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(cfg.Global.JWT.Secret))
}
// GenerateRefreshToken 生成刷新令牌
func GenerateRefreshToken(userID, sessionID string, version int64) (string, error) {
now := time.Now()
claims := Claims{
RegisteredClaims: jwt.RegisteredClaims{
ID: uuid.New().String(),
Issuer: cfg.Global.JWT.Issuer,
Subject: userID,
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(cfg.Global.JWT.RefreshExpiry)),
},
UserID: userID,
Type: "refresh",
SessionID: sessionID,
Version: version,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(cfg.Global.JWT.Secret))
}
// ParseToken 解析Token
func ParseToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(cfg.Global.JWT.Secret), nil
})
if err != nil {
if errors.Is(err, jwt.ErrTokenExpired) {
return nil, ErrExpiredToken
}
return nil, ErrInvalidToken
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, ErrInvalidToken
}
// GetJTI 获取Token ID
func GetJTI(tokenString string) (string, error) {
claims, err := ParseToken(tokenString)
if err != nil {
return "", err
}
return claims.ID, nil
}
// IsAccessToken 是否是访问令牌
func IsAccessToken(claims *Claims) bool {
return claims.Type == "access"
}
// IsRefreshToken 是否是刷新令牌
func IsRefreshToken(claims *Claims) bool {
return claims.Type == "refresh"
}
// GetSecret 获取当前 JWT Secret空则回退 Key
func GetSecret() string {
s := cfg.Global.JWT.Secret
if s == "" {
s = string(cfg.Global.Key)
}
return s
}