簽名方法
package com.ruoyi.edi.service;
import com.ruoyi.common.core.constant.Constants;
import org.springframework.stereotype.Service;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Map;
/**
* Create by administrator 2021/4/23/0023 16:11
**/
@Service
public class SignService {
public static String signTopRequest(Map<String, String> params, String secret, String signMethod,String body) throws Exception {
// 第一步:檢查參數是否已經排序
String[] keys = params.keySet().toArray(new String[0]);
Arrays.sort(keys);
// 第二步:把所有參數名和參數值串在一起
StringBuilder query = new StringBuilder();
if (Constants.SIGN_METHOD_MD5.equalsIgnoreCase(signMethod)) {
query.append(secret);
}
for (String key : keys) {
String value = params.get(key);
if (areNotEmpty(new String[]{key, value})) {
query.append(key).append(value);
}
}
// 第三步:使用MD5/HMAC加密
byte[] bytes;
if (Constants.SIGN_METHOD_HMAC.equalsIgnoreCase(signMethod)) {
bytes = encryptHMAC(query.toString(), secret);
} else {
query.append(body).append(secret);
bytes = encryptMD5(query.toString());
}
// 第四步:把二進制轉化為大寫的十六進制(正確簽名應該為32大寫字符串,此方法需要時使用)
return byte2hex(bytes);
}
public static boolean areNotEmpty(String[] values) {
boolean result = true;
if ((values == null) || (values.length == 0))
result = false;
else {
for (String value : values) {
result &= !isEmpty(value);
}
}
return result;
}
public static boolean isEmpty(String value) {
int strLen;
if ((value == null) || ((strLen = value.length()) == 0))
return true;
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(value.charAt(i))) {
return false;
}
}
return true;
}
public static byte[] encryptHMAC(String data, String secret) throws IOException {
byte[] bytes = null;
try {
SecretKey secretKey = new SecretKeySpec(secret.getBytes(Constants.CHARSET_UTF8), "HmacMD5");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
bytes = mac.doFinal(data.getBytes(Constants.CHARSET_UTF8));
} catch (GeneralSecurityException gse) {
throw new IOException(gse.toString());
}
return bytes;
}
public static byte[] encryptMD5(String data) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(data.getBytes(Constants.CHARSET_UTF8));
return bytes;
// return encryptMD5(data.getBytes(Constants.CHARSET_UTF8));
}
public static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}
}
測試方法
import com.ruoyi.edi.KunZhiWlnWmsApplication;
import com.ruoyi.edi.service.SignService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashMap;
import java.util.Map;
/**
* Create by administrator 2021/4/23/0023 16:54
**/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = KunZhiWlnWmsApplication.class)
public class test {
@Autowired
public SignService signService;
@Test
public void testInsert(){
Map<String,String> map=new HashMap<>();
map.put("method","deliveryorder.create");
map.put("timestamp","2021-01-27 07:44:00");
map.put("format","xml");
map.put("app_key","testwms");
map.put("v","1.0");
map.put("sign_method","md5");
map.put("customerId","test");
try {
String a = signService.signTopRequest(map,"test","md5","<request><deliveryOrder><deliveryOrderCode>XD210119000029</deliveryOrderCode></request>");
System.out.println("==========================================");
System.out.println(a);
} catch (Exception e) {
e.printStackTrace();
}
}
// public static void main(String[] args) throws Exception {
//
// }
}
輸出結果
==========================================
0DFCE3698F2161BC06285EF9CDF7473C
奇門簽名工具:
https://console.open.taobao.com/?spm=a219a.7386797.0.0.2e10669aYnKVTW#/qimen/qimenSignTools
奇門官方簽名代碼
https://open.taobao.com/docV3.htm?spm=a219a.15212433.0.0.16a4669apDjArU&docId=101617&docType=1
公共參數
調用任何一個API都必須傳入的參數,目前支持的公共參數有:
參數名稱 參數類型 是否必須 參數描述
method String 是 API接口名稱。
app_key String 是 TOP分配給應用的AppKey。
session String 否 用戶登錄授權成功后,TOP頒發給應用的授權信息,詳細介紹請點擊這里。當此API文檔的標簽上注明:“需要授權”,則此參數必傳;“不需要授權”,則此參數不需要傳;“可選授權”,則此參數為可選。
timestamp String 是 時間戳,格式為yyyy-MM-dd HH:mm:ss,時區為GMT+8,例如:2016-01-01 12:00:00。淘寶API服務端允許客戶端請求最大時間誤差為10分鍾。
format String 否 響應格式。默認為xml格式,可選值:xml,json。
v String 是 API協議版本,可選值:2.0。
partner_id String 否 合作伙伴身份標識。
target_app_key String 否 被調用的目標AppKey,僅當被調用的API為第三方ISV提供時有效。
simplify Boolean 否 是否采用精簡JSON返回格式,僅當format=json時有效,默認值為:false。
sign_method String 是 簽名的摘要算法,可選值為:hmac,md5,hmac-sha256。
sign String 是 API輸入參數簽名結果,簽名算法參照下面的介紹。
業務參數
API調用除了必須包含公共參數外,如果API本身有業務級的參數也必須傳入,每個API的業務級參數請考API文檔說明。
簽名算法
為了防止API調用過程中被黑客惡意篡改,調用任何一個API都需要攜帶簽名,TOP服務端會根據請求參數,對簽名進行驗證,簽名不合法的請求將會被拒絕。TOP目前支持的簽名算法有三種:MD5(sign_method=md5),HMAC_MD5(sign_method=hmac),HMAC_SHA256(sign_method=hmac-sha256),簽名大體過程如下:
對所有API請求參數(包括公共參數和業務參數,但除去sign參數和byte[]類型的參數),根據參數名稱的ASCII碼表的順序排序。如:foo:1, bar:2, foo_bar:3, foobar:4排序后的順序是bar:2, foo:1, foo_bar:3, foobar:4。
將排序好的參數名和參數值拼裝在一起,根據上面的示例得到的結果為:bar2foo1foo_bar3foobar4。
把拼裝好的字符串采用utf-8編碼,使用簽名算法對編碼后的字節流進行摘要。如果使用MD5算法,則需要在拼裝的字符串前后加上app的secret后,再進行摘要,如:md5(secret+bar2foo1foo_bar3foobar4+secret);如果使用HMAC_MD5算法,則需要用app的secret初始化摘要算法后,再進行摘要,如:hmac_md5(bar2foo1foo_bar3foobar4)。
將摘要得到的字節流結果使用十六進制表示,如:hex("helloworld".getBytes("utf-8")) = "68656C6C6F776F726C64"
說明:MD5和HMAC_MD5都是128位長度的摘要算法,用16進制表示,一個十六進制的字符能表示4個位,所以簽名后的字符串長度固定為32個十六進制字符。
JAVA簽名示例代碼
public static String signTopRequest(Map<String, String> params, String secret, String signMethod) throws IOException {
// 第一步:檢查參數是否已經排序
String[] keys = params.keySet().toArray(new String[0]);
Arrays.sort(keys);
// 第二步:把所有參數名和參數值串在一起
StringBuilder query = new StringBuilder();
if (Constants.SIGN_METHOD_MD5.equals(signMethod)) {
query.append(secret);
}
for (String key : keys) {
String value = params.get(key);
if (StringUtils.areNotEmpty(key, value)) {
query.append(key).append(value);
}
}
// 第三步:使用MD5/HMAC加密
byte[] bytes;
if (Constants.SIGN_METHOD_HMAC.equals(signMethod)) {
bytes = encryptHMAC(query.toString(), secret);
} else {
query.append(secret);
bytes = encryptMD5(query.toString());
}
// 第四步:把二進制轉化為大寫的十六進制(正確簽名應該為32大寫字符串,此方法需要時使用)
//return byte2hex(bytes);
}
public static byte[] encryptHMAC(String data, String secret) throws IOException {
byte[] bytes = null;
try {
SecretKey secretKey = new SecretKeySpec(secret.getBytes(Constants.CHARSET_UTF8), "HmacMD5");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
bytes = mac.doFinal(data.getBytes(Constants.CHARSET_UTF8));
} catch (GeneralSecurityException gse) {
throw new IOException(gse.toString());
}
return bytes;
}
public static byte[] encryptMD5(String data) throws IOException {
return encryptMD5(data.getBytes(Constants.CHARSET_UTF8));
}
public static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}
C#簽名示例代碼
public static string SignTopRequest(IDictionary<string, string> parameters, string secret, string signMethod)
{
// 第一步:把字典按Key的字母順序排序
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters, StringComparer.Ordinal);
IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
// 第二步:把所有參數名和參數值串在一起
StringBuilder query = new StringBuilder();
if (Constants.SIGN_METHOD_MD5.Equals(signMethod))
{
query.Append(secret);
}
while (dem.MoveNext())
{
string key = dem.Current.Key;
string value = dem.Current.Value;
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
{
query.Append(key).Append(value);
}
}
// 第三步:使用MD5/HMAC加密
byte[] bytes;
if (Constants.SIGN_METHOD_HMAC.Equals(signMethod))
{
HMACMD5 hmac = new HMACMD5(Encoding.UTF8.GetBytes(secret));
bytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
}
else
{
query.Append(secret);
MD5 md5 = MD5.Create();
bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
}
// 第四步:把二進制轉化為大寫的十六進制
StringBuilder result = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
result.Append(bytes[i].ToString("X2"));
}
return result.ToString();
}
其他語言簽名示例代碼請參見TOP官方SDK源代碼。
調用示例
以taobao.item.seller.get調用為例,具體步驟如下:
1. 設置參數值
公共參數:
method = "taobao.item.seller.get"
app_key = "12345678"
session = "test"
timestamp = "2016-01-01 12:00:00"
format = "json"
v = "2.0"
sign_method = "md5"
業務參數:
fields = "num_iid,title,nick,price,num"
num_iid = 11223344
2. 按ASCII順序排序
app_key = "12345678"
fields = "num_iid,title,nick,price,num"
format = "json"
method = "taobao.item.seller.get"
num_iid = 11223344
session = "test"
sign_method = "md5"
timestamp = "2016-01-01 12:00:00"
v = "2.0"
3. 拼接參數名與參數值
app_key12345678fieldsnum_iid,title,nick,price,numformatjsonmethodtaobao.item.seller.getnum_iid11223344sessiontestsign_methodmd5timestamp2016-01-01 12:00:00v2.0
4. 生成簽名
假設app的secret為helloworld,則簽名結果為:hex(md5(helloworld+按順序拼接好的參數名與參數值+helloworld)) = "66987CB115214E59E6EC978214934FB8"
5. 組裝HTTP請求
將所有參數名和參數值采用utf-8進行URL編碼(參數順序可隨意,但必須要包括簽名參數),然后通過GET或POST(含byte[]類型參數)發起請求,如:
注意事項
所有的請求和響應數據編碼皆為utf-8格式,URL里的所有參數名和參數值請做URL編碼。如果請求的Content-Type是application/x-www-form-urlencoded,則HTTP Body體里的所有參數值也做URL編碼;如果是multipart/form-data格式,每個表單字段的參數值無需編碼,但每個表單字段的charset部分需要指定為utf-8。
參數名與參數值拼裝起來的URL長度小於1024個字符時,可以用GET發起請求;參數類型含byte[]類型或拼裝好的請求URL過長時,必須用POST發起請求。所有API都可以用POST發起請求, 但要求系統參數都放在query體中, 比如上述HTTP請求,系統參數app_key應該在queryt體,而業務參數fields可以在body體里,也可以在在query體,但推薦放body體,防止URL過長。
生成簽名(sign)僅對未使用TOP官方SDK進行API調用時需要操作,如使用了TOP官方SDK,該步驟SDK會自動完成。