前段時間寫了一篇博文《釘釘如何進行PC端開發》,在里面並未解決本地生成簽名的問題,需要到官網進行生成,由於釘釘門票等認證信息會超期,因此,必須能本地用代碼自動更新相關參數信息,來換取簽名。官方文檔由於這塊並未有.NET版本的簽名API可供調用,無奈只能自己摸索着進行實現。可笑的是,在看釘釘文檔時候並未理解其算法,但是卻在看微信JS-API簽名生成算法的時候頓悟了。感覺二者這個權限體系認證很是類似。
話不多說,切入正題。
釘釘廣泛文檔對於JS-API的說明是這樣的:
開發者在web頁面使用釘釘容器提供的jsapi時,需要驗證調用權限,並以參數signature標識合法性
簽名生成的規則:
List keyArray = sort(noncestr,timestamp,jsapi_ticket,url);
String str = assemble(keyArray);
signature = sha1(str);
參與簽名的字段包括在上文中獲取的jsapi_ticket,noncestr(隨機字符串,自己隨便填寫即可),timestamp(當前時間戳,具體值為當前時間到1970年1月1號的秒數),url(當前網頁的URL,不包含#及其后面部分)。例如:
- noncestr=Zn4zmLFKD0wzilzM
- jsapi_ticket=mS5k98fdkdgDKxkXGEs8LORVREiweeWETE40P37wkidkfksDSKDJFD5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcKIDU8l
- timestamp=1414588745
- url=http://open.dingtalk.com
步驟1. sort()含義為對所有待簽名參數按照字段名的ASCII 碼從小到大排序(字典序)
步驟2. assemble()含義為根據步驟1中獲的參數字段的順序,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串
步驟2. sha1()的含義為對在步驟2拼接好的字符串進行sha1加密。(PS:明明是步驟3好么!)
一開始沒有理解,用代碼生成簽名和官方的就是對不上,很是打擊。但是后來發現就是沒有理解文檔中的第一步,后來發現是:簽名參數按照【字段名】而非一開始理解的參數值。
既然理解了,那么先看看簽名參數按照字段名noncestr、jsapi_ticket、timestamp和url的字典排序吧:
1 ArrayList AL = new ArrayList(); 2 AL.Add("noncestr"); 3 AL.Add("jsapi_ticket"); 4 AL.Add("timestamp"); 5 AL.Add("url"); 6 AL.Sort();
那么下一步就是拼接字符串了:
string assemble =string.Format("jsapi_ticket={0}&noncestr={1}×tamp={2}&url={3}", jsapi_ticket, noncestr,sTimeStamp, url);
最后一步驟:
1 sha = new SHA1CryptoServiceProvider(); 2 enc = new ASCIIEncoding(); 3 byte[] dataToHash = enc.GetBytes(assemble); 4 byte[] dataHashed = sha.ComputeHash(dataToHash); 5 hash = BitConverter.ToString(dataHashed).Replace("-", ""); 6 hash = hash.ToLower();
完整代碼如下,注意此包含時間戳的生產方法:
1 using System; 2 using System.Text; 3 using System.Security.Cryptography; 4 using Suite.Corp; 5 using System.Collections; 6 /* 7 * Author:JackWangCUMT 8 * Date:2016-04-26 8:36 9 * Blogs:http://www.cnblogs.com/isaboy 10 * GitHub:https://github.com/JackWangCUMT 11 * QQ:308106637 12 */ 13 namespace myDDDev 14 { 15 public static class DingTalkAuth 16 { 17 /// <summary> 18 ///開發者在web頁面使用釘釘容器提供的jsapi時,需要驗證調用權限,並以參數signature標識合法性 19 ///簽名生成的規則: 20 ///List keyArray = sort(noncestr, timestamp, jsapi_ticket, url); 21 /// String str = assemble(keyArray); 22 ///signature = sha1(str); 23 /// </summary> 24 /// <param name="noncestr">隨機字符串,自己隨便填寫即可</param> 25 /// <param name="sTimeStamp">當前時間戳,具體值為當前時間到1970年1月1號的秒數</param> 26 /// <param name="jsapi_ticket">獲取的jsapi_ticket</param> 27 /// <param name="url">當前網頁的URL,不包含#及其后面部分</param> 28 /// <param name="signature">生成的簽名</param> 29 /// <returns>0 成功,2 失敗</returns> 30 public static int GenSigurate(string noncestr, string sTimeStamp, string jsapi_ticket, string url, ref string signature) 31 { 32 33 34 //例如: 35 //noncestr = Zn4zmLFKD0wzilzM 36 //jsapi_ticket = mS5k98fdkdgDKxkXGEs8LORVREiweeWETE40P37wkidkfksDSKDJFD5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcKIDU8l 37 //timestamp = 1414588745 38 //url = http://open.dingtalk.com 39 40 //步驟1.sort()含義為對所有待簽名參數按照字段名的ASCII 碼從小到大排序(字典序) 41 //注意,此處是是按照【字段名】的ASCII字典序,而不是參數值的字典序(這個細節折磨我很久了) 42 //0:jsapi_ticket 1:noncestr 2:timestamp 3:url; 43 44 //步驟2.assemble()含義為根據步驟1中獲的參數字段的順序,使用URL鍵值對的格式(即key1 = value1 & key2 = value2…)拼接成字符串 45 //string assemble = "jsapi_ticket=3fOo5UfWhmvRKnRGMmm6cWwmIxDMCnniyVYL2fqcz1I4GNU4054IOlif0dZjDaXUScEjoOnJWOVrdwTCkYrwSl&noncestr=CUMT1987wlrrlw×tamp=1461565921&url=https://jackwangcumt.github.io/home.html"; 46 string assemble =string.Format("jsapi_ticket={0}&noncestr={1}×tamp={2}&url={3}", jsapi_ticket, noncestr,sTimeStamp, url); 47 //步驟2.sha1()的含義為對在步驟2拼接好的字符串進行sha1加密。 48 SHA1 sha; 49 ASCIIEncoding enc; 50 string hash = ""; 51 try 52 { 53 sha = new SHA1CryptoServiceProvider(); 54 enc = new ASCIIEncoding(); 55 byte[] dataToHash = enc.GetBytes(assemble); 56 byte[] dataHashed = sha.ComputeHash(dataToHash); 57 hash = BitConverter.ToString(dataHashed).Replace("-", ""); 58 hash = hash.ToLower(); 59 } 60 catch (Exception) 61 { 62 return 2; 63 } 64 signature = hash; 65 return 0; 66 67 } 68 69 /// <summary> 70 /// 獲取時間戳timestamp(當前時間戳,具體值為當前時間到1970年1月1號的秒數) 71 /// </summary> 72 /// <returns></returns> 73 public static string GetTimeStamp() 74 { 75 TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); 76 return Convert.ToInt64(ts.TotalSeconds).ToString(); 77 } 78 /// <summary> 79 /// 字典排序 80 /// </summary> 81 public class DictionarySort : System.Collections.IComparer 82 { 83 public int Compare(object oLeft, object oRight) 84 { 85 string sLeft = oLeft as string; 86 string sRight = oRight as string; 87 int iLeftLength = sLeft.Length; 88 int iRightLength = sRight.Length; 89 int index = 0; 90 while (index < iLeftLength && index < iRightLength) 91 { 92 if (sLeft[index] < sRight[index]) 93 return -1; 94 else if (sLeft[index] > sRight[index]) 95 return 1; 96 else 97 index++; 98 } 99 return iLeftLength - iRightLength; 100 101 } 102 } 103 } 104 }
代碼已經放置在GitHub上:https://github.com/JackWangCUMT/DDHelper