请求签名鉴权
在调用API 前,需要拼接签名字符串,并将签名计算后的字符串放在请求的 Header 传入,网关会通过的Request 的 Header 中的 X-Tsign-Open-Ca-Signature获取签名,然后通过计算签名来验证请求者的身份。
请求签名的Header包含以下字段:
- 【必选】X-Tsign-App-Id:应用的 AppId。
- 【必选】X-Tsign-Open-Auth-Mode:告诉网关当前哪种方式认证,请求签名接入时传Signature
- 【必选】X-Tsign-Open-Ca-Signature:签名字符串。
- 【必选】X-Tsign-Open-Ca-Timestamp:API 调用者传递时间戳,值为当前时间的毫秒数,也就是从1970年1月1日起至今的时间转换为毫秒,时间戳有效时间为15分钟,为了防重放攻击。
- 【可选】Content-MD5: 当请求 Body 非 Form 表单时,可以计算 Body 的 MD5 值传递给云网关进行 Body MD5 校验。建议当请求 Body 非 Form 表单时,加上此请求头。
- 【可选】X-Tsign-open-Ca-Signature-Headers:当需要对headers进行签名时需要加上此请求头。
其中重点需要计算的是X-Tsign-Open-Ca-Signature (签名字符串),签名字符串的生成分两个步骤:拼接字符串和计算签名。
1.签名字符串生成
步骤一:待签字符串准备
待签字符串由以下字段拼接组成
参数 | 说明 |
---|---|
HTTPMethod | 全大写,例如POST |
Accept | 希望服务器响应发送回来的是xml文本格式的内容,例如:application/json |
Content-MD5 | Content-MD5 是指 Body 的 MD5 值,只有当 Body 非 Form 表单时才计算 MD5 |
Content-Type | 请求的与实体对应的MIME信息,例如:application/json |
Date | 可选,Date头域表示消息发送的时间,缓存在评估响应的新鲜度时要用到,时间的描述格式由RFC822定义。例如,Date: Thu, 11 Jul 2015 15:33:24 GMT。 |
Headers | 可选,对请求的自定义Header进行签名,生成方法见下方 |
Url | Url ,对请求的url进行签名,生成方法见下方 |
(1)待签字符串的拼接方法
String stringToSign=
HTTPMethod + "\n" +
Accept + "\n" +
Content-MD5 + "\n"
Content-Type + "\n" +
Date + "\n" +
Headers +
Url
其中HTTPMethod为全大写,Accept、Content-MD5、Content-Type、Date 如果为空也需要添加换行符”\n”,Headers如果为空不需要添加”\n”。
(2)Content-MD5的计算方法
Content-MD5 是指 Body 的 MD5 值,只有当 Body 非 Form 表单时才计算 MD5,计算方式为:
String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getbytes("UTF-8")));
其中bodyStream 为字节数组。
(3)Headers的添加方法
Headers 是指参与 Headers 签名计算的 Header 的 Key、Value 拼接的字符串,建议对 X-Tsign 开头以及自定义 Header 计算签名。若无需对Headers进行签名,可以设为空。
先对参与 Headers 签名计算的 Header的Key 按照字典排序后使用如下方式拼接,如果某个 Header 的 Value 为空,则使用 HeaderKey + “:” + “\n”参与签名,需要保留 Key 和英文冒号。
String headers =
HeaderKey1 + ":" + HeaderValue1 + "\n"\+
HeaderKey2 + ":" + HeaderValue2 + "\n"\+
...
HeaderKeyN + ":" + HeaderValueN + "\n"
若添加了Headers,则需要将 Headers 签名中 Header 的 Key 使用英文逗号分割放到 Request 的 Header 中,Key为:X-Tsign-open-Ca-Signature-Headers。
(4)Url的添加方法
Url 指 Path + Query + Body 中 Form 参数,组织方法:对 Query+Form 参数按照字典对 Key 进行排序后按照如下方法拼接,如果 Query 或 Form 参数为空,则 Url = Path,不需要添加 ?,如果某个参数的 Value 为空只保留 Key 参与签名,等号不需要再加入签名。
String url =
Path +
"?" +
Key1 + "=" + Value1 +
"&" + Key2 + "=" + Value2 +
...
"&" + KeyN + "=" + ValueN
注意这里 Query 或 Form 参数的 Value 可能有多个,多个的时候只取第一个 Value 参与签名计算。
步骤二:计算签名字符串
对待签字符串使用HmacSHA256进行签名运算,从而得到签名字符串,计算方法如下:
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
byte[] keyBytes = secret.getBytes("UTF-8");
hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, "HmacSHA256"));
String sign = new String(Base64.encodeBase64(hmacSha256.doFinal(stringToSign.getBytes("UTF-8")),"UTF-8"));
2.签名字符串生成代码示例
import Utils.CommonApi;
import com.alibaba.fastjson.JSONObject;
import constants.Constants;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
public class Demo {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
String url = "/v1/accounts/elogin/sign";
String encryptContent = "N9SxYsmDaYnztLOHio9n9R13yeF9GaZOUE6c4GkRL7ifyQarDk9jeVypke9iWZwQCa/SP5ewC+jb6ecAJYsAIXskfXGBiQG/+j1qukHsc5/BF8etGF7Mzgwdl2gBo6AJmIavDSnfmNip2cn6TCwnxqddfHklYWNteOrjEDCXm/ieXhR37BYyYLBGSjITkTXE9+cdXbOlBT+WozjP4TKL8Fo/yCe7XNppV/kCseRkU9k8j97QkLYENhg6CLTv1fM8BDyfBaQRFWituIxaq+xX3KpvKRl7mQ/QX5gdOVFuv1NsysJbyA8FOpF/0bPA3UGfjparjjvOItc+YeLlHMFKYg==";
String shortLinkUrl = "https://smlt.tsign.cn/aIa71lcWtzkF";
String appid = "4438779132";
String secret = "ce0c19c6728c52dfc417beb405c8824d";
String enter = "\n";
String method = "POST";
String accept = "application/json";
String contentType = "application/json; charset=UTF-8";
String contentMD5 = "";
String date = "";
String headers = "";
StringBuilder sb = new StringBuilder();
sb.append(method).append(enter).append(accept).append(enter)
.append(contentMD5).append(enter).append(contentType).append(enter)
.append(date).append(enter);
if ("".equals(headers)){
sb.append(headers).append(url);
} else {
sb.append(headers).append(enter).append(url);
}
String stringToSign = sb.toString();
System.out.println(stringToSign);
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, "HmacSHA256"));
String sign = new String(Base64.encodeBase64(hmacSha256.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
JSONObject params = new JSONObject();
params.put("encryptContent", encryptContent);
params.put("shortLinkUrl", shortLinkUrl);
Map<String, String> header = new HashMap<>();
header.put("Content-Type", contentType);
header.put("X-Tsign-Open-App-Id", appid);
header.put("X-Tsign-Open-Auth-Mode", "Signature");
header.put("X-Tsign-Open-Ca-Signature", sign);
header.put("X-Tsign-Open-Ca-Timestamp", String.valueOf(System.currentTimeMillis()));
header.put("Accept", accept);
CommonApi.requestJson(url, Constants.HOST_ELOGIN, params, header);
}
}