微信扫码登陆原理
为便于理解来段非官方解释
1、你用浏览器打开http://wx.qq.com的时候,微信给你随机分配了一个链接,【相当于给你开了间房,房号1024,注意,只给你房号,没给你钥匙】,用二维码包装着,并且设置了有效时间【10分钟你不进房间,就给你取消】。这里面没有用户什么事情,所以不存在UID(user ID),只是一个随机的字母和数字组合。
2、二维码的转码规则是统一的,所以意味着,只要是个二维码扫描软件,谁都能拿到这个链接,微信可以扫出来,我查查也可以扫出来。
3、所以拿到链接没有用,重要的是谁拿到链接,微信拿到了,就可以从微信客户端发一条信息给服务器,告诉服务器,现在是谁使用了某个链接,其他二维码扫描软件,不能和微信服务器通话,所以毫无价值。【你拿到了房号,就给酒店老板打个电话,说是我,老板就知道张三又来开房了,其他人没有老板电话,知道房号也没用】
4、这时候,在你刚打开的浏览器窗口里面,就知道并显示了你的信息,理论上可以直接打开聊天窗口,但是为了不突兀不尴尬,微信选择再让你在手机上做一个确认操作。【你站到你的房间门口了,老板也知道你是张三了,并且把你的那个好基友也放到了你房间里,但是谁知道你基友会在房间里干点啥?如果他正好弯腰在捡肥皂,这时候恰好你后面有人经过,房门大开大家尴尬不尴尬?所以还是老板考虑周到,他要你在电话里确认一下才给你开门,你大可以等后面没人了再开门进去】
5、好了,现在你可以进去好好享用你的基友了。
每打开一次微信网页版页面的时候会随机生成一个含有唯一uid的二维码,每次刷新页面都会不一样(这个可以保证一个uid只可以绑定一个账号和密码,如果一个uid可以绑定多个账号和密码,那么很可能你的电脑会登陆别人的微信哦);
技术解答
确实返回了唯一 id,但目的是为了识别用户身份,而且实际上打开这个页面的时候浏览器已经和 Server 创建了一个长连接等待确认信息。
查看 http://wx.qq.com 的源码可以看到,这个页面在加载完毕时,也已经把很多登录后才需要的相关资源都预先加载进来了,所以长连接等待登录用户得到确认后展示用户信息的速度很快,因为无需刷页面和加载头像外的其他资源。
2. 当用户使用登陆后的微信扫描该二维码的时候,会将这个id和手机上的微信账号及密码绑定,并上传到微信网页版服务器;
先上个图:
![](https://img2018.cnblogs.com/blog/1182199/201810/1182199-20181025085004448-294841072.png)
二维码样例: http://weixin.qq.com/x/ARmFYVvUzczwBl9u6Y1I ,利用我查查之类的二维码应用可以得到类似这样的地址,但并不会自动打开该地址,微信客户端针对 http://weixin.qq.com/x/ 开头的地址做了特殊处理,会自动获取相关信息并提示确认。 在手机版微信访问这个页面进行确认时,Server已经同时获得了客户端信息,并通过之前保持的长连接告知浏览器。
3. 微信网页版页面每隔1秒或2秒会get请求该id对应的微信账号及密码,如果id绑定上了微信账号和密码,那么就可以请求到账号和密码,就可以自动登陆了。
浏览器展示完长连接里包含的用户信息(头像等)后,会新开一个长连接等待客户端的确认操作,其 URL 类似 https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?uuid=794ecedd804f47&tip=1&_=1395748413642 。从安全的角度来说,无论如何都不会让客户端获得微信帐号和密码的。要知道,密码这玩意腾讯自己都不敢保存(有兴趣的同学可以自行了解下 CSDN 明文密码泄露事件),肯定是不可能返回给浏览器的。
而且从体感来看,怎么着都不可能是页面1-2秒轮询发起GET请求的,实际是通过堵塞等待的长连接,近乎实时的获得信息。 对于验证过程,Open API 一般是通过授权令牌(Token)来解决的,原理是当用户通过授权后,分配一个限定条件下的令牌(如限制本机访问、限制授权有效时间、限制同时登录设备数等),使获得授权的用户仅在有限的前提下能访问相关服务。 像计算机休眠后曾做的授权就自动收回了,这样就有效的避免了在别人电脑上(尤其是网吧)打开,但忘记关闭或退出这类安全问题了。
同时,整个授权过程的验证部分在手机端进行,有效杜绝了 PC 上泛滥的各类木马、『安全工具』的监听,大大降低了帐号被盗的风险。
长连接代码演示
function _poll(_asUUID) { // .... $.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) { switch (_aoWin.code) { case 200: // .... break; case 201: // .... break; case 408: // .... break; case 400: case 500: // .... break; } }, error: function(jqXHR, textStatus, errorThrown) { // .... } }); }