微信掃描二維碼登錄網頁的原理
來自: 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 的帳號信息進行展示。 并在客戶端點擊確認后,獲得服務器授信的令牌,進行隨后的信息交互過程。 在超時、網絡斷開、其他設備上登錄后,此前獲得的令牌或丟失、或失效,對授權過程形成有效的安全防護。
參考: