a329d4906b
- 方案文档: AVCC 体系建设、IPTV TCS 需求(0-req)/PRD(1-prd)/任务(2-task)/二三四期任务 - tcs-iptv: Go 后端(哈希SDK/MA码生成/可信数据空间mock/业务编排/HTTP API+HMAC鉴权) - web-console: React+AntD 监管大屏(角色工作台/全流程演示/监管片库) - 一剧一码+集级哈希, 集级下架/恢复, 全栈测试通过
104 lines
2.6 KiB
Go
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 ""
|
|
}
|