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))
}
HMAC-SHA256
算法来对请求进行签名。服务器和客户端都需要拥有一对 API Key 和 API Secret。客户端用它们生成签名,服务端用相同的算法生成签名来进行比对。apiKey
在内存或数据库中查找对应的 secret
。apiKey
和 timestamp
组成。HMAC-SHA256
算法生成签名。X-API-TIMESTAMP
,服务器验证时间戳是否在当前时间的合理范围内(例如5分钟内)。这有助于防止旧请求被恶意重放。apiKeyAuthMiddleware
中间件来进行 API Key、签名以及时间戳的校验。401
或 403
错误。客户端在发出请求时,需要生成以下内容:
"2024-09-20T12:00:00Z"
)。apiKey + timestamp
的组合,密钥为该客户端的 API Secret。以下是客户端伪代码示例(可以用任意语言实现):
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()
// 处理响应
}
在服务启动后,你可以通过命令行或代码发出请求,携带 API Key、时间戳、签名等。
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 鉴权的安全性,并防止重放攻击。