go接口签名核心是双方用同一规则算出相同签名值,需严格统一参数排序、url编码、hmac计算三步,且必须校验时间戳防重放。

Go 语言做接口签名,核心不是“加签”,而是“双方用同一套规则算出同一个签名值”——只要算法、参数顺序、编码方式、密钥处理完全一致,验证自然通过。别被“教程”带偏去抄封装好的库,先搞清这三步:参数标准化、签名字符串构造、HMAC 计算。
签名前必须统一参数排序和编码
后端收到 GET /api?ts=1715234000&uid=123&method=get_user&sign=xxx,但前端拼参时可能把 uid 放最前,或把空格 encode 成 %20 而不是 +,后端一解码就错。
- 所有参与签名的参数(除
sign外)必须按字典序升序排列,键名和值都用url.QueryEscape编码(不是strings.ReplaceAll手动替换) - 忽略空值参数(
""或nil),但零值数字(如0、0.0)要保留 - 时间戳
ts建议强制要求为秒级整数,避免前端传毫秒导致后端截断不一致
示例:参数 map[string]string{"uid": "u-123", "ts": "1715234000", "method": "get_user"} → 排序后拼成 "method=get_user&ts=1715234000&uid=u-123",再整体 URL 编码一次(注意:是整个字符串编码,不是单个 value)
用 hmac.New 算签名,别手写 base64 或 hex
常见错误是把 hmac.Sum256 的结果直接转 string,或用 fmt.Sprintf("%x", sum) —— 这会引入不可控的格式化空格/换行,且大小写不固定。
- 始终用
hmac.Sum的[:]获取原始字节切片,再传给base64.StdEncoding.EncodeToString - 密钥建议从环境变量读取,不要硬编码;若需兼容旧系统用 hex 密钥,务必先
hex.DecodeString再喂给hmac.New - 算法选
sha256是当前底线,sha1已不安全,md5绝对禁用
简短示意:
h := hmac.New(sha256.New, []byte(os.Getenv("API_SECRET")))
h.Write([]byte(signStr))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
验证时必须校验时间戳和重放攻击
只比对 sign 值?等于裸奔。攻击者截获一次请求,5 分钟内重放照样成功。
- 解析出
ts后立刻检查是否在服务端当前时间 ± 300 秒内(用time.Now().Unix()对比,别用本地时区时间) - 把
ts+uid+nonce(或ts+client_ip)存进 Redis,设置 300 秒过期;验证前先查是否存在,存在则拒绝 —— 这能挡住大部分重放 - 如果不用
nonce,至少确保uid是登录态用户 ID,而非匿名设备 ID,否则一个设备刷一堆请求就废了防重放逻辑
Go 标准库没提供“一键签名”函数,别依赖第三方包的 magic 方法
像 github.com/go-playground/validator 或某些 SDK 里的 SignRequest,往往默认帮你加了 header、自动拼 query、甚至偷偷加时间戳——你根本不知道它到底签了什么。
- 自己写 20 行以内的签名函数,控制权才在你手上
- 测试时用固定密钥 + 固定参数跑一次,把输出
sign值打出来,拿 Python 或 Node.js 同样逻辑跑一遍,必须完全一致才算过关 - 上线前用真实安卓/iOS 客户端抓包,对比服务端日志里还原出的
signStr和客户端实际发送的是否一字不差(尤其注意中文、特殊符号的编码)
最常漏掉的是:前端 JavaScript 用 encodeURIComponent 编码,而 Go 用 url.QueryEscape,二者对 ~!$'()* 等字符处理不同——这种细节不实测,线上一定翻车。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











