### 基于 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 鉴权的安全性，并防止重放攻击。