QQ内置浏览器下的QQ登录实现

在PC时代,调用QQ, 微博之类的三方登录,对于前端来说是比较轻松的(如果不需要异步登录的话),而且体验也不错,QQ那边可以自动识别是否登录,对于用户只是一个确认和点击的流程。

但是到了现在,移动互联的时代,这样的三方登录完全都是APP那边前端的事情,Web前端无能为力。毕竟,让用户在一个小设备上的小网页里面输入他已经在登录状态的QQ的账号密码,是挺难为人也挺不现实的。

缘于产品庆庆同学的执着,偶然发现在QQ内置的浏览器里面进行QQ登录的话,是也可以跟PC那样自动识别到已登录状态的,这让事情有了一线曙光。不过这种便利也只能在QQ的内置浏览器里面

下面就来说说如何实现web网页在QQ内置浏览器里面的登录

识别QQ的内置浏览器

首先得识别当前所在的浏览器是不是QQ内置的。

QQ浏览器分两种情况,一个是独立的浏览器产品,一个是内置在QQ里面的webview。识别的方式,用userAgent。

先贴出对应平台对应分类的UA(使用v2ex提供的一个工具页面可以方便的打印UA )

安卓平台下的独立的QQ浏览器

Mozilla/5.0 (Linux; U; Android 4.4.4; zh-cn; HM 1S Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko)Version/4.0 Chrome/37.0.0.0 MQQBrowser/7.4 Mobile Safari/537.36

安卓平台下的内置的QQ浏览器

Mozilla/5.0 (Linux; Android 4.4.4; HM 1S Build/KTU84P; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.49 Mobile MQQBrowser/6.2 TBS/043124 Safari/537.36 V1_AND_SQ_6.7.1_500_YYB_D QQ/6.7.1.3105 NetType/WIFI WebP/0.3.0 Pixel/720

IOS平台下的独立的QQ浏览器

Mozilla/5.0 (iPhone 5SGLOBAL; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 MQQBrowser/7.4 Mobile/14E304 Safari/8536.25 MttCustomUA/2 QBWebViewType/1

IOS平台下内置的QQ浏览器(TIM内)

Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E304 QQ/6.5.5.0 TIM/1.0.5.9 V1_IPH_SQ_6.5.5_1_TIM_D Pixel/640 Core/UIWebView NetType/4G QBWebViewType/1

这里因为测试设备有限,没法确切知道所有版本的QQ下的情况,只能等以后慢慢完善。

从上面的UA可以找到两个关键字QQMQQBrowser。本来两个关键字是分开的,独立浏览器里面是MQQBrowser,内置的是QQ,但是这在安卓的内置里面同时实现了。

所以判断方式需要绕一点:先判断MQQBrowser,如果存在,判断QQ是否存在,存在则是内置,不存在则是独立;如果不存在MQQBrowser,则判断QQ是否存在,存在是内置,不存在,就都不是。

不过我们这次的需求只需要判断内置,所以简单,判断QQ是否存在即可:navigator.userAgent.match(/QQ\/([\d.]+)/)

登录的实现

一开始是打算继续用PC时代用的那个登录SDK好实现一个优雅的异步免刷新的登录效果。在实际测试的时候,也的确是可以做到在登录页QQ自动识别状态只需要用户点击登录按钮确认,可是在登录回跳这块卡住了。

在PC,SDK是用window.open新开一个弹窗来实现登录和各种跳转,在新窗口的最终落地页和主窗口通信返回登录成功获取的AccessToken,然后关闭弹出窗口。

但是内置浏览器里面window.open的效果是替换当前网页展示,类似于location.href赋值跳转,这使得最终落地页一直联系不到主窗口,然后卡着了。

很自然的,就想到了用iframe,把window.open打开的地址放到iframe里面,不就可以实现两个window的通信了吗?说干就干,QQ登录界面顺利的展现了,点击登录,结果主界面跳了,跟之前一样,再次无法通信。看来QQ在登录成功之后是用window.top来跳转的。不明白QQ这样做是出于什么考虑的,安全性?可是登录成功的回调地址不是已经被限制了只能是对应域名下的了吗?唯一知道的就是,这个方法行不通……

所以,目前还是只能走以前的老路,通过页面跳转的方式来实现登录。主要的策略就是在QQ登录成功之后的回调页再跳回之前的发起登录的页面。

本来这个需求是由后端来实现,在回跳发起页之前,在cookie里面记录用户的登录信息,这样发起页就能知道用户登录成功了。

不过我这次的需求,有两个麻烦的地方:

  1. 后端目前还没有在移动站点配置登录功能
  2. 我需要知道QQ用户的OpenIDAccessToken

所以改为由后端配置好回调页路由,具体页面里面的跳转和信息记录由前端自行控制。

然后具体的实现步骤是:
1. 用户点击发起页上的QQ登录按钮
2. 前端在后端给的redirect_url里面加上一个search query字段redirect,方便后面回跳回来
3. 前端根据appIdredirect_url整合出跳转QQ登录的地址(https://graph.qq.com/oauth2.0/authorize? response_type=token&client_id={{appId}}&scope=all&display=mobile&redirect_uri={{回调地址}})并跳转
4. 在QQ登录页面,QQ识别到当前用户的登录状态,自动填充信息并添加一键登录按钮
5. 用户点击登录
6. QQ开始回跳到redirect_url并在redirect_url后附加hash query,这个query里面有 access_token expires_in
7. 这个回调页,js抓取token和token过期时间存储在localStorage,同时还在localStorage记录两个个临时的key: TempQQLoginSuccessTempQQLoginSuccessTime
8. 回调页识别search query里面的redirect字段,获取发起页地址并跳转过去。
9. 发起页初始化的时候,查看localStorage里面有没有token和expire_in对应的字段,如果没有则是未登录状态,执行一次退出操作(退出操作是尝试清空回调页记录的四个key,下文同理)。如果存在,则判断当前时间是否超过expire_in对应的时间,如果超过,则执行退出操作。没有超过,则此时用户是登录状态。
10. 确定用户登录之后,再判断回调页记录的两个临时字段是否存在。如果有,判断TempQQLoginSuccessTime存储的时间和当前时间的时间差是否有超过一定的阈值。如果没有超过则说明上一次的发起页有登录行为,可以提示登录成功,如果超过了,则直接显示登录后的状态即可。同时不管超过与否,清空这两个临时key。

以上,整个登录流程就通了,成功的获取了用户的accessToken。

后面再使用accssToken作为参数,以jsonp的方式访问QQ的接口https://graph.qq.com/oauth2.0/me?access_token={{accessToken}}就能顺利的获取到用户的openId。这里要注意的一点是,QQ的这个接口里面callback的函数名写死了是callback,所以在使用jsonp的时候,以Zepto为例,jsonpCallback需要设定为callback

关于这个项目,具体实现代码在这:https://github.com/shuizhongyueming/fe-qq-login,有兴趣的可以看看