目录
一、什么是 API 签名认证?
1. 用于验证 API 请求的安全机制
2. 作用
- 确保只有经过授权的用户或应用程序能访问 API
- 防止未经授权的访问和数据篡改
二、为什么需要 API 签名认证?
1. 限制接口的调用者、调用次数
2. 提高 API 的安全性
三、如何实现 API 签名认证?
核心:签发签名和校验签名
- accessKey:调用的标识
- secretKey:密钥
1. 客户端准备请求:客户端构建API请求,并收集所有必要的参数,如请求方法、URL、时间戳、身份凭证等。
2. 参数排序:将请求参数按照一定的规则进行排序,通常是按参数名称的字母顺序进行排序。
3. 生成待签名字符串:将排序后的参数按照一定的格式拼接成一个待签名的字符串。
4. 添加密钥:将密钥(一般是客户端的私钥)加入待签名字符串中。
5. 计算签名:使用特定的加密算法(如HMAC-SHA1、HMAC-SHA256等)对待签名字符串进行加密,生成签名。
6. 发送请求:将生成的签名添加到API请求中的特定位置(如请求头或请求参数)。
7. 服务器端验证签名:服务器端收到请求后,会使用相同的算法和密钥对接收到的参数进行签名计算。然后将服务器端计算得到的签名与请求中的签名进行比对,如果一致,则验证通过;否则,验证失败。
1. 客户端在请求头中携带 accessKey 和 secretKey 发送请求给服务端
2. 服务端从请求头中取出参数进行校验
- 客户端携带参数
/**
* 将参数添加到请求头 map
* @return
*/
private Map<String, String> headerMap(String body) {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("accessKey", accessKey);
// 一定不能发送给后端!
// hashMap.put("secretKey", secretKey);
hashMap.put("nonce", RandomUtil.randomNumbers(5));
hashMap.put("body", body);
hashMap.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
hashMap.put("sign", SignUtil.genSign(body, secretKey));
return hashMap;
}
- 服务端校验参数
- 使用同一套加密算法 / 签名生成算法
package com.ghost.leapiinterface.utils;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.Digester;
/**
* 签名生成工具类
* @author 乐小鑫
* @version 1.0
* @Date 2024-02-02-15:43
*/
public class SignUtil {
/**
* 使用 MD5 加密算法生成签名
* @param body 用户参数
* @return 签名
*/
public static String genSign(String body, String secretKey) {
Digester md5 = new Digester(DigestAlgorithm.MD5);
String str = body.toString() + "." + secretKey;
return md5.digestHex(str);
}
}
@PostMapping("/user")
public String getNameByJSON(@RequestBody User user, HttpServletRequest request) {
// 获取请求头中携带的参数,校验调用接口的权限
String accessKey = request.getHeader("accessKey");
// String secretKey = request.getHeader("secretKey");
String nonce = request.getHeader("nonce");
String body = request.getHeader("body");
String timestamp = request.getHeader("timestamp");
String sign = request.getHeader("sign");
// TODO 实际上要从数据库查是否已分配给用户
if (!accessKey.equals("ghost")) {
throw new RuntimeException("无权限");
}
// 校验随机数
if (nonce.length() > 5) {
throw new RuntimeException("无权限");
}
// TODO 校验时间戳 timestamp:和当前时间不能超过 5 min
// 和客户端使用同一套加密算法进行校验
String serverSign = SignUtil.genSign(body, "abcdefg");// 实际上要从数据库中取出数据进行校验
if (!serverSign.equals(sign)) {
throw new RuntimeException("无权限");
}
// System.out.println("getNameByJSON 被调用,accessKey: " + accessKey + "; secretKey:" +secretKey);
return "POST 你的名字是:" + user.getUsername();
}
四、API 签名认证的六个参数
1. 用户标识 accessKey
2. 密钥 secretKey:不能传递给后端❗
3. 用户参数
4. 签名 sign
5. 随机数 防重放
6. 时间戳 timeStamp
附:对称加密、非对称加密和单向加密
1. 对称加密
- 对称加密使用相同的密钥对数据进行加解密
- 发送方和接收方必须事先共享密钥,否则无法进行加解密
- 常见的对称加密算法:DES、3DES、AES 等
- 适用场景:适用于数据传输量较大、安全性要求不是特别高的场景
2. 非对称加密
- 非对称加密则使用一对不同的密钥进行加解密,分别称为公钥和私钥
- 公钥可以公开,任何人都可以使用它对数据进行加密
- 私钥只能由密钥持有人保管,并用于解密数据
- 常见的非对称加密算法:RSA、DSA 等
- 适用场景:适用于对数据安全性要求较高的场景,例如数字签名、身份认证等
3. 单向加密
- 只能对数据进行加密,而无法逆向解密回原始数据
- 举例:哈希函数
- 相同的输入始终产生相同的哈希值
- 即使输入数据发生微小变化,哈希值也会发生巨大变化
- 无法从哈希值推导出原始输入数据
- MD5、SHA-256 加密算法也是单向加密
举个例子,常见的哈希函数是 MD5 和 SHA-256。假设我们对字符串 "Hello, World!" 进行 MD5 哈希,得到的哈希值是 "65a8e27d8879283831b664bd8b7f0ad4"。无论我们多少次对这个字符串进行 MD5 哈希运算,都会得到相同的哈希值。但是,如果我们只知道哈希值 "65a8e27d8879283831b664bd8b7f0ad4",是无法确定原始的字符串是 "Hello, World!" 的。