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, }) }