大家应该都体验过腾讯系应用的一键登录功能(PC),当你的电脑上登陆了 QQ 之后,使用QQ快捷登录进行OAuth验证时不需要再手动输入账号和密码,点击自己的头像就可以成功登陆应用。
也许你也曾有过好奇她是如何实现的,今天我们就来复刻一个。
原理剖析
当我们登陆QQ的时候有快捷登录,没登QQ的时候只有扫码登录,一定适合我们的电脑QQ进行了通信,但是浏览器权限非常低,在没有弹窗授权的情况下不可能直接和应用进行通信,可以猜一手通过HTTP我们可以使用Chrome的网络抓包工具来看一下到底是怎么实现的
经过仔细检查最终发现了这样一个请求,看样子时JSONP类型的请求,他的响应格式如下
var var_sso_uin_list = [{
"uin": ------,
"face_index": 0,
"gender": 0,
"nickname": "沿途の风景",
"client_type": ------,
"uin_flag": ------,
"account": ------
}];
ptui_getuins_CB(var_sso_uin_list);
再来看这个请求的地址,第一感觉就是请求了localhost,可以ping一下看一看IP
看样子腾讯是将这个域名解析到了127.0.0.1,调用该域名的接口时就会访问用户本机,而这个域名后面还有一个端口,我们再检查一下本机的4301
端口
果不其然,看来QQ客户端就是监听了某个端口,快捷登录的数据就是从这里获取的。
剩下的就是OAuth的东西了,我们就不转门去研究了,这个应该是和QQ互联的借口相关。
复刻
在知道实现原理之后我们就可以动手实现一个了,我们选择给予Electron来实现一个客户端(JS是万能的)。
首先初始化一个Electron的项目(不会Electron的可以简单了解一下,我后面会出一个Electron的学习笔记)。
可以利用NodeJS的内置模块http
来启动一个服务,监听某一个端口,将对应路径的请求返回即可,http服务的代码如下
import { createServer } from 'http'
import { parse as parseUrl } from 'url'
import { parse as parseQs } from 'querystring'
let server;
export function openServer() {
closeServer();
server = createServer((req, res) => {
const url = req.url
const urlObj = parseUrl(url)
const qs = parseQs(urlObj.query)
if (urlObj.pathname === '/pt_get_uins') {
res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8' })
res.end(
'var var_sso_uin_list = [{' +
'"uin": 123456,' +
'"face_index": 0,' +
'"gender": 0,' +
'"nickname": "沿途の风景",' +
'"client_type": 123456,' +
'"uin_flag": 123456,' +
'"account": 123456' +
'}];' + qs.callback +
'(var_sso_uin_list);'
)
} else {
res.writeHead(400)
res.end('Not Found')
}
})
server.listen(8888)
}
export function closeServer() {
server && server.removeAllListeners();
server && server.close(() => {
console.log("服务接口关闭");
});
}
在这段代码中,我们启动了一个httpServer,这个server中针对指定的路径返回了之前我们解析出的QQ返回的文件格式,给响应设置了Content-Type为javascript,拼接了请求中传递的callback函数。
我们将这段代码添加到主进程代码中
import { app, BrowserWindow } from 'electron';
import { openServer } from './httpServer';
let mainWindow: BrowserWindow | null;
app.whenReady().then(() => {
mainWindow = new BrowserWindow({});
mainWindow.loadURL(process.argv[2]);
openServer()
})
这时启动应用即可体验效果,打开浏览器发送访问路径即可
具体的嵌入到应用中我们就不模拟了,就是将这个请求嵌入到业务组件中即可。