全面的.NET微信網頁開發之JS-SDK使用步驟、配置資訊和介面請求籤名生成詳解

追逐時光者發表於2023-12-03

JSSDK使用步驟

步驟一:繫結安全域名:

先登入微信公眾平臺進入“公眾號設定”的“功能設定”裡填寫“JS介面安全域名”。

步驟二:引入JS檔案:

  • 在需要呼叫JS介面的頁面引入如下JS檔案,(支援https):http://res.wx.qq.com/open/js/jweixin-1.6.0.js
  • 如需進一步提升服務穩定性,當上述資源不可訪問時,可改訪問:http://res2.wx.qq.com/open/js/jweixin-1.6.0.js (支援https)。

備註:支援使用 AMD/CMD 標準模組載入方法載入。

在專案中引入:

<script src="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>

步驟三:透過config介面注入許可權驗證配置:

注意:所有需要使用JS-SDK的頁面必須先注入配置資訊,否則將無法呼叫(同一個url僅需呼叫一次,對於變化url的SPA的web app可在每次url變化時進行呼叫,目前Android微信客戶端不支援pushState的H5新特性,所以使用pushState來實現web app的頁面會導致簽名失敗,此問題會在Android6.2中修復)。

wx.config({
  debug: true, // 開啟除錯模式,呼叫的所有api的返回值會在客戶端alert出來,若要檢視傳入的引數,可以在pc端開啟,引數資訊會透過log打出,僅在pc端時才會列印。
  appId: '', // 必填,公眾號的唯一標識
  timestamp: , // 必填,生成簽名的時間戳
  nonceStr: '', // 必填,生成簽名的隨機串
  signature: '',// 必填,簽名
  jsApiList: [] // 必填,需要使用的JS介面列表
});

步驟四:透過ready介面處理成功驗證:

注意:假如需要在頁面載入時就呼叫的話,需要把對應的執行函式放到wx.ready(function(){});方法裡面載入執行,之前我呼叫載入就獲取地理位置的介面就是因為沒有放到這裡面所以一直沒有獲取到使用者當前經緯度座標。

wx.ready(function(){
    // config資訊驗證後會執行ready方法,所有介面呼叫都必須在config介面獲得結果之後,config是一個客戶端的非同步操作,所以如果需要在頁面載入時就呼叫相關介面,則須把相關介面放在ready函式中呼叫來確保正確執行。對於使用者觸發時才呼叫的介面,則可以直接呼叫,不需要放在ready函式中。
});  

步驟五:透過error介面處理失敗驗證:

wx.error(function(res){
    // config資訊驗證失敗會執行error函式,如簽名過期導致驗證失敗,具體錯誤資訊可以開啟config的debug模式檢視,也可以在返回的res引數中檢視,對於SPA可以在這裡更新簽名。
});

介面呼叫說明:

所有介面透過wx物件(也可使用jWeixin物件)來呼叫,引數是一個物件,除了每個介面本身需要傳的引數之外,還有以下通用引數:

  1. success:介面呼叫成功時執行的回撥函式。
  2. fail:介面呼叫失敗時執行的回撥函式。
  3. complete:介面呼叫完成時執行的回撥函式,無論成功或失敗都會執行。
  4. cancel:使用者點選取消時的回撥函式,僅部分有使用者取消操作的api才會用到。
  5. trigger: 監聽Menu中的按鈕點選時觸發的方法,該方法僅支援Menu中的相關介面。

備註:不要嘗試在trigger中使用ajax非同步請求修改本次分享的內容,因為客戶端分享操作是一個同步操作,這時候使用ajax的回包會還沒有返回。

以上幾個函式都帶有一個引數,型別為物件,其中除了每個介面本身返回的資料之外,還有一個通用屬性errMsg,其值格式如下:

呼叫成功時:"xxx:ok" ,其中xxx為呼叫的介面名

使用者取消時:"xxx:cancel",其中xxx為呼叫的介面名

呼叫失敗時:其值為具體錯誤資訊

獲取access_token(公眾號的全域性唯一介面呼叫憑據)

access_token是公眾號的全域性唯一介面呼叫憑據,公眾號呼叫各介面時都需使用access_token。開發者需要進行妥善儲存。access_token的儲存至少要保留512個字元空間。access_token的有效期目前為2個小時,需定時重新整理,重複獲取將導致上次獲取的access_token失效。

返回引數說明

正常情況下(即請求成功時),微信只會返回下述JSON資料包給公眾號:

{"access_token":"ACCESS_TOKEN","expires_in":7200}

錯誤時微信會返回錯誤碼等資訊,JSON資料包示例如下(該示例為AppID無效錯誤):

{"errcode":40013,"errmsg":"invalid appid"}

透過介面獲取程式碼

        /// <summary>
        /// 獲取微信公眾號全域性唯一介面憑證
        /// </summary>
        /// <returns></returns>
        public static string RequestAccessToken()
        {   // 設定引數
            string appid=WxAppId;//第三方使用者唯一憑證
            string appsecret=WxAppSecret;//第三方使用者唯一憑證金鑰,即appsecret
            
            //請求介面獲取
            string _url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + appsecret;
            string method = "GET";
            HttpWebRequest request = WebRequest.Create(_url) as HttpWebRequest;
            CookieContainer cookieContainer = new CookieContainer();
            request.CookieContainer = cookieContainer;
            request.AllowAutoRedirect = true;
            request.Method = method;
            request.ContentType = "text/html";
            request.Headers.Add("charset", "utf-8");

            //傳送請求並獲取響應資料
            HttpWebResponse response = request.GetResponse() as HttpWebResponse;
            Stream responseStream = response.GetResponseStream();
            StreamReader sr = new StreamReader(responseStream, Encoding.UTF8);
            //獲取返回過來的結果
            string content = sr.ReadToEnd();

            dynamic resultContent=JsonConvert.DeserializeObject(content,new { access_token="", expires_in="", errcode="", errmsg="" }.GetType());
            if (resultContent!=null&&!string.IsNullOrWhiteSpace(resultContent.access_token)) //注意:請求成功時是不會有errcode=0返回,判斷access_token是否有值即可
            {
                return resultContent.access_token;//返回請求唯一憑證
            }
            else
            {
                //請求失敗,返回為空
                return "";
            }
        }

獲取jsapi_ticket微信公眾號呼叫微信JS介面的臨時票據

注意:前提是先要獲取到了公眾號全域性唯一介面呼叫憑據(accessToken)。

        /// <summary>
        /// 獲取jsapi_ticket微信公眾號呼叫微信JS介面的臨時票據
        /// </summary>
        /// <param name="accessToken">微信公眾號的全域性唯一介面呼叫憑證</param>
        /// <returns></returns>
        public static string RequestJsapi_ticket(string accessToken)
        {
            string content = "";
            try
            {
                //TODO:注意api_ticket 是用於呼叫微信卡券JS API的臨時票據,有效期為7200 秒,透過access_token 來獲取。

                string url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accessToken + "&type=jsapi";
                string method = "GET";
                HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
                CookieContainer cookieContainer = new CookieContainer();
                request.CookieContainer = cookieContainer;
                request.AllowAutoRedirect = true;
                request.Method = method;
                request.ContentType = "text/html";
                request.Headers.Add("charset", "utf-8");
                //傳送請求並獲取響應資料
                HttpWebResponse response = request.GetResponse() as HttpWebResponse;
                Stream responseStream = response.GetResponseStream();
                StreamReader sr = new StreamReader(responseStream, Encoding.UTF8);
                //獲取返回過來的結果
                content = sr.ReadToEnd();
                dynamic resultStr = JsonConvert.DeserializeObject(content,new { errcode="", errmsg="",ticket="", expires_in=""}.GetType());

                //請求成功
                if (resultStr.errcode=="0"&&resultStr.errmsg=="ok")
                {
                    content=resultStr.ticket;
                }
                else
                {
                    content = "";
                }
               
                return content;
            }
            catch (Exception ex)
            {
                content = ex.Message;
                return content;
            }
        }

生成簽名的隨機串(nonceStr)

方法一:

        /// <summary>
        /// 隨機字串陣列集合
        /// </summary>
        private static readonly string[] NonceStrings = new string[]
                                    {
                                    "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
                                    "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"
                                    };

        /// <summary>
        /// 生成簽名的隨機串
        /// </summary>
        /// <returns></returns>
        public static string CreateNonceStr()
        {
            Random random = new Random();
            var sb = new StringBuilder();
            var length = NonceStrings.Length;

            //生成15位數的隨機字串,當然也可以透過控制對應字串大小生成,但是至多不超過32位
            for (int i = 0; i < 15; i++)
            {
                sb.Append(NonceStrings[random.Next(length - 1)]);//透過random獲得的隨機索引到,NonceStrings陣列中獲取對應陣列值
            }
            return sb.ToString();
        }

方法二:

string nonceStr = Guid.NewGuid().ToString().Replace("-", "").Substring(0,15);

生成簽名時間戳(timestamp)

        /// <summary>
        /// 獲取當前時間戳
        /// </summary>
        /// <returns></returns>
        public static long GetCurrentUinxTime()
        {
            DateTime currentDate = DateTime.Now;//當前時間
            //轉化為時間戳
            DateTime localTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
            return long.Parse((currentDate - localTime).TotalSeconds.ToString().Split('.')[0]);
        }

獲取當前網頁URL

後端

注意:一定要是在安全域名內,否則生成的是無效的簽名(url必須是呼叫JS介面頁面的完整URL)。

        //獲取當前網頁完整的URL(包括URL中的引數)
        string currentWebUrl = Request.Url.ToString();

前端

        //獲取當前網頁完整的URL(包括URL中的引數)
        var currentWebUrl =self.location.href;

生成簽名(signature)

簽名演算法說明

簽名生成規則如下:參與簽名的欄位包括noncestr(隨機字串), 有效的jsapi_ticket, timestamp(時間戳), url(當前網頁的URL,不包含#及其後面部分) 。對所有待簽名引數按照欄位名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字串string1。這裡需要注意的是所有引數名均為小寫字元。對string1作sha1加密,欄位名和欄位值都採用原始值,不進行URL 轉義。

即signature=sha1(string1)。 示例:

noncestr=Wm3WZYTPz0wzccnW
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg
timestamp=1414587457
url=http://mp.weixin.qq.com?params=value

步驟1. 對所有待簽名引數按照欄位名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字串string1:

jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW&timestamp=1414587457&url=http://mp.weixin.qq.com?params=value

步驟2. 對string1進行sha1簽名,得到signature:

0f9de62fce790f9a083d5c99e95740ceb90c27ed

注意事項

  1. 簽名用的noncestr和timestamp必須與wx.config中的nonceStr和timestamp相同。
  2. 簽名用的url必須是呼叫JS介面頁面的完整URL。
  3. 出於安全考慮,開發者必須在伺服器端實現簽名的邏輯。

獲取簽名程式碼

        /// <summary>
        /// 獲取簽名
        /// </summary>
        /// <param name="jsapi_ticket">微信公眾號呼叫微信JS臨時票據</param>
        /// <param name="nonceStr">隨機串</param>
        /// <param name="timestamp">時間戳</param>
        /// <param name="url">當前網頁URL</param>
        /// <returns></returns>
        public static string GetSignature(string jsapi_ticket, string nonceStr, long timestamp, string url)
        {

            var string1Builder = new StringBuilder();
            //注意這裡引數名必須全部小寫,且必須有序
            string1Builder.Append("jsapi_ticket=").Append(jsapi_ticket).Append("&")
                          .Append("noncestr=").Append(nonceStr).Append("&")
                          .Append("timestamp=").Append(timestamp).Append("&")
                          .Append("url=").Append(url.IndexOf("#") >= 0 ? url.Substring(0, url.IndexOf("#")) : url);

            return Sha1(string1Builder.ToString(),Encoding.UTF8);
        }

        /// <summary>
        /// 簽名加密,使用SHA加密所得
        /// </summary>
        /// <param name="content">簽名加密引數</param>
        /// <param name="encode">編碼UTF-8</param>
        /// <returns></returns>
        public static string Sha1(string content, Encoding encode)
        {
            try
            {
                SHA1 sha1 = new SHA1CryptoServiceProvider();
                byte[] bytesIn = encode.GetBytes(content);
                byte[] bytesOut = sha1.ComputeHash(bytesIn);
                sha1.Dispose();
                string result = BitConverter.ToString(bytesOut);
                result = result.Replace("-", "");
                return result;
            }
            catch (Exception ex)
            {
                throw new Exception("SHA1加密出錯:" + ex.Message);
            }
        }

invalid signature簽名錯誤排查

假如你遇到簽名錯誤的情況,建議你按照以下順序進行排查。

  1. 確認簽名演算法正確,可用http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 頁面工具進行校驗。

  2. 確認config中nonceStr(js中駝峰標準大寫S), timestamp與用以簽名中的對應noncestr, timestamp一致。

  3. 確認url是頁面完整的url(請在當前頁面alert(location.href.split('#')[0])確認),包括'http(s)://'部分,以及'?'後面的GET引數部分,但不包括'#'hash後面的部分。

  4. 確認 config 中的 appid 與用來獲取 jsapi_ticket 的 appid 一致。

  5. 確保一定快取access_token和jsapi_ticket。

  6. 確保你獲取用來簽名的url是動態獲取的,動態頁面可參見例項程式碼中php的實現方式。如果是html的靜態頁面在前端透過ajax將url傳到後臺簽名,前端需要用js獲取當前頁面除去'#'hash部分的連結(可用location.href.split('#')[0]獲取,而且需要encodeURIComponent),因為頁面一旦分享,微信客戶端會在你的連結末尾加入其它引數,如果不是動態獲取當前連結,將導致分享後的頁面簽名失敗。

DotNetGuide技術社群交流群

  • DotNetGuide技術社群是一個面向.NET開發者的開源技術社群,旨在為開發者們提供全面的C#/.NET/.NET Core相關學習資料、技術分享和諮詢、專案推薦、招聘資訊和解決問題的平臺。
  • 在這個社群中,開發者們可以分享自己的技術文章、專案經驗、遇到的疑難技術問題以及解決方案,並且還有機會結識志同道合的開發者。
  • 我們致力於構建一個積極向上、和諧友善的.NET技術交流平臺,為廣大.NET開發者帶來更多的價值和成長機會。

歡迎加入DotNetGuide技術社群微信交流群?

參考資料