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 "" }