Files
silk/server-go/internal/handler/video_auth.go
T
2026-08-14 00:07:52 +08:00

103 lines
3.0 KiB
Go

package handler
import (
"errors"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
const videoTokenTTL = 5 * time.Minute
var videoTokenSecret []byte
// SetVideoTokenSecret 设置视频短时令牌签名密钥。
func SetVideoTokenSecret(secret string) {
videoTokenSecret = []byte(secret)
}
// IssueVideoToken 签发绑定用户、资源类型和资源 ID 的短时视频令牌。
func IssueVideoToken(userID, resourceType, resourceID string, ttl time.Duration) (string, error) {
if len(videoTokenSecret) == 0 {
return "", errors.New("video token secret not configured")
}
now := time.Now()
claims := jwt.MapClaims{
"sub": userID,
"purpose": "video",
"rtype": resourceType,
"rid": resourceID,
"iat": now.Unix(),
"exp": now.Add(ttl).Unix(),
}
return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(videoTokenSecret)
}
// ValidateVideoToken 校验视频令牌的资源类型、资源 ID、用途和有效期。
func ValidateVideoToken(token, resourceType, resourceID string) error {
claims, err := parseVideoToken(token)
if err != nil {
return err
}
return validateVideoClaims(claims, resourceType, resourceID, "")
}
// ValidateVideoTokenForUser 额外校验令牌所属用户,用于可以拿到当前 JWT 的调用方。
func ValidateVideoTokenForUser(token, resourceType, resourceID, userID string) error {
claims, err := parseVideoToken(token)
if err != nil {
return err
}
return validateVideoClaims(claims, resourceType, resourceID, userID)
}
// requireVideoToken 校验流代理 URL 上的 videoToken,缺失返回 401,错误/过期/资源错配返回 403。
func requireVideoToken(c *gin.Context, resourceType, resourceID string) bool {
token := c.Query("videoToken")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing video token"})
return false
}
if err := ValidateVideoToken(token, resourceType, resourceID); err != nil {
c.JSON(http.StatusForbidden, gin.H{"error": "invalid video token"})
return false
}
return true
}
func parseVideoToken(token string) (jwt.MapClaims, error) {
if len(videoTokenSecret) == 0 {
return nil, errors.New("video token secret not configured")
}
claims := jwt.MapClaims{}
parsed, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) {
return videoTokenSecret, nil
}, jwt.WithValidMethods([]string{"HS256"}))
if err != nil {
return nil, err
}
if !parsed.Valid {
return nil, errors.New("invalid video token")
}
return claims, nil
}
func validateVideoClaims(claims jwt.MapClaims, resourceType, resourceID, userID string) error {
if claims["purpose"] != "video" {
return errors.New("invalid video token purpose")
}
if claims["rtype"] != resourceType {
return errors.New("video token resource type mismatch")
}
if claims["rid"] != resourceID {
return errors.New("video token resource mismatch")
}
if userID != "" && claims["sub"] != userID {
return errors.New("video token user mismatch")
}
return nil
}