微信JS-SDK說明文檔 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 生成簽名 1.簽名規則 參與簽名的欄位包括noncestr(隨機字元串), 有效的jsapi_ticket, timestamp(時間戳), ...
微信JS-SDK說明文檔
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
生成簽名
1.簽名規則
參與簽名的欄位包括noncestr(隨機字元串), 有效的jsapi_ticket, timestamp(時間戳), url(當前網頁的URL,不包含#及其後面部分) 。
對所有待簽名參數按照欄位名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字元串string1。
這裡需要註意的是所有參數名均為小寫字元。對string1作sha1加密,欄位名和欄位值都採用原始值,不進行URL 轉義。
2.註意事項
1.簽名用的noncestr和timestamp必須與wx.config中的nonceStr和timestamp相同。
2.簽名用的url必須是調用JS介面頁面的完整URL。
3.出於安全考慮,開發者必須在伺服器端實現簽名的邏輯。
4.調用介面時,請登錄“微信公眾平臺-開發-基本配置”提前將伺服器IP地址添加到IP白名單中,點擊查看設置方法,否則將無法調用成功。小程式無需配置IP白名單。
3.簽名邏輯
所知,簽名欄位有noncestr,jsapi_ticket,timestamp,url。那這四個值怎麼來呢?
noncestr:隨機字元串,可以直接生成。
string nonceStr=Guid.NewGuid().ToString("N");
jsapi_ticket:公眾號用於調用微信JS介面的臨時票據(簽名密鑰)。正常情況下,jsapi_ticket的有效期為7200秒,通過access_token來獲取。由於獲取jsapi_ticket的api調用次數非常有限,頻繁刷新jsapi_ticket會導致api調用受限,影響自身業務,開發者必須在自己的服務全局緩存jsapi_ticket 。
獲取jsapi_ticket時就要用到access_token了,access_token是公眾號的全局唯一介面調用憑據,公眾號調用各介面時都需使用access_token。
我們可以通過下麵的介面取得access_token
https請求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
這裡要用到3個參數(grant_type,appid,secret);
參數 | 是否必須 | 說明 |
grant_type | 是 | 獲取access_token填寫client_credential |
appid | 是 | 第三方用戶唯一憑證 |
secret | 是 | 第三方用戶唯一憑證密鑰,即appsecret |
其中,grant_type的值即為client_credential,AppID和AppSecret可在“微信公眾平臺-開發-基本配置”頁中獲得(需要已經成為開發者,且帳號沒有異常狀態)。
我在上篇隨筆記錄了AppID和AppSecret的獲取方式,鏈接如下:
https://www.cnblogs.com/p1024q/p/11321864.html
正常情況下,微信會返回如下JSON數據
{"access_token":"ACCESS_TOKEN","expires_in":7200}
其中, access_token的值就是我們要用的值,expires_in 是憑證有效時間(7200秒)
取到access_token後,要將值存到緩存不大於7200秒,再調用下麵的介面
http請求方式: GET https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
成功則返回如下JSON
{ "errcode":0, "errmsg":"ok", "ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA", "expires_in":7200 }
ticket值就是要用的jsapi_ticket。
timestamp:時間戳.。
url:當前網頁的URL,不包含#及其後面部分。作為介面請求參數通過前端傳過來。
有了這四個值,就能根據簽名規則進行簽名,得到簽名值signature。
簽名成功,需要返回下麵幾個參數給前端作驗簽使用。
參數名 | 類型 | 說明 |
timestamp | int | 生成簽名的時間戳 |
noncestr | string | 生成簽名的隨機串 |
signature | string | 簽名 |
廢話不多說,直接上後端簽名代碼:
1 static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();//日誌 2 3 public WXShare GetWxShareInfo(string url) 4 { 5 DateTime now = DateTime.Now; 6 var timestamp = DateTimeHelper.GetTimeStamp(now);//取十位時間戳 7 var guid = Guid.NewGuid().ToString("N");//隨機串 8 var ticket = "";//簽名密鑰 9 try { 10 WXShare s= new WXShare(); 11 //取緩存中的Ticket,沒有則重新生成Ticket值(也可以將Ticket值保存到文件中,此時從文件中讀取Ticket) 12 WxShareCache Cache = new WxShareCache(Key).GetCache<WxShareCache>(); 13 if (Cache == null || string.IsNullOrWhiteSpace(Cache.Ticket)) { 14 Cache = new WxShareCache(Key); 15 Cache.Ticket = GetTicket();//獲取Ticket值 16 Cache.SetCache(Cache);//添加緩存 17 ticket = Cache.Ticket; 18 } else { 19 ticket = Cache.Ticket; 20 } 21 url = HttpUtility.UrlDecode(url);//url解碼 22 string sign = GetSignature(ticket,guid,timestamp,url); 23 s.noncestr = guid; 24 s.timestamp = timestamp; 25 s.sign = sign; 26 logger.Warn($"url:{url},時間戳:{timestamp},隨機數:{guid},ticket:{ticket},sign值:{sign}");//記錄日誌 27 return s; 28 } catch (Exception ex) { 29 logger.Warn(ex); 30 throw ex; 31 } 32 }
返回給前端的對象
/// <summary> /// 返回實體 /// </summary> public class WXShare { /// <summary> /// 隨機碼 /// </summary> public string noncestr { get; set; } /// <summary> /// 時間戳 /// </summary> public int timestamp { get; set; } /// <summary> /// 簽名值 /// </summary> public string signature { get; set; } }
時間戳
/// <summary> /// 十位時間戳 /// </summary> /// <param name="dt"></param> /// <returns></returns> public static int GetTimeStamp(DateTime dt) { DateTime dateStart = new DateTime(1970, 1, 1, 8, 0, 0); int timeStamp = Convert.ToInt32((dt - dateStart).TotalSeconds); return timeStamp; }
請求方法
//請求基類 private static HttpClient _client = null; public static HttpClient Client { get { if (_client == null) { var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip, AllowAutoRedirect = false, UseCookies = false, }; _client = new HttpClient(handler); _client.Timeout = TimeSpan.FromSeconds(5); _client.DefaultRequestHeaders.Add("Accept","application/json"); } return _client; } }
簽名密鑰
/// <summary> /// GetTicket /// </summary> /// <returns></returns> public static string GetTicket() { string token = GetAccessToken();//獲取AccessToken IDictionary<string,string> dic = new Dictionary<string,string>(); dic["access_token"] = token; dic["type"] = "jsapi"; FormUrlEncodedContent content = new FormUrlEncodedContent(dic); var response = Client.PostAsync("https://api.weixin.qq.com/cgi-bin/ticket/getticket",content).Result; if (response.StatusCode != HttpStatusCode.OK) return ""; var result = response.Content.ReadAsStringAsync().Result; JObject obj = JObject.Parse(result); string ticket = obj["ticket"]?.ToString()??""; return ticket; }
AccessToken
/// <summary> /// GetAccessToken /// </summary> /// <returns></returns> public static string GetAccessToken() { IDictionary<string,string> dic = new Dictionary<string,string>(); dic["grant_type"] = "client_credential"; dic["appid"] = "";//自己的appid dic["secret"] = "";//自己的appsecret FormUrlEncodedContent content = new FormUrlEncodedContent(dic); var response = Client.PostAsync("https://api.weixin.qq.com/cgi-bin/token",content).Result; if (response.StatusCode != HttpStatusCode.OK) return ""; var result = response.Content.ReadAsStringAsync().Result; JObject obj = JObject.Parse(result); string token = obj["access_token"]?.ToString()??""; return token; }
簽名演算法
/// <summary> /// 簽名演算法 /// </summary> /// <param name="ticket">ticket</param> /// <param name="noncestr">隨機字元串</param> /// <param name="timestamp">時間戳</param> /// <param name="url"></param> /// <returns></returns> public static string GetSignature(string ticket,string noncestr,long timestamp,string url) { var string1Builder = new StringBuilder(); //拼接字元串 string1Builder.Append("jsapi_ticket=").Append(ticket).Append("&") .Append("noncestr=").Append(noncestr).Append("&") .Append("timestamp=").Append(timestamp).Append("&") .Append("url=").Append(url.IndexOf("#") >= 0 ? url.Substring(0,url.IndexOf("#")) : url); string str = string1Builder.ToString(); return SHA1(str);//加密 }
SHA1加密
public static string SHA1(string content) { return SHA1(content,Encoding.UTF8); } /// <summary> /// SHA1 加密 /// </summary> /// <param name="content">需要加密字元串</param> /// <param name="encode">指定加密編碼</param> /// <returns>返回40位小寫字元串</returns> public static string SHA1(string content,Encoding encode) { try { SHA1 sha1 = new SHA1CryptoServiceProvider(); byte[] bytes_in = encode.GetBytes(content); byte[] bytes_out = sha1.ComputeHash(bytes_in); sha1.Dispose(); string result = BitConverter.ToString(bytes_out); result = result.Replace("-","").ToLower();//轉小寫 return result; } catch (Exception ex) { throw new Exception("SHA1加密出錯:" + ex.Message); } }
前端驗簽
1.引入JS文件
在需要調用JS介面的頁面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.4.0.js
2.調用後端介面並註入許可權驗證
$(function(){ var url=encodeURIComponent(location.href.split('#')[0]); //對當前url編碼 //ajax註入許可權驗證 $.ajax({ url:"ajax", type:'GET', data: {url:url}, error: function(XMLHttpRequest, textStatus, errorThrown){ alert("發生錯誤:"+errorThrown); }, success: function(res){ var appId = "";//與後端的appid相同 var noncestr = res.noncestr; var timestamp = res.timestamp; var signature = res.signature; wx.config({ debug: true, //開啟調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時才會列印。 appId: appId, //必填,公眾號的唯一標識 timestamp: timestamp, // 必填,生成簽名的時間戳 nonceStr: noncestr, //必填,生成簽名的隨機串 signature: signature,// 必填,簽名 jsApiList: ['onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ', 'onMenuShareWeibo','onMenuShareQZone','chooseImage', 'uploadImage','downloadImage','startRecord','stopRecord', 'onVoiceRecordEnd','playVoice','pauseVoice','stopVoice', 'translateVoice','openLocation','getLocation','hideOptionMenu', 'showOptionMenu','closeWindow','hideMenuItems','showMenuItems', 'showAllNonBaseMenuItem','hideAllNonBaseMenuItem','scanQRCode'] //必填,需要使用的JS介面列表,所有JS介面列表 }); } }); });
至此,後端簽名,前端驗簽過程結束。
在這過程中,掉過幾次坑。最讓我印象深刻的是測試的時候怎麼樣都是簽名錯誤(返回invalid signature)。考慮到可能是url的問題,所以在前端做了編碼,後端做瞭解碼,然後驗簽成功。
測試簽名正確與否,微信有個校驗工具,如下:
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
將簽名的四個參數輸入,生成的簽名與後端生成的簽名作對比,sign值一致說明簽名是正確的,不一致就需要檢查程式邏輯問題了。