手把手实现第三方社交登录方式微信登录

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 手把手实现第三方社交登录方式微信登录

为什么要使用第三方社交登录吗?

  • 社交登录
  • 含义:访问某个网站的用户可以通过社交媒体账号登录
  • 优点
  • 可以利用社会化媒体的影响增加网站和用户粘性,提高留存
  • 借助分享、裂变等方式对网站进行推广,提高知名度 AARRR中的留存和传播
  • 同时增加网站的流量,使用量,提高网站产品的转换率
  • 类型
  • 腾信微信
  • 腾讯QQ
  • 阿里支付宝
  • 微博
  • .....
  • 微信登录
  • 场景二维码登录
  • Oauth2.0授权登录

特别注意: 订阅号不能实现微信登录服务,服务号才可以实现,但是微信认证也要300大洋,我的钱,呜呜呜。

微信登录业务逻辑梳理-系统时序图

  • 时序图
  • 介绍微信登录从接入微信服务器node后端开发整个链路
  • 整个链路比较复杂,当前先宏观上了解整个流程,但是我会详细地讲解每个流程逻辑
  • 如果有部分细节不理解没关系,在微信登录实战开发时结合时序图都会一一掌握
  • 时序图的组成元素
  • 对象
  • 扮演的角色,大矩形表示
  • 生命线
  • 对象存活 的事件对象下的虚线
  • 控制焦点
  • 在这个时间段执行响应的操作,小矩形标识
  • 消息
  • 发送消息:实线箭头
  • 返回消息:虚线箭头
  • 处理消息:自身调用,处理数据

image.png

深入理解微信服务器的接口授权接入

  • 自身的node服务器,并且提供一个给微信服务器回调的接口
  • 按照微信的要求,验证请求消息是来自微信服务器
  • 公众号的全局唯一接口调用凭据 access_token 的获取
  • 微信公众账号测试号申请系统
  • 微信提供测试的appID和appsecret
  • 回调接口的接入配置测试
  • 要将自己本地服务的地址映射成外网可访问的域名
  • 通过内网穿透工具:

使用花生壳,生成的外网地址固定,不用频繁更换

场景二维码的接入:developers.weixin.qq.com/doc/offiacc…

  • 通过access_token获取ticket
{
  "expire_seconds": 604800,
  "action_name": "QR_SCENE", 
  "action_info": {
    "scene": {"scene_id": 123}
  }
} 

拼接二维码url

  • 微信提供的部分url拼接上获取的ticket,将此url和ticket返回客户端
https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET
  • 扫码之后的操作看时序图流程
  • 微信回调node服务器接口发送的XML格式消息
  • 返回给微信服务器的消息
  • image.png

微信服务器回调本地接口【对称加密】接入验证

微信服务器回调本地接口接入验证

  • 花生壳配置本地地址

image.png

测试访问

https://474y3966p9.goho.co/api/wx_login/v1/callback

image.png

接收微信服务器发送的参数对称加密验证

// sha1加密安装
yarn add sha1@1.1.1
const SecretTool = require('../utils/SecretTool')
const WxLoginService = {
  wechat_insert: (signature, timestamp, nonce, echostr) => {
    // 服务器的token
    let token = 'testxdclass'
    // 将token、timestamp、nonce三个参数进行字典序排序,拼接成一个字符串,进行sha1加密
    let str = SecretTool.sha1([token, timestamp, nonce].sort().join(''))
    // 获得加密后的字符串可与signature对比,验证标识该请求来源于微信服务器
    if (str === signature) {
      // 确认此次GET请求来自微信服务器,原样返回echostr参数内容,则接入生效
      return echostr
    }
  },
}
module.exports = WxLoginService
  • 微信公众平台配置
  • 基本配置
  • image.png
const appId = 'wx5beac15ca207c40c';
const appSecret = '8189e5f14346ccaa3bd5f6909f31a362';
const accessTokenPc = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${appSecret}`;
const getAccess_token = () => {
  return axios({
    method: 'get',
    url: accessTokenPc,
  });
};

请求微信服务器获取 ticket

const ticketReq = (token) => {
  return axios({
    method: 'post',
    url: `https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=${token}`,
    data: {
      expire_seconds: 60 * 2, // 二维码有效时间
      action_name: 'QR_SCENE',
      action_info: { scene: { scene_id: 123 } },
    },
  });
};

获取二维码方法封装

const qrUrl = 'https://mp.weixin.qq.com/cgi-bin/showqrcode';
const wechatLogin = {
  // 获取微信登录二维码
  getQR: async () => {
    let token = (await getAccess_token()).data.access_token;
    console.log((await getAccess_token()))
    console.log('token:', token)
    let ticket = (await ticketReq(token)).data.ticket;
    return { qrcodeUrl: `${qrUrl}?ticket=${ticket}`, ticket: ticket };
  }
}

将 ticket作为key值,{ isScan : false } 转成 json 存入Redis缓存

let key = `wechat:ticket:${ticket}`
redisConfig.set(key, JSON.stringify({ isScan: 'no' }), 120)
  • 返回二维码url+ticket

掌握流数据+xml数据处理接受微信服务器请求参数

  • 创建用户扫码时微信回调接口的开发 POST请求
// controller 控制层
wechat_message: (req, res) => {
  let handleRes = WxLoginService.wechat_message(req)
  res.send(handleRes)
}
// service 数据层
wechat_message: (req, res) => {
  return '成功'
}

处理微信回调的参数

  • 封装数据处理工具类
/**
 * @param {*} getXMLStr 以流的形式接收微信服务器发过来的数据
 * @param {*} getJsData 通过工具解析xml数据转换成对象
 * @param {*} getObjData 优化数据
 */
var { parseString } = require('xml2js');
// 微信XML数据类型处理
class WxDataTool {
  static getXMLStr(req) {
    return new Promise((resolve, reject) => {
      let data = '';
      req.on('data', (msg) => {
        data += msg.toString();
      });
      req.on('end', () => {
        resolve(data);
      });
    });
  }
  static getJsData(data) {
    return new Promise((resolve, reject) => {
      parseString(data, (err, result) => {
        if (err) {
          reject('error');
        } else {
          resolve(result);
        }
      });
    });
  }
  static getObjData(obj) {
    let tempObj = {};
    if (obj && typeof obj === 'object') {
      // 循环对象,提取数据
      for (let key in obj) {
        let value = obj[key];
        if (value && value.length > 0) {
          tempObj[key] = value[0];
        }
      }
      return tempObj;
    } else {
      return tempObj;
    }
  }
};
module.exports = WxDataTool

根据微信回调参数生成权限token+redis缓存状态+xml数据返回

  • 根据openid进行注册/登录
// 根据openid判断是否注册
let openidRes = await DB.Account.findAll({ where: { openid }, raw: true })
// 随机获取头像、用户名
let head_img = RandomTool.randomAvatar()
let username = RandomTool.randomName()
// 无注册则增加个用户数据
let user = null
if (openidRes.length === 0) {
  let resData = await DB.Account.create({ username, head_img, openid })
  user = { head_img, username, id: resData.toJSON().id }
} else {
  user = {
    head_img: openidRes.head_img,
    username: openidRes.username,
    id: openidRes.id
  }
}

生成token

let token = SecretTool.jwtSign(user, '168h')

更新redis缓存中的扫码状态+token

let key = `wechat:ticket:${ticket}`
const existsKey = await redisConfig.exists(key)
if (existsKey) {
  redisConfig.set(key, JSON.stringify({ isScan: 'yes', token }), 120)
}

返回微信xml格式数据

let content = ''
if (msgObj.MsgType == 'event') {
  // 扫码
  if (msgObj.Event == 'SCAN') {
    content = '欢迎回来,讲师微信:xdclass6'
  } else if (msgObj.Event == 'subscribe') {
    // 订阅
    content = '感谢关注,讲师微信:xdclass6'
  }
  // 根据来时的信息格式,重组返回。(注意中间不能有空格)
  let msgStr = `<xml>
    <ToUserName><![CDATA[${lastData.FromUserName}]]></ToUserName>
    <FromUserName><![CDATA[${lastData.ToUserName}]]></FromUserName>
    <CreateTime>${Date.now()}</CreateTime>
    <MsgType><![CDATA[text]]></MsgType>
    <Content><![CDATA[${content}]]></Content>
  </xml>`
  return msgStr
}

客户端怎么知道用户何时扫码?开发轮询接口

image.png创建轮询接口

http://127.0.0.1:8081/api/wx_login/v1/check_scan
请求方法:get
请求参数:ticket

根据ticket参数查询redis缓存

let { ticket } = req.query
let key = `wechat:ticket:${ticket}`
let redisData = JSON.parse(await redisConfig.get(key))

判断是否存在对应key

if (redisData && redisData.isScan == 'yes') {
  let token = redisData.token
  return JsonData.buildSuccessAndData(`Bearer ${token}`)
} else {
  return JsonData.buildResult(CodeEnum.WECHAT_WAIT_SCAN)
}

源代码:

里面都有我每次的commit记录,想要源码的同学可以看下面的链接~

[前端] github.com/mohaixiao/c…

[后端] github.com/mohaixiao/c…

目录
相关文章
|
6月前
|
存储 小程序 前端开发
微信小程序进阶——后台交互个人中心授权登录
微信小程序进阶——后台交互个人中心授权登录
105 0
|
7月前
|
Web App开发 移动开发 前端开发
前端企业微信服务商第三方应用开发详情流程
前端企业微信服务商第三方应用开发详情流程
304 0
|
5月前
|
存储 小程序 数据库
【微信小程序开发】之微信授权登陆
【微信小程序开发】之微信授权登陆
337 0
|
安全 Java API
手把手带你实现第三方应用登录
手把手带你实现第三方应用登录
1015 0
手把手带你实现第三方应用登录
|
9月前
|
API
企业微信授权登录服务端API实战开发(1):企业微信环境部署
企业微信授权登录服务端API实战开发(1):企业微信环境部署
167 0
|
9月前
|
Java Maven
集成一个以官网(微信,QQ,微博)为标准的登录分享功能
今天要分享的是一个老生常谈的一个功能,也是网上一搜一大片的技术点,没什么技术含量,就是整合一下,提供一下方便,相对于友盟,ShareSdk中夹杂着一些别的功能,此文封装的绝对纯净,除了官网所提供的,不夹杂任何的代码逻辑,登录就是登录,分享就是分享,实实在在的以官网为标准。
|
开发工具
微信授权 & 扫码登录 - 快速入门(手动 & 第三方SDK)(二)
微信授权 & 扫码登录 - 快速入门(手动 & 第三方SDK)(二)
300 0
|
SQL 前端开发 Java
Java开发:实现用户注册登录的功能
在Java开发过程中,实现用户的注册功能是最基本的,用户通过手机号或者邮箱作为注册账号也是非常常见的操作方式,不管是通过手机号注册或者邮箱注册,原理都差不多,那么本文就来分享一下在Java开发过程中的用户注册账号的功能实现。
265 0
Java开发:实现用户注册登录的功能
|
开发工具
微信授权 & 扫码登录 - 快速入门(手动 & 第三方SDK)(一)
微信授权 & 扫码登录 - 快速入门(手动 & 第三方SDK)(一)
681 0
微信授权 & 扫码登录 - 快速入门(手动 & 第三方SDK)(一)
微信公众平台开发(2)--微信认证流程图文详解
本文目录 1. 微信认证的作用 2. 登录 3. 开始认证 4. 认证流程 4.1 准备资料 4.2 同意协议 4.3 填写资料 4.4 确认名称 4.5 填写发票 4.6 支付费用 4.7 打款验证 5. 其他工作
145 0