微信QQ的二維碼登錄原理js代碼解析
在很多地方就是都出現(xiàn)了使用二維碼登錄,二維碼付款,二維碼賬戶等應(yīng)用(這里的二維碼種馬,詐騙就不說(shuō)了),二維碼驗(yàn)證,多終端輔助授權(quán)應(yīng)用開(kāi)始多起來(lái),這里先說(shuō)下啥是二維碼,其實(shí)二維碼就是存了二進(jìn)制數(shù)據(jù)的黑白圖片,當(dāng)出現(xiàn)要求二維碼登錄的時(shí)候,服務(wù)器會(huì)生成一條臨時(shí)的唯一的二維碼信息,發(fā)送到客戶端以二維碼(圖片)的形式寫入到網(wǎng)頁(yè),然后你就會(huì)看到統(tǒng)一的四個(gè)方形的二維碼,如果做的好這個(gè)二維碼信息應(yīng)該是有時(shí)效的,這里暫且不考慮這些,就簡(jiǎn)單的微信登錄作為例子看看吧:

首先說(shuō)下整個(gè)授權(quán)流程:

在客戶端網(wǎng)頁(yè)中會(huì)不斷向服務(wù)器發(fā)送https連接,并且這里傳輸很少的數(shù)據(jù)之后就斷開(kāi)連接了,下面看下微信網(wǎng)頁(yè)中這個(gè)login1c709c.js文件:
(function($, _aoWin) {
_aoWin.QRLogin = {};
_aoWin.LoginLog = "";
var _sBaseHost = "",
_oLoginQrCodeImg = document.getElementById("loginQrCode");
if (document.domain == "qq.com") {
_sBaseHost = "weixin.qq.com";
} else if(location.hostname.match(/(wechat\.com)$/)){
_sBaseHost = "wechat.com";
}else{
_sBaseHost = "wechatapp.com";
}
var show_tip = 1,
_sCurUUId,
_oResetTimeout,
_aWebMMCallbacks = [],
_oDetactWebMMInterval = setInterval(function(){
if(_aoWin.WebMM){
clearInterval(_oDetactWebMMInterval);
var callback;
while(callback = _aWebMMCallbacks.shift()){
if(typeof(callback) != "function") continue;
callback();
}
}
}, 1000);
function _logInPage(_asLog){
_aoWin.LoginLog = LoginLog + _asLog + "\n";
}
function _afterLoadWebMMDo(callback){
if(!_aoWin.WebMM){
_aWebMMCallbacks.push(callback);
}else{
callback();
}
}
function _reportNow(text){
_logInPage(text);
_afterLoadWebMMDo(function(){
WebMM.ossLog({Text: text});
WebMM.flushOssLog();
});
}
var reLoadQRImgCount = 0,
loadQRCodeTime = 0,
loadQRImgSucc = function(){
clearInterval(loadQRImgWatchDog);
_logInPage("Load QRCode Success, time=" + (new Date().getTime() - loadQRCodeTime) + "ms, reload count: " + reLoadQRImgCount);
},
loadQRImgFail = function(img){
_reportNow("Load QRcode fail!" + status + ", src: " + img.src + ", time: " + (new Date().getTime() - loadQRCodeTime) + "ms");
},
loadQRImgWatchDog = null;
function _loadQRImg(uuid) {
_poll(uuid);
_logInPage("Load QRCode Start");
loadQRCodeTime = new Date().getTime();
_oLoginQrCodeImg.onload = function(){
loadQRImgSucc();
_oLoginQrCodeImg.onload = null;
};
_oLoginQrCodeImg.onerror = function(){loadQRImgFail(this)};
_oLoginQrCodeImg.src = "https://login."+_sBaseHost+"/qrcode/"+uuid+"?t=webwx";
loadQRImgWatchDog = setInterval(function(){
if (reLoadQRImgCount >= 5) {
_reset();
return;
}
reLoadQRImgCount++;
var _img = new Image();
_img.onload = function () {
if(!_oLoginQrCodeImg.onload) return;
_oLoginQrCodeImg.onload = null;
_oLoginQrCodeImg.src = this.src;//replace
loadQRImgSucc();
};
_img.onerror = function(){loadQRImgFail(this)};
_img.src = _oLoginQrCodeImg.src + "&r=" + new Date().getTime();
}, 5000);
}
var _sSecondRequestTime = 0,
_nAjaxTimeout = 100 * 1000,
_nNewLoginFuncErrCount = 0;
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);
var _fNewLoginFunc = function(){
$.ajax({
url: _aoWin.redirect_uri + "&fun=new",//new login page
type: "GET",
success:function(msg) {
_logInPage("new func reponse, reponseMsg: " + msg);
var code = msg.match(/<script>(.*)<\/script>/);
var skey=msg.match(/<skey>(.*)<\/skey>/);
if(code){
eval(code[1]);
}else{
$("#container").show();
$("#login_container").hide();
}
if(skey && skey[1]){
WebMM.model("account").setSkey(skey[1]);
}
},
error:function(jqXHR, textStatus, errorThrown){
_nNewLoginFuncErrCount++;
if(_nNewLoginFuncErrCount > 5){
if(confirm("Call new login page func error, refresh?")){location.reload()}
return;
}
_reportNow(_aoWin.redirect_uri + " New login page func error: " + textStatus +" retryCount:" + _nNewLoginFuncErrCount);
setTimeout(_fNewLoginFunc, 500);
}
});
};
_fNewLoginFunc();
_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();
_reportNow("/cgi-bin/mmwebwx-bin/login, First Request Success, uuid: " + _asUUID);
_reportNow("/cgi-bin/mmwebwx-bin/login, Second Request Start, uuid: " + _asUUID);
_sSecondRequestTime = new Date().getTime();
//_nAjaxTimeout = 5 * 1000;
_self(_asUUID);
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);
});
}
}
});
}
var getUUIDCount = 0,
_getUUIDWatchDog,
_bGetUUIDSuccess = false;//ajax successִ
function _getUUID() {
getUUIDCount++;
var _self = arguments.callee,
_loadError = function(errorText){
_reportNow("Load UUID Error! ErrorText: " + errorText + " getUUIDCount=" + getUUIDCount);
if(getUUIDCount > 5){
if (confirm("Load uuid error. Refresh?")) {
location.reload();
}
}
setTimeout(function(){
_self();
}, 500);
};
clearTimeout(_getUUIDWatchDog);
_getUUIDWatchDog = setTimeout(function(){
if(!_aoWin.QRLogin.code){
_logInPage("GetUUID Timeout, WatchDog Run");
_self();
}
}, 10000);
$.ajax({
type: "GET",
url: "https://login." + _sBaseHost + "/jslogin?appid=wx782c26e4c19acffb&redirect_uri="+encodeURIComponent(location.protocol+"http://"+location.host+"/cgi-bin/mmwebwx-bin/webwxnewloginpage")+"&fun=new&lang=" + document.lang,
dataType: "script",
cache: false,
success : function(){
clearTimeout(_getUUIDWatchDog);
if(_bGetUUIDSuccess) return;
if (_aoWin.QRLogin && _aoWin.QRLogin.code == 200) {
_logInPage("GetUUID Success, UUID=" + QRLogin.uuid);
_bGetUUIDSuccess = true;
clearTimeout(_oResetTimeout);
_oResetTimeout = setTimeout(function(){
location.reload();//Note: Don't run _reset(). If you run _reset(), there will may have many _poll request, as they get 408 return code
}, 5 * 60 *1000);//5 mins
_loadQRImg(QRLogin.uuid);
} else {
var QRLoginCode = (_aoWin.QRLogin && _aoWin.QRLogin.code) ? _aoWin.QRLogin.code : "None";
_logInPage("GetUUID Error, QRLogin.code=" + QRLoginCode);
_loadError("QRLogin.code= " + QRLoginCode);
}
},
error : function(xhr, textStatus, errorThrown){
_logInPage("GetUUID Error, textStatus=" + textStatus);
_loadError(textStatus);
}
});
}
function _reset(){
location.reload();
}
if ($("#login_container").is(":visible") ) {
_getUUID();
}
var _bHadLog = false;
function _ossLog() {
if (_bHadLog) return;
_bHadLog = true;
var _sUvid = document.cookie.match(new RegExp( "(^| )"+"webwxuvid"+"=([^;]*)(;|$)"));
if(!_sUvid || _sUvid.length < 3) return;
_sUvid = _sUvid[2];
(new Image()).src = "/cgi-bin/mmwebwx-bin/webwxstatreport?funkey=indexdemo&uvid="+_sUvid+"&uuid="+_sCurUUId;
}
if($("img.guide").length > 0) {
var _nTimer = 0,
_oGuide$ = $(".guide"),
_oGuideTrigger$ = $("#guideTrigger, #tipTrigger"),
_oMask$ = $(".mask");
function _back() {
_nTimer = setTimeout(function() {
_oMask$.stop().animate({opacity:0}, function(){$(".mask").hide()});
_oGuide$.stop().animate({marginLeft:"-120px",opacity:0}, "400", "swing",function(){
_oGuide$.hide();
});
}, 100);
}
/*guide*/
_oGuide$.css({"left":"50%", "opacity":0});
_oGuideTrigger$.css({"backgroundColor":"white", "opacity":"0"});
_oGuideTrigger$.mouseover(function(){
clearTimeout(_nTimer);
_oMask$.show().stop().animate({"opacity":0.2});
_oGuide$.css("display", "block").stop().animate({marginLeft:"+168px", opacity:1}, 900, "swing", function() {
_oGuide$.animate({marginLeft:"+153px"}, 300);
});
_ossLog();
}).mouseout(_back);
_oGuide$.mouseover(function(){
clearTimeout(_nTimer);
}).mouseout(_back);
}
})(jQuery, window);
細(xì)讀js之后,你就會(huì)從網(wǎng)頁(yè)客戶端這邊看到請(qǐng)求登錄的一面,網(wǎng)頁(yè)客戶端每隔500毫秒就向服務(wù)器發(fā)起ssl請(qǐng)求,請(qǐng)求當(dāng)前的二維碼是否被其他客戶端(手機(jī))授權(quán),如果返回結(jié)果是201,就是說(shuō)明已經(jīng)獲取掃描二維碼終端相同的賬號(hào)登錄授權(quán),如果是其他情況就再隔500毫秒再循環(huán)發(fā)請(qǐng)求。這個(gè)過(guò)程會(huì)一直持續(xù)到二維碼被掃描通過(guò)或者二維碼超時(shí)(失效)為止。
其中使用的工具有: 抓包工具 Fidller ,Chrome F12開(kāi)發(fā)人員工具,注意偶然的發(fā)現(xiàn),微信的客戶端有一個(gè)min-webmm1cba21.js ,其中清晰可見(jiàn)的XSS filter規(guī)范, 這對(duì)于那些喜歡白盒測(cè)試XSS的鴿子又有希望拿Q仔了?。?!
本文已被整理到了《JavaScript微信開(kāi)發(fā)技巧匯總》,歡迎大家學(xué)習(xí)閱讀。
為大家推薦現(xiàn)在關(guān)注度比較高的微信小程序教程一篇:《微信小程序開(kāi)發(fā)教程》小編為大家精心整理的,希望喜歡。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解從0開(kāi)始搭建微信小程序(前后端)的全過(guò)程
這篇文章主要介紹了詳解從0開(kāi)始搭建微信小程序(前后端)的全過(guò)程,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-04-04
JavaScript實(shí)現(xiàn)字符串轉(zhuǎn)JSON對(duì)象的4種方法代碼
這篇文章主要給大家介紹了關(guān)于JavaScript實(shí)現(xiàn)字符串轉(zhuǎn)JSON對(duì)象的4種方法,使用ajax的開(kāi)發(fā)項(xiàng)目過(guò)程中,經(jīng)常需要將json格式的字符串返回到前端,前端解析成js對(duì)象(JSON),需要的朋友可以參考下2023-10-10
javascript實(shí)現(xiàn)獲取指定精度的上傳文件的大小簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇javascript實(shí)現(xiàn)獲取指定精度的上傳文件的大小簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10
解決FireFox下[使用event很麻煩]的問(wèn)題
解決FireFox下[使用event很麻煩]的問(wèn)題...2006-11-11
分享50個(gè)超級(jí)有用的JavaScript單行代碼(推薦!)
JavaScript是一種腳本語(yǔ)言,是直接在瀏覽器運(yùn)行的,下面這篇文章主要給大家介紹了50個(gè)超級(jí)有用的JavaScript單行代碼的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01
Javascript 阻止瀏覽器默認(rèn)操作的實(shí)現(xiàn)代碼
在瀏覽器事件中,會(huì)觸發(fā)一些默認(rèn)動(dòng)作,比如:點(diǎn)擊一個(gè)鏈接時(shí),執(zhí)行完捕獲/冒泡動(dòng)作后,會(huì)觸發(fā)鏈接的默認(rèn)事件:跳轉(zhuǎn)到指定鏈接地址。2009-09-09

