109 lines
2.8 KiB
Go
109 lines
2.8 KiB
Go
|
|
package auth
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"crypto/rsa"
|
|||
|
|
"errors"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"github.com/golang-jwt/jwt/v5"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// YggdrasilJWTService Yggdrasil JWT服务(使用RSA512)
|
|||
|
|
type YggdrasilJWTService struct {
|
|||
|
|
privateKey *rsa.PrivateKey
|
|||
|
|
publicKey *rsa.PublicKey
|
|||
|
|
issuer string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewYggdrasilJWTService 创建新的Yggdrasil JWT服务
|
|||
|
|
func NewYggdrasilJWTService(privateKey *rsa.PrivateKey, issuer string) *YggdrasilJWTService {
|
|||
|
|
if issuer == "" {
|
|||
|
|
issuer = "carrotskin"
|
|||
|
|
}
|
|||
|
|
return &YggdrasilJWTService{
|
|||
|
|
privateKey: privateKey,
|
|||
|
|
publicKey: &privateKey.PublicKey,
|
|||
|
|
issuer: issuer,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// YggdrasilTokenClaims Yggdrasil Token声明
|
|||
|
|
type YggdrasilTokenClaims struct {
|
|||
|
|
Version int `json:"version"` // 版本号,用于失效旧Token
|
|||
|
|
UserID int64 `json:"user_id"` // 用户ID
|
|||
|
|
ProfileID string `json:"profile_id,omitempty"` // 选中的Profile UUID
|
|||
|
|
jwt.RegisteredClaims
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// StaleTokenPolicy Token过期策略
|
|||
|
|
type StaleTokenPolicy int
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
StalePolicyAllow StaleTokenPolicy = iota // 允许过期的Token(但未过StaleAt)
|
|||
|
|
StalePolicyDeny // 拒绝过期的Token
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// GenerateAccessToken 生成AccessToken JWT
|
|||
|
|
func (j *YggdrasilJWTService) GenerateAccessToken(
|
|||
|
|
userID int64,
|
|||
|
|
clientUUID string,
|
|||
|
|
version int,
|
|||
|
|
profileID string,
|
|||
|
|
expiresAt time.Time,
|
|||
|
|
staleAt time.Time,
|
|||
|
|
) (string, error) {
|
|||
|
|
claims := YggdrasilTokenClaims{
|
|||
|
|
Version: version,
|
|||
|
|
UserID: userID,
|
|||
|
|
ProfileID: profileID,
|
|||
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|||
|
|
Subject: clientUUID,
|
|||
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|||
|
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
|||
|
|
NotBefore: jwt.NewNumericDate(time.Now()),
|
|||
|
|
Issuer: j.issuer,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
|
|||
|
|
return token.SignedString(j.privateKey)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ParseAccessToken 解析AccessToken JWT
|
|||
|
|
func (j *YggdrasilJWTService) ParseAccessToken(accessToken string, stalePolicy StaleTokenPolicy) (*YggdrasilTokenClaims, error) {
|
|||
|
|
token, err := jwt.ParseWithClaims(accessToken, &YggdrasilTokenClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|||
|
|
// 验证签名算法
|
|||
|
|
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
|||
|
|
return nil, errors.New("不支持的签名算法,需要使用RSA")
|
|||
|
|
}
|
|||
|
|
return j.publicKey, nil
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !token.Valid {
|
|||
|
|
return nil, errors.New("无效的token")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
claims, ok := token.Claims.(*YggdrasilTokenClaims)
|
|||
|
|
if !ok {
|
|||
|
|
return nil, errors.New("无法解析token声明")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查StaleAt(如果设置了拒绝过期策略)
|
|||
|
|
if stalePolicy == StalePolicyDeny && claims.ExpiresAt != nil {
|
|||
|
|
if time.Now().After(claims.ExpiresAt.Time) {
|
|||
|
|
return nil, errors.New("token已过期")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return claims, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetPublicKey 获取公钥
|
|||
|
|
func (j *YggdrasilJWTService) GetPublicKey() *rsa.PublicKey {
|
|||
|
|
return j.publicKey
|
|||
|
|
}
|