今天在做机动车重点库是遇到了一个新的鉴权模式,本质上和:
- 华为云
- 阿里云
- AWS
那套签名机制类似。 总体做下来感觉比较有记录意义,现在分享出来给大家做参考。
目录
[body 要按 key 排序](#body 要按 key 排序)
[六、Java 完整工具类(直接能用)](#六、Java 完整工具类(直接能用))
[Hmac 工具类](#Hmac 工具类)
[1 body字段顺序](#1 body字段顺序)
[2 path 不是完整 URL](#2 path 不是完整 URL)
[3 POST 必须大写](#3 POST 必须大写)
[4 \n 换行问题](#4 \n 换行问题)
[5 body 不是 JSON](#5 body 不是 JSON)
[6 Content-Type](#6 Content-Type)
一、需要做的事情
核心只有三步:
| 步骤 | 内容 |
|---|---|
| 1 | 组装签名字符串 |
| 2 | 用 SK 做 HmacSHA256 |
| 3 | Base64 编码后放 header |
二、规则
签名原文格式:
HTTP_METHOD
PATH
AK
TIMESTAMP
BODY排序后的字段拼接
注意:
是直接拼接
不是 JSON
不是 &
不是 urlencode
三、示例
请求:
POST
"****/****/test"
AK:
appkey92f6
时间戳:
1685443083119
body:
{
"authType":1,
"lend_time":"2023-05-19 23:00:00",
"org_code":"51013",
"org_code_all":"510",
"pageNo":1,
"pageSize":5,
"start_time":"2023-05-19 00:00:00"
}
四、真正参与签名的数据
注意:
body 要按 key 排序
变成:
authType1
lend_time2023-05-19 23:00:00
org_code51013
org_code_all510
pageNo1
pageSize5
start_time2023-05-19 00:00:00
五、最终签名原文
POST
****/***/test
appkey92f6
1685443083119
authType1
lend_time2023-05-19 23:00:00
org_code51013
org_code_all510
pageNo1
pageSize5
start_time2023-05-19 00:00:00
注意:
文档里的 \n 是换行
六、Java 完整工具类(直接能用)
Hmac 工具类
import com.alibaba.fastjson.JSON;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;
public class HmacSignUtil {
/**
* 生成签名
*/
public static String sign(
String method,
String path,
String ak,
String sk,
String timestamp,
Map<String, Object> body
) throws Exception {
// 1 body按key排序
TreeMap<String, Object> sortedMap = new TreeMap<>(body);
// 2 拼接body
StringBuilder bodyBuilder = new StringBuilder();
for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
bodyBuilder.append(entry.getKey())
.append(entry.getValue());
}
// 3 拼接签名原文
String signStr =
method.toUpperCase() + "\n"
+ path + "\n"
+ ak + "\n"
+ timestamp + "\n"
+ bodyBuilder;
System.out.println("签名原文:");
System.out.println(signStr);
// 4 HmacSHA256
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec =
new SecretKeySpec(sk.getBytes(StandardCharsets.UTF_8),
"HmacSHA256");
mac.init(secretKeySpec);
byte[] bytes =
mac.doFinal(signStr.getBytes(StandardCharsets.UTF_8));
// 5 Base64
return Base64.getEncoder().encodeToString(bytes);
}
}
七、调用示例
Map<String, Object> body = new HashMap<>();
body.put("authType", 1);
body.put("lend_time", "2023-05-19 23:00:00");
body.put("org_code", "51013");
body.put("org_code_all", "510");
body.put("pageNo", 1);
body.put("pageSize", 5);
body.put("start_time", "2023-05-19 00:00:00");
String ak = "你的AK";
String sk = "你的SK";
String timestamp = String.valueOf(System.currentTimeMillis());
String sign = HmacSignUtil.sign(
"POST",
"****/****/test",
ak,
sk,
timestamp,
body
);
System.out.println(sign);
请求头
然后请求时:
headers.add("X-BG-HMAC-SIGNATURE", sign);
headers.add("X-BG-HMAC-ALGORITHM", "HmacSHA256");
headers.add("X-BG-HMAC-ACCESS-KEY", ak);
headers.add("X-BG-DATE-TIME", timestamp);
headers.add("Accept", "*/*");
八、最容易踩坑的地方(你一定会遇到)
1 body字段顺序
必须:
按 key 字典序排序
所以:
TreeMap
非常关键。
2 path 不是完整 URL
错误:
https://xx.com/***/test
正确:
"****/****/test"
只取:
/*** 后面的内容
3 POST 必须大写
POST
GET
4 \n 换行问题
必须:
"\n"
不能:
System.lineSeparator()
5 body 不是 JSON
很多人会错写:
JSON.toJSONString(body)
这是错的。
文档要求:
key + value 拼接
6 Content-Type
只有:
application/json
application/x-www-form-urlencoded
才参与 body 签名。
九、政务系统现状
| 鉴权 | 类型 |
|---|---|
| HMAC | 最常见 |
| JWT | 常见 |
| OAuth2 | 平台类 |
| 国密 SM2/SM3/SM4 | 政务高频 |
| RSA 签名 | 公安常见 |