103 lines
3.0 KiB
Go
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
|
|
}
|