backend/pkg/library/centrifugo/token.go

204 lines
5.7 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 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,
})
}