网站接入第三方登录(微信,QQ)最新填坑指南

4,548 阅读3分钟

年前接到任务给网站增加第三方登录功能,接入微信和QQ。由于我们网站同时兼容web和electron(pc客户端),中间也踩了一些坑。基本思路是采用postMessage去实现跨域跨窗口通讯(之所以跨域,是因为我们的登录模块是单独抽离出来一个组件库,提供多个站点使用)。electron那块则利用BrowserWindow实现窗口通讯。

基本实现原理:应用打开微信、QQ第三方登录窗口,用户扫码登录后自动跳转到设置的回调页面,回调页面会在页面url带上返回的code,就是腾讯服务器发来的Authorization Code。前端将授权码发给后台,后台调用接口获取用户信息登录成功。这里只讲解前端部分。

官方参考文档

微信开放平台基于OAuth2.0协议的第三方登录

QQ互联基于OAuth2.0协议的第三方登录

准备工作

微信:在微信开放平台注册账号,同时拥有第三方登录的权限。

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) => {
    // 获取参数做处理
  });
}