### 基于 HMAC 的 API Key + Secret 鉴权实现 #### 1. 导入相关依赖 ```go package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "log" "net/http" "time" ) const apiKeyHeader = "X-API-KEY" const apiSignatureHeader = "X-API-SIGNATURE" const apiTimestampHeader = "X-API-TIMESTAMP" // 这些 API Key 和 Secret 应该存储在安全的存储中,例如数据库或配置文件 var validAPIKeys = map[string]string{ "your-api-key": "your-api-secret", } // 验证签名是否有效 func isValidSignature(apiKey, signature, timestamp string) bool { // 使用提供的 API Key 查找对应的 API Secret secret, ok := validAPIKeys[apiKey] if !ok { return false } // 计算签名,签名内容是 "apiKey + timestamp" message := apiKey + timestamp mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(message)) expectedSignature := hex.EncodeToString(mac.Sum(nil)) // 验证客户端提供的签名是否与预期签名匹配 return hmac.Equal([]byte(signature), []byte(expectedSignature)) } func apiKeyAuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { apiKey := r.Header.Get(apiKeyHeader) signature := r.Header.Get(apiSignatureHeader) timestamp := r.Header.Get(apiTimestampHeader) // 检查 API Key, 签名和时间戳是否存在 if apiKey == "" || signature == "" || timestamp == "" { http.Error(w, "API Key, Signature, and Timestamp required", http.StatusUnauthorized) return } // 校验请求的签名是否有效 if !isValidSignature(apiKey, signature, timestamp) { http.Error(w, "Invalid Signature", http.StatusForbidden) return } // 校验时间戳是否在合理范围内(防止重放攻击) reqTime, err := time.Parse(time.RFC3339, timestamp) if err != nil || time.Since(reqTime) > 5*time.Minute { http.Error(w, "Request too old or invalid timestamp", http.StatusForbidden) return } // 如果鉴权成功,继续处理请求 next.ServeHTTP(w, r) }) } func helloHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World!") } func main() { // 路由定义 mux := http.NewServeMux() // 在 API 处理器中使用中间件进行鉴权 mux.Handle("/", apiKeyAuthMiddleware(http.HandlerFunc(helloHandler))) // 启动服务器 log.Println("Server starting on :8080") log.Fatal(http.ListenAndServe(":8080", mux)) } ``` #### 2. 代码详解 ##### 1. **HMAC 签名验证** - 使用 `HMAC-SHA256` 算法来对请求进行签名。服务器和客户端都需要拥有一对 **API Key** 和 **API Secret**。客户端用它们生成签名,服务端用相同的算法生成签名来进行比对。 - 具体流程如下: 1. 服务器通过 `apiKey` 在内存或数据库中查找对应的 `secret`。 2. 服务器生成一个字符串,这个字符串由 `apiKey` 和 `timestamp` 组成。 3. 使用 `HMAC-SHA256` 算法生成签名。 4. 将生成的签名与客户端提供的签名进行对比,若匹配则鉴权通过。 ##### 2. **防止重放攻击** - 在请求头中加入一个时间戳 `X-API-TIMESTAMP`,服务器验证时间戳是否在当前时间的合理范围内(例如5分钟内)。这有助于防止旧请求被恶意重放。 ##### 3. **主函数** - 服务器的每个请求都会通过 `apiKeyAuthMiddleware` 中间件来进行 API Key、签名以及时间戳的校验。 - 如果签名和时间戳都有效,允许请求继续处理,否则返回 `401` 或 `403` 错误。 #### 3. 客户端请求的生成方法 客户端在发出请求时,需要生成以下内容: 1. **API Key**: 作为请求头的一部分发送,表示客户端的身份。 2. **时间戳**: 使用 RFC3339 格式化的当前时间(例如 `"2024-09-20T12:00:00Z"`)。 3. **签名**: 使用 HMAC-SHA256 算法生成的签名,签名的内容是 `apiKey + timestamp` 的组合,密钥为该客户端的 API Secret。 以下是客户端伪代码示例(可以用任意语言实现): ```go import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "net/http" "time" ) func generateSignature(apiKey, apiSecret, timestamp string) string { message := apiKey + timestamp mac := hmac.New(sha256.New, []byte(apiSecret)) mac.Write([]byte(message)) return hex.EncodeToString(mac.Sum(nil)) } func makeRequest() { apiKey := "your-api-key" apiSecret := "your-api-secret" timestamp := time.Now().UTC().Format(time.RFC3339) signature := generateSignature(apiKey, apiSecret, timestamp) req, _ := http.NewRequest("GET", "http://localhost:8080", nil) req.Header.Set("X-API-KEY", apiKey) req.Header.Set("X-API-TIMESTAMP", timestamp) req.Header.Set("X-API-SIGNATURE", signature) client := &http.Client{} res, _ := client.Do(req) defer res.Body.Close() // 处理响应 } ``` #### 4. 测试流程 在服务启动后,你可以通过命令行或代码发出请求,携带 API Key、时间戳、签名等。 ```bash curl -H "X-API-KEY: your-api-key" \ -H "X-API-TIMESTAMP: 2024-09-20T12:00:00Z" \ -H "X-API-SIGNATURE: <计算出的签名>" \ http://localhost:8080/ ``` 如果鉴权成功,返回 `"Hello, World!"`;否则会返回 `401 Unauthorized` 或 `403 Forbidden` 错误。 ### 总结 通过使用 HMAC 签名,你可以大大提升 API 鉴权的安全性,并防止重放攻击。