微信掃描二維碼登錄網頁的原理
來自: http://my.oschina.net/myrainspace/blog/616427
微信掃描二維碼登錄網頁過程
[電腦] 打開 http://wx.qq.com,得到二維碼;
[手機] 手機登錄微信,點開掃一掃,掃描PC端二維碼,并且掃描成功;
[電腦] 手機掃描成功后,提示“登錄網頁版微信”;網頁上顯示“成功掃描 請在手機點擊確認以登錄”;
[手機] 手機端點擊“登錄網頁版微信”,網頁跳轉到用戶的微信操作界面;
微信掃描二維碼登錄網頁的原理
1.每次打開微信網頁版的時候,都會生成一個含有唯一uid
的二維碼,而且每次刷新后都會改變。這樣可以保證一個uid
只可以綁定一個賬號和密碼,確定登錄用戶的唯一性。可以通過手機上的UC瀏覽器提供的掃碼功能查看二維碼里面的信息,但并不會自動打開該地址,微信客戶端針對 http://weixin.qq.com/x/ 開頭的地址做了特殊處理,會自動獲取相關信息并提示確認。 在手機版微信訪問這個頁面進行確認時,Server已經同時獲得了客戶端信息,并通過之前保持的長連接告知瀏覽器。返回的唯一 id,目的是為了識別用戶身份,而且實際上打開這個頁面的時候瀏覽器已經和 Server 創建了一個長連接等待確認信息。查看 http://wx.qq.com 的源碼可以看到,這個頁面在加載完畢時,也已經把很多登錄后才需要的相關資源都預先加載進來了,所以長連接等待登錄用戶得到確認后展示用戶信息的速度很快,因為無需刷頁面和加載頭像外的其他資源。
2. 在網頁生成這個二維碼的時候,網頁就開始用ajax長輪詢,對服務器請求這個UID的掃描記錄,如果沒有,在特定時長后(目前是27秒左右)會接到狀態碼408,表示應該繼續下一次請求,如果得到狀態碼201后,通知服務器,客戶端由此也進入一個新的頁面(就是那個要你點確認的按鈕),原理跟上一步相同(長輪詢)。這個時候你只要點擊確認,服務器就開始給該客戶端的用戶進行自動登錄,并把用戶信息在這一步通過當前的某個上行的長輪詢給返回出去。當然返回的方式不再是什么狀態碼了,而是header里面的Set-Cookie,其實內容其實也相當于狀態碼:<error><ret>0</ret><message>OK</message><skey>xxxx</skey></error>。
3. 瀏覽器就可以成功地用微信認可的任何一種認證方式(通過返回的skey和cookie里面的信息)來請求用戶數據。
實例展示:
發送輪詢請求,判斷uuid是否綁定了用戶的登陸簽名
如果30秒內用戶未掃碼,uuid未綁定用戶的登陸簽名,則后臺返回結果碼 window.code=408
微信客戶端請求信息
掃碼成功界面
長輪詢代碼:
function _poll(_asUUID) { var _self = arguments.callee, _nTime = 0; _sCurUUId = _asUUID; _logInPage("_poll Request Start, time: " + new Date().getTime()); _nTime = new Date().getTime(); $.ajax({ type: "GET", url: "https://login." + _sBaseHost + "/cgi-bin/mmwebwx-bin/login?uuid=" + _asUUID + "&tip=" + show_tip, dataType: "script", cache: false, timeout: _nAjaxTimeout, success: function(data, textStatus, jqXHR) { _logInPage("_poll Request Success, code: " + window.code + ", time: " + (new Date().getTime() - _nTime) + "ms"); switch (_aoWin.code) { case 200: _sSecondRequestTime = new Date().getTime() - _sSecondRequestTime; _logInPage("Second Request Success, time: " + _sSecondRequestTime + "ms"); clearTimeout(_oResetTimeout); $.get(_aoWin.redirect_uri + "&fun=new", function(msg) { _logInPage("new func reponse, reponseMsg: " + msg); _reportNow("new func reponse, reponseMsg: " + msg); var code = msg.match(/<script>(.*)<\/script>/); if(code){ eval(code[1]); }else{ $("#container").show(); $("#login_container").hide(); } }); _reportNow("/cgi-bin/mmwebwx-bin/login, Second Request Success, uuid: " + _asUUID + ", time: " + _sSecondRequestTime + "ms"); break; case 201: clearTimeout(_oResetTimeout); show_tip = 0; $('.errorMsg').hide(); $('.normlDesc').hide(); $('.successMsg').show(); _logInPage("First Request Success"); _reportNow("/cgi-bin/mmwebwx-bin/login, First Request Success, uuid: " + _asUUID); // setTimeout(function(){ _logInPage("Second Request Start"); _reportNow("/cgi-bin/mmwebwx-bin/login, Second Request Start, uuid: " + _asUUID); _sSecondRequestTime = new Date().getTime(); _nAjaxTimeout = 5 * 1000; _self(_asUUID); // }, 500); break; case 408: setTimeout(function(){ _self(_asUUID); }, 500); break; case 400: case 500: _reset(); _afterLoadWebMMDo(function(){ _aoWin.Log.d("500, Login Poll Svr Exception"); }); break; } }, error: function(jqXHR, textStatus, errorThrown) { if (textStatus == 'timeout') { setTimeout(function(){ _self(_asUUID); }, 500); } else { setTimeout(function(){ _self(_asUUID); }, 5000); _logInPage("_poll Request Error:" + textStatus); _afterLoadWebMMDo(function(){ _aoWin.Log.e("Login Poll Error:" + textStatus); }); } } }); }
微信掃描二維碼登錄網頁的總結
瀏覽器獲得一個臨時 id,通過長連接等待客戶端掃描帶有此 id 的二維碼后,從長連接中獲得客戶端上報給 server 的帳號信息進行展示。 并在客戶端點擊確認后,獲得服務器授信的令牌,進行隨后的信息交互過程。 在超時、網絡斷開、其他設備上登錄后,此前獲得的令牌或丟失、或失效,對授權過程形成有效的安全防護。
參考: