微信扫描二维码登录网页的原理

cyqv1737 8年前

来自: 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 的帐号信息进行展示。 并在客户端点击确认后,获得服务器授信的令牌,进行随后的信息交互过程。 在超时、网络断开、其他设备上登录后,此前获得的令牌或丢失、或失效,对授权过程形成有效的安全防护。


参考:

  1. 微信扫描二维码登录网页是什么原理,前后两个事件是如何联系的?

  2. 微信扫码登录网页实现原理