1. 程式人生 > >微信JSSDK簽名

微信JSSDK簽名

微信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值一致說明簽名是正確的,不一致就需要檢查程式邏輯問題了。

&n