年前接到任务给网站增加第三方登录功能,接入微信和QQ。由于我们网站同时兼容web和electron(pc客户端),中间也踩了一些坑。基本思路是采用postMessage
去实现跨域跨窗口通讯(之所以跨域,是因为我们的登录模块是单独抽离出来一个组件库,提供多个站点使用)。electron那块则利用BrowserWindow
实现窗口通讯。
基本实现原理:应用打开微信、QQ第三方登录窗口,用户扫码登录后自动跳转到设置的回调页面,回调页面会在页面url带上返回的code
,就是腾讯服务器发来的Authorization Code。前端将授权码发给后台,后台调用接口获取用户信息登录成功。这里只讲解前端部分。
官方参考文档
准备工作
微信:在微信开放平台注册账号,同时拥有第三方登录的权限。
QQ:在QQ互联注册账号,需要注意的是,QQ只能在线上验证第三方登录,这点很坑,没有像微信开放平台一样本地测试的功能,这点跟QQ客服确定过。所以需要你把功能做好了, 在线上调试。我的做法是在预发布环境调试,因为QQ互联要求审核的域名外网可访问,当然如果你的测试环境外网可访问,也没啥问题。
web端接入微信,QQ登录
componentDidMount() {
if(!this.props.isElectron) {
// 监听第三方登录,web端使用postMessage
thirdLoginCb = (event) => {
const {login, code, loginType} = event.data;
if (login === '1') {
// 将授权码发送给后台做鉴权
this.thirdLogin(code, loginType)
}
}
window.addEventListener('message', thirdLoginCb, false)
}
}
wechatLogin = () => {
// electron端做特殊处理,后面会讲
if (this.props.isElectron) {
this.props.openWechat();
return;
}
// 客户端通过窗口监听数据,此处填写你的应用appid
const appid = 'xxx';
// 此处为本地域名
const domain = window.location.origin;
// thirdLoginDomain即第三方登录授权之后跳转的地址,我们会在这个地址做postmessage的处理。
const url = encodeURIComponent(`${this.props.thirdLoginDomain}?domain=${domain}&type=wx&login=1`);
loginWindow = window.open(`https://open.weixin.qq.com/connect/qrconnect?appid=${appid}&redirect_uri=${url}&response_type=code&scope=snsapi_login&state=STATE#wechat_redirect`, '', 'height=600, width=1200, top=200, left=400, toolbar=no, menubar=no, scrollbars=no, resizable=no, location=no, status=no');
}
qqLogin = () => {
// electron端做特殊处理,后面会讲
if (this.props.isElectron) {
this.props.openQQ();
return;
}
// 客户端通过窗口监听数据,此处填写你的应用appid
const appid = 'xxx';
// 此处为本地域名
const domain = window.location.origin;
// thirdLoginDomain即第三方登录授权之后跳转的地址,我们会在这个地址做postmessage的处理。
const url = encodeURIComponent(`${this.props.thirdLoginDomain}?domain=${domain}&type=qq&login=1`);
loginWindow = window.open(`https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=${appid}&redirect_uri=${url}&state=STATE#qq_redirect`,'', 'height=600, width=1200, top=200, left=400, toolbar=no, menubar=no, scrollbars=no, resizable=no, location=no, status=no')
}
窗口监听登录成功之后返回数据
登录成功后跳转的页面做数据传输
componentDidMount() {
// getQueryParam是获取当前location.href参数的通用方法,可以自己封装。
if (window.location.search.includes('code') && !window.location.search.includes('isElectron')) {
window.opener.postMessage({
domain: getQueryParam('domain'),
loginType: getQueryParam('type'),
code: getQueryParam('code'),
login: getQueryParam('login'),
}, getQueryParam('domain'));
}
window.close();
}
electron客户端获取窗口数据
openLoginWindow = (link) => {
const BrowserWindow = window.require('electron').remote.BrowserWindow;
let loginWindow = new BrowserWindow({
width: 1200, height: 600, title: '第三方登录', resizable: false, webPreferences: { nodeIntegration: false },
});
loginWindow.setMenu(null);
loginWindow.on('closed', () => {
loginWindow = null;
});
loginWindow.loadURL(link);
loginWindow.show();
loginWindow.webContents.on('did-navigate', (event, url) => {
// 获取参数做处理
});
}