为什么要使用第三方社交登录吗?
- 社交登录
- 含义:访问某个网站的用户可以通过社交媒体账号登录
- 优点
- 可以利用社会化媒体的影响增加网站和用户粘性,提高留存
- 借助分享、裂变等方式对网站进行推广,提高知名度 AARRR中的留存和传播
- 同时增加网站的流量,使用量,提高网站产品的转换率
- 类型
- 腾信微信
- 腾讯QQ
- 阿里支付宝
- 微博
- .....
- 微信登录
- 场景二维码登录
- Oauth2.0授权登录
特别注意: 订阅号不能实现微信登录服务,服务号才可以实现,但是微信认证也要300大洋,我的钱,呜呜呜。
微信登录业务逻辑梳理-系统时序图
- 时序图
- 介绍微信登录从接入微信服务器到node后端开发整个链路
- 整个链路比较复杂,当前先宏观上了解整个流程,但是我会详细地讲解每个流程逻辑
- 如果有部分细节不理解没关系,在微信登录实战开发时结合时序图都会一一掌握
- 时序图的组成元素
- 对象
- 扮演的角色,大矩形表示
- 生命线
- 对象存活 的事件对象下的虚线
- 控制焦点
- 在这个时间段执行响应的操作,小矩形标识
- 消息
- 发送消息:实线箭头
- 返回消息:虚线箭头
- 处理消息:自身调用,处理数据
深入理解微信服务器的接口授权接入
- 自身的node服务器,并且提供一个给微信服务器回调的接口
- 按照微信的要求,验证请求消息是来自微信服务器
- 公众号的全局唯一接口调用凭据 access_token 的获取
- GET api.weixin.qq.com/cgi-bin/tok…
- 使用测试提供的appid、secret演示接口请求:mp.weixin.qq.com/debug/cgi-b…
- 微信公众账号测试号申请系统
- 微信提供测试的appID和appsecret
- 回调接口的接入配置测试
- 要将自己本地服务的地址映射成外网可访问的域名
- 通过内网穿透工具:
使用花生壳,生成的外网地址固定,不用频繁更换
场景二维码的接入:developers.weixin.qq.com/doc/offiacc…
- 通过access_token获取ticket
- POST URL: api.weixin.qq.com/cgi-bin/qrc…
- 入参
{ "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格式消息
- 返回给微信服务器的消息
微信服务器回调本地接口【对称加密】接入验证
微信服务器回调本地接口接入验证
- 创建本地回调的接口
- http://127.0.0.1:8081/api/wx_login/v1/callback
- 内网穿透实现外网访问本地接口
- 花生壳配置本地地址
测试访问
https://474y3966p9.goho.co/api/wx_login/v1/callback
接收微信服务器发送的参数对称加密验证
- developers.weixin.qq.com/doc/offiacc…
- 对称加密逻辑开发验证
// 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
- 微信公众平台配置
- 基本配置
- 创建获取二维码接口
- http://127.0.0.1:8081/api/wx_login/v1/login
- 请求微信服务器获取 access_token
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 }
客户端怎么知道用户何时扫码?开发轮询接口
创建轮询接口
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记录,想要源码的同学可以看下面的链接~