经验分享——C#语言对接Java语言项目有关签名编码问题

112 阅读5分钟

最近对接了拼多多项目和菜鸟物流项目,对接过程中遇到了不少问题,不过现在都解决了。现在把遇到的问题和解决方案分享出来,希望帮到一些需要的人。

遇到最大问题就是签名问题,其实可以归结为编码问题。就是两个语言的不同。他们是Java语言,我们是C#语言。一方面是他们文档写得不够详细,还有一些小错误,误导了我们。所以花费不少时间。

现在开始吧!

菜鸟物流他们提供的签名demo代码

public static void main(String[] args) {
    String appSecret  = "test3as9dj1kj08488c49b69ead19339"; //替换你的appSecret
    //logistics_interface参数
    String logisticsInterfaceStr = "{\"cpCode\":\"YUNDA\",\"mailNo\":\"123456\",\"pushType\":\"1\",\"receiverAddress\":{\"address\":\"test\"},\"receiverTel\":\"18888888888\",\"subCallBackParam\":\"test\"}";
    JSONObject json = JSON.parseObject(logisticsInterfaceStr);
    String sortStr = getAloneKeys(json);
    //排序后放入map
    Map<String, String> map = new HashMap<String, String>();
    map.put("logistics_interface", sortStr);
    map.put("nonce", "1686210870");
    String sign = getSign(appSecret, map);
    System.out.println("sign:" + sign);//sign:c8e33b8fc5767727e57218e6c24afefc
}

他们的签名步骤

S1. 秘钥appSecret(由接口方提供)

S2. 对logistics_interface和nonce按照字段名小写从小到大排序

S3. 构造URL 键值对的形式,拼接成字符串A

S4. 把密钥appSecret加到字符串A前,例:appSecret+A

S5. 再用md5加密,编码utf8

这里他们提供了签名密钥,签名的业务数据和签名结果,但是他们没有提供最后的签名字符串。

就是这个

var resultStr = appSecret + tempStr;

待签名的最终字符串:resultStr

test3as9dj1kj08488c49b69ead19339logistics_interface=%7B%22cpCode%22%3A%22YUNDA%22%2C%22mailNo%22%3A%22123456%22%2C%22pushType%22%3A%221%22%2C%22receiverAddress%22%3A%7B%22address%22%3A%22test%22%7D%2C%22receiverTel%22%3A%2218888888888%22%2C%22subCallBackParam%22%3A%22test%22%7D&nonce=1686210870

c#有关签名方法 最开始我是使用

HttpUtility.UrlEncode

结果死活与他们提供的签名不一致,那就是有问题。找了很久没有解决,后面问了同事,他说要用这个方法

WebUtility.UrlEncode

换了之后果然与他们提供签名一致了。

但是我想找出他们两个的不同,于是我用两个方法生成对比,发内容是一样的,但是有些字母大小写不一致,如下:

%7b%22cpCode%22%3a%22YUNDA%22%2c%22mailNo%22%3a%22123456%22%2c%22pushType%22%3a%221%22%2c%22receiverAddress%22%3a%7b%22address%22%3a%22test%22%7d%2c%22receiverTel%22%3a%2218888888888%22%2c%22subCallBackParam%22%3a%22test%22%7d

%7B%22cpCode%22%3A%22YUNDA%22%2C%22mailNo%22%3A%22123456%22%2C%22pushType%22%3A%221%22%2C%22receiverAddress%22%3A%7B%22address%22%3A%22test%22%7D%2C%22receiverTel%22%3A%2218888888888%22%2C%22subCallBackParam%22%3A%22test%22%7D

所以得出结论:他们是有一些区别的

于是又问gpt平台,有什么办法把他们弄成一致呢? 平台给出了我答案——就是下面这个自定义编码方法。

在自定义的 CustomUrlEncode 方法中,我们遍历输入字符串的每个字符,并检查字符是否为保留字符(字母、数字、连字符、下划线、句点、波浪号)。如果是保留字符,则直接追加到结果字符串中;否则,将字符转换为字节数组,并使用 % 后跟两个十六进制数字表示该字节,最后将其追加到结果字符串中。

通过以上代码,您可以确保自定义的 CustomUrlEncode 方法生成的结果与 System.Net.WebUtility.UrlEncode 完全一致。

自定义编码方法


        static string CustomUrlEncode(string input)
        {
            var sb = new StringBuilder();

            foreach (char c in input)
            {
                if (IsUnreserved(c))
                {
                    sb.Append(c);
                }
                else
                {
                    byte[] bytes = Encoding.UTF8.GetBytes(c.ToString());

                    foreach (byte b in bytes)
                    {
                        sb.Append('%');
                        sb.Append(b.ToString("X2"));
                    }
                }
            }

            return sb.ToString();
        }

        static bool IsUnreserved(char c)
        {
            return char.IsLetterOrDigit(c) || c == '-' || c == '_' || c == '.' || c == '~';
        }

    public string GetCommonSign(SortedDictionary<string, object> dicResult)
        {
            dicResult.Remove("sign");
            //  var xxx = "9b1aed5de48c9de4572246f63d6d524263ee6d3f";

            var appSecret = "test3as9dj1kj08488c49b69ead19339";
            StringBuilder sb = new StringBuilder();
            foreach (var item in dicResult)
            {
                if (item.Value == null)
                {

                }
                else
                {
                    if (item.Value is string)
                    {
                       // sb.AppendFormat("{0}={1}&", item.Key.ToLower(), System.Net.WebUtility.UrlEncode(item.Value.ToString())); 

                        sb.AppendFormat("{0}={1}&", item.Key.ToLower(), CustomUrlEncode(item.Value.ToString()));
                    }
                    else
                    {
                         sb.AppendFormat("{0}={1}&", item.Key.ToLower(),CustomUrlEncode(JsonConvert.SerializeObject(item.Value)));
                    }
                }
            }
            string tempStr=sb.ToString().TrimEnd('&');
            var resultStr = appSecret + tempStr;
            //大写
            return MD5(resultStr.Trim()).ToUpper();
        }

测试得到一致的结果。

上面的方法没法处理中文和空白字符,修正处理代码

      static string CustomUrlEncode(string input)
        {
           var temp= input.Replace(" ", "+");
            StringBuilder sb = new StringBuilder();
            byte[] bytes = Encoding.UTF8.GetBytes(temp);

            foreach (byte b in bytes)
            {
                char c = (char)b;

                // 判断是否为保留字符,并且是字母或数字
                if ((b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') || (b >= '0' && b <= '9') ||
                    b == '-' || b == '_' || b == '+' || b == '.' || b == '~')
                {
                    sb.Append(c);
                }
                else
                {
                    sb.Append(Uri.HexEscape(c));
                }
            }
            return sb.ToString();
        }

因此,我们可以使用两种方法来解决编码问题了。

其他辅助代码

        public static string MD5(string str)
        {
            using (var md5 = System.Security.Cryptography.MD5.Create())
            {
                var result = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
                var strResult = BitConverter.ToString(result);
                return strResult.Replace("-", "");
            }
        }

        /// <summary>
        /// 参数转换
        /// </summary>
        /// <param name="jsonString"></param>
        /// <returns></returns>
        public SortedDictionary<string, object> ConvertJsonToSortedDictionary(string jsonString)
        {
            var jsonObject = JsonConvert.DeserializeObject<SortedDictionary<string, object>>(jsonString);
            var sortedDictionary = new SortedDictionary<string, object>(jsonObject);
            return sortedDictionary;
        }

签名另一种写法

public string GetSign(SortedDictionary<stringobject> dicResult)  
        {  
            dicResult.Remove("sign");  
            StringBuilder sb = new StringBuilder();  
            foreach (var item in dicResult)  
            {  
                if (item.Value == null)  
                {  
  
                }  
                else  
                {  
                    sb.AppendFormat("{0}={1}", item.Key.ToLower(), item.Value);  
                    sb.Append("&");  
                }  
            }  
  
            var resultStr = appSecret + sb.ToString().Substring(0, sb.ToString().Length - 1);  
            return MD5(resultStr.Trim());  
        }

测试json格式

{
  "logistics_interface": "{\"cpCode\":\"YUNDA\",\"mailNo\":\"123456\",\"pushType\":\"1\",\"receiverAddress\":{\"address\":\"test\"},\"receiverTel\":\"18888888888\",\"subCallBackParam\":\"test\"}",
  "nonce": "1686210870",
  "sign": "sign"
}

实体类


    public partial class TestModel555
    {
        public string logistics_interface { get; set; }
        public string nonce { get; set; }
        public string sign { get; set; }
    }

测试方法

 [HttpPost]
 public ActionResult CaiNiaoTest(TestModel555 testModel2)
 {
      string newjson = JsonConvert.SerializeObject(testModel2);
     SortedDictionary<string, object> dictionary =   caiNiaoServeoce.ConvertJsonToSortedDictionary(newjson);
     string sign2 = caiNiaoServeoce.GetCommonSign(dictionary);

     return Content("签名==" + sign2);
 }

测试结果 签名==C8E33B8FC5767727E57218E6C24AFEFC

image.png

签名完善判断

    public string GetCommonSignZTO(SortedDictionary<string, object> dicResult,out SortedDictionary<string, string> dictionary2)
        {
            dicResult.Remove("sign");
            //  var xxx = "9b1aed5de48c9de4572246f63d6d524263ee6d3f";
            dictionary2 =new  SortedDictionary<string, string>();
            var appSecret = "xxxxxxxxxx";
            StringBuilder sb = new StringBuilder();
            foreach (var item in dicResult)
            {
                if (item.Value == null)
                {

                }
                else
                {
                      if (item.Value.GetType() == typeof(string) ||  item.Value.GetType() == typeof(double) || item.Value.GetType() == typeof(bool) || item.Value.GetType() == typeof(float)|| item.Value.GetType() == typeof(Int64))
                    {
                         sb.AppendFormat("{0}={1}&", item.Key.ToLower(), System.Net.WebUtility.UrlEncode(item.Value.ToString()));//

                       // sb.AppendFormat("{0}={1}&", item.Key.ToLower(), CustomUrlEncode(item.Value.ToString()));
                        dictionary2.Add(item.Key.ToLower(), item.Value.ToString());
                    }
                    else
                    { // JsonConvert.SerializeObject(test3)
                        sb.AppendFormat("{0}={1}&", item.Key.ToLower(), System.Net.WebUtility.UrlEncode(JsonConvert.SerializeObject(item.Value)));
                        dictionary2.Add(item.Key.ToLower(), JsonConvert.SerializeObject(item.Value));
                    }
                }
            }
            string tempStr = sb.ToString().TrimEnd('&');
            var resultStr = appSecret + tempStr;
            //大写
            return MD5(resultStr.Trim()).ToLower();
        }
    public string GetCommonSignZTO(SortedDictionary<string, object> dicResult,out SortedDictionary<string, string> dictionary2)
        {
            dicResult.Remove("sign");
            //  var xxx = "9b1aed5de48c9de4572246f63d6d524263ee6d3f";
            dictionary2 =new  SortedDictionary<string, string>();
            var appSecret = "xxxxxxxxxxxxxx";
            StringBuilder sb = new StringBuilder();
            foreach (var item in dicResult)
            {
                if (item.Value == null)
                {
                     
                }
                else
                {
                    if (item.Value is string  )
                    {
                         sb.AppendFormat("{0}={1}&", item.Key.ToLower(), System.Net.WebUtility.UrlEncode(item.Value.ToString()));//

                       // sb.AppendFormat("{0}={1}&", item.Key.ToLower(), CustomUrlEncode(item.Value.ToString()));
                             dictionary2.Add(item.Key, item.Value.ToString());           
                    }
                    else
                    { 
                        sb.AppendFormat("{0}={1}&", item.Key.ToLower(), System.Net.WebUtility.UrlEncode(JsonConvert.SerializeObject(item.Value)));
                        if (item.Key == "payType")
                        {
                            dictionary2.Add(item.Key, JsonConvert.SerializeObject(item.Value));
                        }
                        else
                        {
                            dictionary2.Add(item.Key.ToLower(), JsonConvert.SerializeObject(item.Value));
                        }
                       
                    }
                }
            }
            string tempStr = sb.ToString().TrimEnd('&');
            var resultStr = appSecret + tempStr;
            //大写
            return MD5(resultStr.Trim()).ToLower();
        }

其他方法

  CaiNiaoService caiNiaoServeoce  = new CaiNiaoService();//
             string body = JsonConvert.SerializeObject(order);
             SortedDictionary<string, object> dictionary = caiNiaoServeoce.ConvertJsonToSortedDictionary(body);

             SortedDictionary<string, string> dictionary2 =null;


             order.sign = caiNiaoServeoce.GetCommonSignZTO(dictionary, out dictionary2);
             dictionary2.Add("sign", order.sign);


              string body2 = "";
              int i = 1;
              foreach (var item in dictionary2)
              {
                  if (i == dictionary2.Count)
                  {
                      body2 += item.Key + "=" + item.Value;
                  }
                  else
                  {
                      body2 += item.Key + "=" + item.Value + "&";
                  }
                  i++;
              }

              string responseBody = HttpHelperService.SendHttpRequest2(postUrl, body2, "application/x-www-form-urlencoded", headers, 3000);

到此,分享完毕!

参考:菜鸟物流接口文档 www.yuque.com/u34567120/w…