| 名称 | 说明 |
|---|---|
| x-auth-access-key | 客户端的访问密钥 |
| x-auth-timestamp | 请求发起时的时间戳, 单位: 秒 |
| x-auth-signature | 请求的签名 |
| x-auth-nonce | 随机字符串, 用于防重放攻击 |
注意: 各字段按字符串升序排序后, 以英文逗号
,连接拼接成待签名字符串s; 因此 access key 中不得包含英文逗号,。
支持的签名算法:
HMAC-SHA256: HMAC with SHA-256HMAC-SHA384: HMAC with SHA-384HMAC-SHA512: HMAC with SHA-512
- 取出客户端访问密钥:
x-auth-access-key; - 取当前的时间戳:
x-auth-timestamp; - 取随机字符串:
x-auth-nonce(可选); - 将
x-auth-access-key,x-auth-timestamp,x-auth-nonce按字符串排序, 拼接成字符串s(各字段按字符串升序排序后, 用英文逗号,连接); - 取出客户端访问密钥对应的 secretKey, 对
s使用指定的 HMAC 算法计算签名, 并编码为 base64, 得到x-auth-signature;
支持的签名算法:
RSA-SHA256: RSA with SHA-256RSA-SHA384: RSA with SHA-384RSA-SHA512: RSA with SHA-512ECDSA-SHA256: ECDSA with SHA-256ECDSA-SHA384: ECDSA with SHA-384ECDSA-SHA512: ECDSA with SHA-512ED25519: Ed25519
- 取出客户端访问密钥:
x-auth-access-key; - 取当前的时间戳:
x-auth-timestamp; - 取随机字符串:
x-auth-nonce(可选); - 将
x-auth-access-key,x-auth-timestamp,x-auth-nonce按字符串排序, 拼接成字符串s(各字段按字符串升序排序后, 用英文逗号,连接); - 使用私钥对
s进行签名, 并编码为 base64, 得到x-auth-signature;
package main
import (
"net/http"
"github.com/qingtao/aksk/v2/core"
"github.com/qingtao/aksk/v2/request"
)
func main() {
// 创建签名修改器
modifier, err := request.NewModifier(request.Config{
Ak: "your-access-key",
Sk: "your-secret-key",
}, core.WithSignMethod(core.SignMethodHMACSHA256))
if err != nil {
panic(err)
}
// 创建请求
req, _ := http.NewRequest("GET", "http://example.com/api", nil)
// 添加签名头部
if err := modifier.ModifyRequest(req); err != nil {
panic(err)
}
// 发送请求
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}package main
import (
"net/http"
"github.com/qingtao/aksk/v2/core"
"github.com/qingtao/aksk/v2/middleware"
)
func main() {
config := middleware.Config{
KeyGetter: func(ak string) (string, error) {
// 根据 ak 查询 secret key
return "your-secret-key", nil
},
}
m, err := middleware.New(config,
core.WithSignMethod(core.SignMethodHMACSHA256),
)
if err != nil {
panic(err)
}
http.Handle("/api", m.HandleFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
}))
http.ListenAndServe(":8080", nil)
}package main
import (
"net/http"
"time"
"github.com/qingtao/aksk/v2/core"
"github.com/qingtao/aksk/v2/request"
)
func main() {
// 创建 NonceStore, 使用选项函数配置
store := request.NewMemoryStore(
request.WithMinLen(16),
request.WithMaxLen(64),
request.WithTTL(5*time.Minute),
)
// 客户端: 创建带 nonce 的签名修改器
modifier, _ := request.NewModifier(request.Config{
Ak: "your-access-key",
Sk: "your-secret-key",
NonceStore: store,
}, core.WithSignMethod(core.SignMethodHMACSHA256))
// 服务端: 创建带 nonce 校验的验证器
validator, _ := request.NewValidator(request.Config{
KeyGetter: func(ak string) (string, error) {
return "your-secret-key", nil
},
NonceStore: store,
}, core.WithSignMethod(core.SignMethodHMACSHA256))
_ = modifier
_ = validator
}MemoryStore 是基于进程内存 (fastcache) 的默认实现, 使用时有以下限制:
- 仅限单实例部署: 各实例的 nonce 缓存相互独立, 服务横向扩展 (多副本) 后
攻击者向不同副本重放即可绕过防重放。多实例场景请实现
NonceStore接口, 使用共享存储 (如 Redis) 保存 nonce。 - 防重放窗口受缓存容量影响:
freecache按最大条目数上限 + LRU 淘汰, 过期由 TTL 主动清理。 正常流量下 nonce 在 TTL 后自动失效; 但当写入达到上限时会淘汰最久未用条目, 极端高负载下仍可能提前删除尚未过期的 nonce, 缩短防重放窗口。 建议按流量规划CacheSize, 使其足以容纳一个 TTL 周期内的请求量。 - nonce TTL 须与 acceptableSkew 对齐: 防重放窗口(ttl)必须不小于请求时间戳有效窗口(acceptableSkew), 否则会出现"时间戳仍有效但 nonce 已过期"的空窗, 可被重放。在
request.Config中设置AcceptableSkew即可自动注入core.WithAcceptableSkew并同步给MemoryStore的 TTL, 无需手动对齐两者。
客户端重试请使用新的 nonce; 若复用同一 nonce, 在 TTL 内会被判定为
nonce already used。
实现 NonceStore 接口即可使用自定义存储 (如 Redis):
type NonceStore interface {
GenerateNonce() (string, error)
Check(ak, nonce string) error
}| 配置项 | 默认值 | 说明 |
|---|---|---|
| MinLen | 16 | nonce 最小长度 |
| MaxLen | 64 | nonce 最大长度 |
| TTL | 60 秒 | nonce 过期时间, 应与 acceptableSkew 保持一致 |
| CacheSize | 1,048,576 | nonce 缓存最大条目数(硬上限, 防溢出) |