Files
MAcode/tcs-iptv/internal/httpx/auth.go
T
selfrelease a329d4906b init: AIGC-Hub/AVCC 方案文档 + TCS-IPTV 内容可信锁定系统 MVP
- 方案文档: AVCC 体系建设、IPTV TCS 需求(0-req)/PRD(1-prd)/任务(2-task)/二三四期任务
- tcs-iptv: Go 后端(哈希SDK/MA码生成/可信数据空间mock/业务编排/HTTP API+HMAC鉴权)
- web-console: React+AntD 监管大屏(角色工作台/全流程演示/监管片库)
- 一剧一码+集级哈希, 集级下架/恢复, 全栈测试通过
2026-06-14 16:50:31 +08:00

104 lines
2.6 KiB
Go

package httpx
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"strings"
"github.com/gin-gonic/gin"
)
// 鉴权采用 API Key + HMAC-SHA256(需求17-AC4、需求20)。
// 请求头:
// Authorization: TCS {apiKey}:{signature}
// X-TCS-Role: regulator | reviewer | cp | operator
// signature = base64(HMAC-SHA256(apiSecret, method+"\n"+path))
// KeyStore 提供 apiKey -> (apiSecret, role) 的查询。MVP 用内存实现。
type KeyStore interface {
Lookup(apiKey string) (secret string, role string, ok bool)
}
// MemoryKeyStore 内存密钥库。
type MemoryKeyStore struct {
keys map[string]struct {
secret string
role string
}
}
// NewMemoryKeyStore 创建并预置密钥。
func NewMemoryKeyStore() *MemoryKeyStore {
return &MemoryKeyStore{keys: map[string]struct {
secret string
role string
}{}}
}
// Add 注册一个密钥及其角色。
func (m *MemoryKeyStore) Add(apiKey, secret, role string) {
m.keys[apiKey] = struct {
secret string
role string
}{secret, role}
}
// Lookup 查询密钥。
func (m *MemoryKeyStore) Lookup(apiKey string) (string, string, bool) {
v, ok := m.keys[apiKey]
return v.secret, v.role, ok
}
// Sign 计算签名(供客户端/测试使用)。
func Sign(secret, method, path string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(method + "\n" + path))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
// 上下文键。
const ctxRoleKey = "tcs_role"
// AuthMiddleware 校验 HMAC 签名,将角色写入上下文(需求14 权限基础)。
func AuthMiddleware(store KeyStore) gin.HandlerFunc {
return func(c *gin.Context) {
authz := c.GetHeader("Authorization")
if !strings.HasPrefix(authz, "TCS ") {
Error(c, 401, "UNAUTHORIZED", "缺少或非法 Authorization 头")
c.Abort()
return
}
parts := strings.SplitN(strings.TrimPrefix(authz, "TCS "), ":", 2)
if len(parts) != 2 {
Error(c, 401, "UNAUTHORIZED", "Authorization 格式应为 TCS {apiKey}:{signature}")
c.Abort()
return
}
apiKey, sig := parts[0], parts[1]
secret, role, ok := store.Lookup(apiKey)
if !ok {
Error(c, 401, "UNAUTHORIZED", "未知 API Key")
c.Abort()
return
}
expected := Sign(secret, c.Request.Method, c.Request.URL.Path)
if !hmac.Equal([]byte(expected), []byte(sig)) {
Error(c, 401, "UNAUTHORIZED", "签名校验失败")
c.Abort()
return
}
c.Set(ctxRoleKey, role)
c.Next()
}
}
// RoleFromContext 取出鉴权后的角色。
func RoleFromContext(c *gin.Context) string {
if v, ok := c.Get(ctxRoleKey); ok {
return fmt.Sprintf("%v", v)
}
return ""
}