菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
382
0

淘宝奇门接口的签名方法

原创
05/13 14:22
阅读数 62458

签名方法

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
image

奇门官方签名代码

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[]类型参数)发起请求,如:

http://gw.api.taobao.com/router/rest?method=taobao.item.seller.get&app_key=12345678&session=test&timestamp=2016-01-01+12%3A00%3A00&format=json&v=2.0&sign_method=md5&fields=num_iid%2Ctitle%2Cnick%2Cprice%2Cnum&num_iid=11223344&sign=66987CB115214E59E6EC978214934FB8

注意事项

所有的请求和响应数据编码皆为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会自动完成。

发表评论

0/200
382 点赞
0 评论
收藏