在后端 API 开发中,接口的安全性至关重要,尤其是对外开放的接口,容易遭受伪造请求、参数篡改等攻击。接口签名验证机制,通过对请求参数进行加密签名,确保请求的真实性和完整性,是抵御这类攻击的有效手段。
签名验证的核心原理
签名验证的核心逻辑是:客户端与服务端约定一套签名规则,客户端发送请求时,根据规则生成签名并随请求一起发送;服务端收到请求后,用同样的规则重新生成签名,与客户端传来的签名比对,一致则认为请求有效,否则拒绝。
签名生成的关键步骤
1. 确定参与签名的参数
并非所有参数都需要参与签名,通常包括:
-
业务参数(如订单号、金额等核心数据)
-
时间戳(timestamp):防止请求被重放(如限制 10 分钟内有效)
-
随机数(nonce):避免签名被预测
注意:文件、图片等二进制参数不参与签名,签名本身、token 等也不参与。
2. 参数排序与拼接
为避免因参数顺序不同导致签名不一致,需对参数按 Key 进行 ASCII 升序排序,然后拼接成 “key=value&key=value” 的字符串。
示例:参数{ "orderNo":"123", "amount":100, "timestamp":1620000000 }排序后拼接为amount=100&orderNo=123×tamp=1620000000。
3. 加入密钥并加密
在拼接后的字符串末尾加入服务端与客户端约定的密钥(secret),然后使用 MD5、SHA256 等哈希算法加密,生成签名。
代码示例:
public String generateSign(Map<String, String> params, String secret) {
// 1. 参数排序
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
// 2. 拼接字符串
StringBuilder sb = new StringBuilder();
for (String key : keys) {
String value = params.get(key);
if (value != null && !value.isEmpty()) {
sb.append(key).append("=").append(value).append("&");
}
}
// 3. 加入密钥
sb.append("secret=").append(secret);
// 4. SHA256加密
return DigestUtils.sha256Hex(sb.toString());
}
4. 服务端验证流程
服务端收到请求后:
- 提取参数和客户端传来的签名(sign)
- 验证时间戳是否在有效期内(如
当前时间 - timestamp < 10分钟) - 验证随机数(nonce)是否已使用过(防止重放,可存入 Redis 设置短期过期)
- 按客户端同样的规则生成签名,与客户端 sign 比对
- 全部通过则处理请求,否则返回 “签名无效”
避坑指南
- 密钥管理:密钥需妥善保管,客户端加密存储,服务端定期轮换
- 敏感参数加密:签名只能防篡改,不能防泄露,敏感参数(如密码)需额外加密传输
- 避免明文传输:签名验证需配合 HTTPS 使用,防止参数和签名被中间人窃取