live-sfu-demo/internal/auth/jwt.go

142 lines
3.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 auth
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
)
type Claims struct {
Username string `json:"username"`
Role string `json:"role"`
TokenType string `json:"token_type,omitempty"` // access / refresh,兼容 SyncTV 双令牌模型
jwt.RegisteredClaims
}
type JWTManager struct {
secret []byte
ttl time.Duration
refreshTTL time.Duration
issuer string
}
func NewJWTManager(secret string, ttl time.Duration) *JWTManager {
if secret == "" {
secret = "insecure-jwt-secret-change-me"
}
if ttl == 0 {
ttl = 2 * time.Hour
}
return &JWTManager{secret: []byte(secret), ttl: ttl, refreshTTL: 7 * 24 * time.Hour, issuer: "sync-live"}
}
func NewJWTManagerWithRefresh(secret string, ttl, refreshTTL time.Duration) *JWTManager {
if secret == "" {
secret = "insecure-jwt-secret-change-me"
}
if ttl == 0 {
ttl = 2 * time.Hour
}
if refreshTTL == 0 {
refreshTTL = 7 * 24 * time.Hour
}
return &JWTManager{secret: []byte(secret), ttl: ttl, refreshTTL: refreshTTL, issuer: "sync-live"}
}
func (j *JWTManager) Sign(username, role string) (string, error) {
now := time.Now()
claims := Claims{
Username: username,
Role: role,
TokenType: "access",
RegisteredClaims: jwt.RegisteredClaims{
Issuer: j.issuer,
Subject: username,
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(j.ttl)),
NotBefore: jwt.NewNumericDate(now),
ID: username + "-access-" + now.Format(time.RFC3339Nano),
},
}
tok := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return tok.SignedString(j.secret)
}
func (j *JWTManager) SignRefresh(username, role string) (string, error) {
now := time.Now()
claims := Claims{
Username: username,
Role: role,
TokenType: "refresh",
RegisteredClaims: jwt.RegisteredClaims{
Issuer: j.issuer,
Subject: username,
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(j.refreshTTL)),
NotBefore: jwt.NewNumericDate(now),
ID: username + "-refresh-" + now.Format(time.RFC3339Nano),
},
}
tok := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return tok.SignedString(j.secret)
}
func (j *JWTManager) Verify(tokenStr string) (*Claims, error) {
if tokenStr == "" {
return nil, errors.New("empty token")
}
tok, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (interface{}, error) {
if t.Method != jwt.SigningMethodHS256 {
return nil, errors.New("unexpected signing method")
}
return j.secret, nil
})
if err != nil {
return nil, err
}
claims, ok := tok.Claims.(*Claims)
if !ok || !tok.Valid {
return nil, errors.New("invalid token")
}
return claims, nil
}
func (j *JWTManager) VerifyRefresh(tokenStr string) (*Claims, error) {
claims, err := j.Verify(tokenStr)
if err != nil {
return nil, err
}
if claims.TokenType != "" && claims.TokenType != "refresh" {
return nil, errors.New("not a refresh token")
}
return claims, nil
}
func (j *JWTManager) Refresh(refreshToken string) (string, *Claims, error) {
claims, err := j.VerifyRefresh(refreshToken)
if err != nil {
return "", nil, err
}
newAccess, err := j.Sign(claims.Username, claims.Role)
if err != nil {
return "", nil, err
}
newClaims, _ := j.Verify(newAccess)
return newAccess, newClaims, nil
}
func (j *JWTManager) TTL() time.Duration { return j.ttl }
func (j *JWTManager) RefreshTTL() time.Duration { return j.refreshTTL }
func (j *JWTManager) IssuePair(username, role string) (access, refresh string, err error) {
access, err = j.Sign(username, role)
if err != nil {
return "", "", err
}
refresh, err = j.SignRefresh(username, role)
if err != nil {
return "", "", err
}
return access, refresh, nil
}