backend/pkg/library/centrifugo/token.go

204 lines
5.7 KiB
Go
Raw Normal View History

2026-01-06 07:15:18 +00:00
package centrifugo
import (
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
// TokenConfig JWT Token 配置
type TokenConfig struct {
// Secret 用於簽名的密鑰(與 Centrifugo 配置的 token_hmac_secret_key 一致)
Secret string
// ExpireIn Token 過期時間(預設 1 小時)
ExpireIn time.Duration
}
// TokenGenerator JWT Token 生成器
type TokenGenerator struct {
config TokenConfig
}
// NewTokenGenerator 創建新的 Token 生成器
func NewTokenGenerator(secret string) *TokenGenerator {
return &TokenGenerator{
config: TokenConfig{
Secret: secret,
ExpireIn: time.Hour,
},
}
}
// NewTokenGeneratorWithConfig 創建使用自定義配置的 Token 生成器
func NewTokenGeneratorWithConfig(config TokenConfig) *TokenGenerator {
if config.ExpireIn == 0 {
config.ExpireIn = time.Hour
}
return &TokenGenerator{
config: config,
}
}
// ConnectionClaims 連線 Token 的 Claims
type ConnectionClaims struct {
jwt.RegisteredClaims
// Sub 用戶 ID必填
Sub string `json:"sub"`
// Info 用戶資訊(可選,會在 presence 中顯示)
Info map[string]interface{} `json:"info,omitempty"`
// Channels 自動訂閱的頻道列表(可選)
Channels []string `json:"channels,omitempty"`
// TokenVersion Token 版本(用於批量撤銷)
TokenVersion int64 `json:"tv,omitempty"`
}
// SubscriptionClaims 訂閱 Token 的 Claims用於私有頻道
type SubscriptionClaims struct {
jwt.RegisteredClaims
// Sub 用戶 ID必填
Sub string `json:"sub"`
// Channel 頻道名稱(必填)
Channel string `json:"channel"`
// Info 頻道特定的用戶資訊(可選)
Info map[string]interface{} `json:"info,omitempty"`
// TokenVersion Token 版本(用於批量撤銷)
TokenVersion int64 `json:"tv,omitempty"`
}
// ConnectionTokenOptions 連線 Token 選項
type ConnectionTokenOptions struct {
// UserID 用戶 ID必填
UserID string
// Info 用戶資訊(可選)
Info map[string]interface{}
// Channels 自動訂閱的頻道列表(可選)
Channels []string
// ExpireAt 自定義過期時間(可選,為空則使用預設)
ExpireAt *time.Time
// TokenVersion Token 版本(可選,用於黑名單機制)
TokenVersion int64
}
// SubscriptionTokenOptions 訂閱 Token 選項
type SubscriptionTokenOptions struct {
// UserID 用戶 ID必填
UserID string
// Channel 頻道名稱(必填)
Channel string
// Info 頻道特定的用戶資訊(可選)
Info map[string]interface{}
// ExpireAt 自定義過期時間(可選,為空則使用預設)
ExpireAt *time.Time
// TokenVersion Token 版本(可選,用於黑名單機制)
TokenVersion int64
}
// TokenResult 生成 Token 的結果
type TokenResult struct {
Token string // JWT Token 字串
JTI string // JWT ID用於撤銷單一 Token
ExpiresAt time.Time // 過期時間
}
// GenerateConnectionToken 生成連線 Token
// 用於前端建立 WebSocket 連線時的身份驗證
func (g *TokenGenerator) GenerateConnectionToken(opts ConnectionTokenOptions) (string, error) {
result, err := g.GenerateConnectionTokenWithJTI(opts)
if err != nil {
return "", err
}
return result.Token, nil
}
// GenerateConnectionTokenWithJTI 生成連線 Token 並返回 JTI
// 用於需要支援單一 Token 撤銷的場景
func (g *TokenGenerator) GenerateConnectionTokenWithJTI(opts ConnectionTokenOptions) (*TokenResult, error) {
now := time.Now()
expireAt := now.Add(g.config.ExpireIn)
if opts.ExpireAt != nil {
expireAt = *opts.ExpireAt
}
// 生成唯一的 JTI
jti := uuid.New().String()
// 如果沒有指定 TokenVersion使用當前時間戳
tokenVersion := opts.TokenVersion
if tokenVersion == 0 {
tokenVersion = now.UnixNano()
}
claims := ConnectionClaims{
RegisteredClaims: jwt.RegisteredClaims{
ID: jti,
Subject: opts.UserID,
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(expireAt),
},
Sub: opts.UserID,
Info: opts.Info,
Channels: opts.Channels,
TokenVersion: tokenVersion,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenStr, err := token.SignedString([]byte(g.config.Secret))
if err != nil {
return nil, err
}
return &TokenResult{
Token: tokenStr,
JTI: jti,
ExpiresAt: expireAt,
}, nil
}
// GenerateSubscriptionToken 生成訂閱 Token
// 用於訂閱私有頻道時的身份驗證
func (g *TokenGenerator) GenerateSubscriptionToken(opts SubscriptionTokenOptions) (string, error) {
now := time.Now()
expireAt := now.Add(g.config.ExpireIn)
if opts.ExpireAt != nil {
expireAt = *opts.ExpireAt
}
claims := SubscriptionClaims{
RegisteredClaims: jwt.RegisteredClaims{
Subject: opts.UserID,
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(expireAt),
},
Sub: opts.UserID,
Channel: opts.Channel,
Info: opts.Info,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(g.config.Secret))
}
// GenerateAnonymousToken 生成匿名連線 Token
// 用於允許匿名用戶連線(但仍需要驗證)
func (g *TokenGenerator) GenerateAnonymousToken() (string, error) {
return g.GenerateConnectionToken(ConnectionTokenOptions{
UserID: "", // 空的 UserID 表示匿名用戶
})
}
// QuickConnectionToken 快速生成連線 Token只需用戶 ID
func (g *TokenGenerator) QuickConnectionToken(userID string) (string, error) {
return g.GenerateConnectionToken(ConnectionTokenOptions{
UserID: userID,
})
}
// QuickSubscriptionToken 快速生成訂閱 Token只需用戶 ID 和頻道)
func (g *TokenGenerator) QuickSubscriptionToken(userID, channel string) (string, error) {
return g.GenerateSubscriptionToken(SubscriptionTokenOptions{
UserID: userID,
Channel: channel,
})
}