文章目录

  • 前言
  • 一、微信小程序实现网页一键登录功能
    • 1.旧版登录方法
    • 2.新版登录方法
  • 二、相关第三方包源码

前言

如果微信小程序要获取微信登录的用户信息,需要拿到code去后台换取用户信息,具体步骤又如下:

  • 使用微信开放功能button按钮绑定点击事件为获取用户授权
  • 授权成功调用微信登录接口获取code
  • 用获取到的code去调用后台接口获取到用户的openid
  • code+openid去调用后台写的小程序自动登录接口获取到access_token
  • access_token拿到就可以去查询用户信息了

一、微信小程序实现网页一键登录功能

首先服务端先安装两个包

npm i koa-weixin-auth --save
npm i koa-body --save

1.旧版登录方法

小程序端

<button bindgetuserinfo="login" open-type="getUserInfo" type="primary">登陆</button>
login(e) {console.log(e);let {userInfo,encryptedData,iv} = e.detailwx.login({success(res0) {if (res0.code) {//发起网络请求wx.request({url: 'http://localhost:3000/user/wexin-login',method: 'POST',header: {'content-type': 'application/json'},data: {code: res0.code,userInfo,encryptedData,iv},success(res) {console.log('请求成功', res.data)getApp().globalData.token = res.data.data.authorizationTokenconsole.log('authorization', getApp().globalData.token)},fail(err) {console.log('请求异常', err)}})} else {console.log('登录失败!' + res.errMsg)}}})
},

服务端

const WeixinAuth = require("../lib/koa2-weixin-auth")
const WXBizDataCrypt = require('../lib/WXBizDataCrypt')// 小程序的机要信息
const miniProgramAppId = '自己的appId'
const miniProgramAppSecret = '自己的appSecret'
const weixinAuth = new WeixinAuth(config.miniProgram.appId, config.miniProgram.appSecret);// 这是第一次小程序登陆法
router.post("/wexin-login", async (ctx) => {let { code } = ctx.request.bodyconst token = await weixinAuth.getAccessToken(code);// const accessToken = token.data.access_token;const openid = token.data.openid;// const userinfo = await weixinAuth.getUser(openid)// 这个地方有一个错误,invalid credential, access_token is invalid or not latest// 拉取不到userInfoctx.status = 200ctx.body = {code: 200,msg: 'ok',data: openid}
})

2.新版登录方法

小程序端

<button bindgetuserinfo="login" open-type="getUserInfo" type="primary">登陆</button>
login(e) {console.log(e);let {userInfo,encryptedData,iv} = e.detailconsole.log('userInfo', userInfo);const requestLoginApi = (code)=>{//发起网络请求wx.request({url: 'http://localhost:3000/user/wexin-login2',method: 'POST',header: {'content-type': 'application/json'},data: {code: code,userInfo,encryptedData,iv},success(res) {console.log('请求成功', res.data)let token = res.data.data.authorizationTokenwx.setStorageSync('token', token)onUserLogin(token)console.log('authorization', token)},fail(err) {console.log('请求异常', err)}})}const onUserLogin = (token)=>{getApp().globalData.token = tokenwx.showToast({title: '登陆成功了',})}wx.checkSession({success () {//session_key 未过期,并且在本生命周期一直有效console.log('在登陆中');let token = wx.getStorageSync('token')if (token) onUserLogin(token)},fail () {// session_key 已经失效,需要重新执行登录流程wx.login({success(res0) {if (res0.code) {requestLoginApi(res0.code)} else {console.log('登录失败!' + res.errMsg)}}})}})
},
const WeixinAuth = require("../lib/koa2-weixin-auth")
const WXBizDataCrypt = require('../lib/WXBizDataCrypt')// 小程序的机要信息
const miniProgramAppId = '自己的appId'
const miniProgramAppSecret = '自己的appSecret'
router.post("/wexin-login2", async (ctx) => {console.log('request.body', ctx.request.body);let { code,userInfo,encryptedData,iv,sessionKeyIsValid } = ctx.request.bodyconsole.log("sessionKeyIsValid", sessionKeyIsValid);let sessionKey// 如果客户端有token,则传来,解析if (sessionKeyIsValid) {let token = ctx.request.header.authorization;token = token.split(' ')[1]// token有可能是空的if (token) {let payload = await util.promisify(jsonwebtoken.verify)(token, config.jwtSecret).catch(err => {console.log('err', err);})console.log('payload', payload);if (payload) sessionKey = payload.sessionKey}}// 除了尝试从token中获取sessionKey,还可以从数据库中或服务器redis缓存中获取// 如果在db或redis中存储,可以与cookie结合起来使用,// 目前没有这样做,sessionKey仍然存在丢失的时候,又缺少一个wx.clearSession方法// console.log("ctx.session.sessionKeyRecordId", ctx.session.sessionKeyRecordId);if (sessionKeyIsValid && !sessionKey && ctx.session.sessionKeyRecordId) {let sessionKeyRecordId = ctx.session.sessionKeyRecordIdconsole.log("sessionKeyRecordId", sessionKeyRecordId);// 如果还不有找到历史上有效的sessionKey,从db中取一下let sesskonKeyRecordOld = await SessionKey.findOne({where: {id: ctx.session.sessionKeyRecordId}})if (sesskonKeyRecordOld) sessionKey = sesskonKeyRecordOld.sessionKeyconsole.log("从db中查找sessionKey3", sessionKey);}// 如果从token中没有取到,则从服务器上取一次if (!sessionKey) {const token = await weixinAuth.getAccessToken(code)// 目前微信的 session_key, 有效期3天sessionKey = token.data.session_key;console.log('sessionKey2', sessionKey);}let decryptedUserInfovar pc = new WXBizDataCrypt(config.miniProgram.appId, sessionKey)// 有可能因为sessionKey不与code匹配,而出错// 通过错误,通知前端再重新拉取codedecryptedUserInfo = pc.decryptData(encryptedData, iv)console.log('解密后 decryptedUserInfo.openId: ', decryptedUserInfo.openId)let user = await User.findOne({ where: { openId: decryptedUserInfo.openId } })if (!user) {//如果用户没有查到,则创建let createRes = await User.create(decryptedUserInfo)console.log("createRes", createRes);if (createRes) user = createRes.dataValues}let sessionKeyRecord = await SessionKey.findOne({ where: { uid: user.id } })if (sessionKeyRecord) {await sessionKeyRecord.update({sessionKey: sessionKey})} else {let sessionKeyRecordCreateRes = await SessionKey.create({uid: user.id,sessionKey: sessionKey})sessionKeyRecord = sessionKeyRecordCreateRes.dataValuesconsole.log("created record", sessionKeyRecord);}// ctx.cookies.set("sessionKeyRecordId", sessionKeyRecord.id)ctx.session.sessionKeyRecordId = sessionKeyRecord.idconsole.log("sessionKeyRecordId", sessionKeyRecord.id);// 添加上openId与sessionKeylet authorizationToken = jsonwebtoken.sign({uid: user.id,nickName: decryptedUserInfo.nickName,avatarUrl: decryptedUserInfo.avatarUrl,openId: decryptedUserInfo.openId,sessionKey: sessionKey},config.jwtSecret,{ expiresIn: '3d' }//修改为3天,这是sessionKey的有效时间)Object.assign(decryptedUserInfo, { authorizationToken })ctx.status = 200ctx.body = {code: 200,msg: 'ok',data: decryptedUserInfo}
})

二、相关第三方包源码

koa2-weixin-auth.js

const querystring = require("querystring");
const request = require("request");const AccessToken = function(data){if(!(this instanceof AccessToken)){return new AccessToken(data);}this.data = data;
}/*!* 检查AccessToken是否有效,检查规则为当前时间和过期时间进行对比** Examples:* ```* token.isValid();* ```*/
AccessToken.prototype.isValid = function() {return !!this.data.session_key && (new Date().getTime()) < (this.data.create_at + this.data.expires_in * 1000);
}/*** 根据appid和appsecret创建OAuth接口的构造函数* 如需跨进程跨机器进行操作,access token需要进行全局维护* 使用使用token的优先级是:** 1. 使用当前缓存的token对象* 2. 调用开发传入的获取token的异步方法,获得token之后使用(并缓存它)。* Examples:* ```* var OAuth = require('oauth');* var api = new OAuth('appid', 'secret');* ```* @param {String} appid 在公众平台上申请得到的appid* @param {String} appsecret 在公众平台上申请得到的app secret*/
const Auth =  function (appid, appsecret) {this.appid = appid;this.appsecret = appsecret;this.store = {};this.getToken = function (openid) {return this.store[openid];};this.saveToken = function (openid, token) {this.store[openid] = token;};
}/*** 获取授权页面的URL地址* @param {String} redirect 授权后要跳转的地址* @param {String} state 开发者可提供的数据* @param {String} scope 作用范围,值为snsapi_userinfo和snsapi_base,前者用于弹出,后者用于跳转*/
Auth.prototype.getAuthorizeURL = function(redirect_uri, scope, state) {return new Promise((resolve, reject) => {const url = "https://open.weixin.qq.com/connect/oauth2/authorize";let info = {appid: this.appid,redirect_uri: redirect_uri,scope: scope || 'snsapi_base',state: state || '',response_type: 'code'}resolve(url + '?' + querystring.stringify(info) + '#wechat_redirect')})
}/*!* 处理token,更新过期时间*/
Auth.prototype.processToken = function(data){data.create_at = new Date().getTime();// 存储tokenthis.saveToken(data.openid, data);return AccessToken(data);
}/*** 根据授权获取到的code,换取access token和openid* 获取openid之后,可以调用`wechat.API`来获取更多信息* Examples:* ```* api.getAccessToken(code);* ```* Exception:** - `err`, 获取access token出现异常时的异常对象** 返回值:* ```* {*  data: {*    "access_token": "ACCESS_TOKEN",*    "expires_in": 7200,*    "refresh_token": "REFRESH_TOKEN",*    "openid": "OPENID",*    "scope": "SCOPE"*  }* }* ```* @param {String} code 授权获取到的code*/
Auth.prototype.getAccessToken = function(code){return new Promise((resolve, reject) => {const url = "https://api.weixin.qq.com/sns/jscode2session";// const url = "https://api.weixin.qq.com/sns/oauth2/access_token";const info = {appid: this.appid,secret: this.appsecret,js_code: code,grant_type: 'authorization_code'}request.post(url,{form:info},(err, res, body) => {if(err){reject(err)}else{const data = JSON.parse(body);resolve(this.processToken(data))}})})
}/*** 根据refresh token,刷新access token,调用getAccessToken后才有效* Examples:* ```* api.refreshAccessToken(refreshToken);* ```* Exception:** - `err`, 刷新access token出现异常时的异常对象** Return:* ```* {*  data: {*    "access_token": "ACCESS_TOKEN",*    "expires_in": 7200,*    "refresh_token": "REFRESH_TOKEN",*    "openid": "OPENID",*    "scope": "SCOPE"*  }* }* ```* @param {String} refreshToken refreshToken*/
Auth.prototype.refreshAccessToken = function(refreshToken){return new Promise((resolve, reject) => {const url = 'https://api.weixin.qq.com/sns/oauth2/refresh_token';var info = {appid: this.appid,grant_type: 'refresh_token',refresh_token: refreshToken};request.post(url,{form:info},(err, res, body) => {if(err){reject(err)}else{const data = JSON.parse(body);resolve(this.processToken(data))}})})
}/*** 根据openid,获取用户信息。* 当access token无效时,自动通过refresh token获取新的access token。然后再获取用户信息* Examples:* ```* api.getUser(options);* ```** Options:* ```* openId* // 或* {*  "openId": "the open Id", // 必须*  "lang": "the lang code" // zh_CN 简体,zh_TW 繁体,en 英语* }* ```* Callback:** - `err`, 获取用户信息出现异常时的异常对象** Result:* ```* {*  "openid": "OPENID",*  "nickname": "NICKNAME",*  "sex": "1",*  "province": "PROVINCE"*  "city": "CITY",*  "country": "COUNTRY",*  "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",*  "privilege": [*    "PRIVILEGE1"*    "PRIVILEGE2"*  ]* }* ```* @param {Object|String} options 传入openid或者参见Options*/
Auth.prototype.getUser = async function(openid){const data = this.getToken(openid);console.log("getUser",data);if(!data){var error = new Error('No token for ' + options.openid + ', please authorize first.');error.name = 'NoOAuthTokenError';throw error;}const token = AccessToken(data);var accessToken;if(token.isValid()){accessToken = token.data.session_key;}else{var newToken = await this.refreshAccessToken(token.data.refresh_token);accessToken = newToken.data.session_key}console.log('accessToken',accessToken)return await this._getUser(openid,accessToken);
}Auth.prototype._getUser = function(openid, accessToken,lang){return new Promise((resolve, reject) => {const url = "https://api.weixin.qq.com/sns/userinfo";const info = {access_token:accessToken,openid:openid,lang:lang||'zh_CN'}request.post(url,{form:info},(err, res, body) => {if(err){reject(err)}else{resolve(JSON.parse(body));}})})
}/*** 根据code,获取用户信息。* Examples:* ```* var user = yield api.getUserByCode(code);* ```* Exception:** - `err`, 获取用户信息出现异常时的异常对象** Result:* ```* {*  "openid": "OPENID",*  "nickname": "NICKNAME",*  "sex": "1",*  "province": "PROVINCE"*  "city": "CITY",*  "country": "COUNTRY",*  "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",*  "privilege": [*    "PRIVILEGE1"*    "PRIVILEGE2"*  ]* }* ```* @param {String} code 授权获取到的code*/
Auth.prototype.getUserByCode = async function(code){const token = await this.getAccessToken(code);return await this.getUser(token.data.openid);
}module.exports = Auth;

WXBizDataCrypt .js

var crypto = require('crypto')function WXBizDataCrypt(appId, sessionKey) {this.appId = appIdthis.sessionKey = sessionKey
}WXBizDataCrypt.prototype.decryptData = function (encryptedData, iv) {// base64 decodevar sessionKey = new Buffer.from(this.sessionKey, 'base64')encryptedData = new Buffer.from(encryptedData, 'base64')iv = new Buffer.from(iv, 'base64')try {// 解密var decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv)// 设置自动 padding 为 true,删除填充补位decipher.setAutoPadding(true)// 问题是cipher.update(data, 'binary')输出一个缓冲区,该缓冲区自动字符串化为十六进制编码的字符串var decoded = decipher.update(encryptedData,'binary',"utf8")// 这里有一个错误发生:// error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt// 本质是由于sessionKey与code不匹配造成的decoded += decipher.final('utf8')decoded = JSON.parse(decoded)} catch (err) {console.log('err',err);throw new Error('Illegal Buffer')}if (decoded.watermark.appid !== this.appId) {throw new Error('Illegal Buffer')}return decoded
}module.exports = WXBizDataCrypt

【愚公系列】2022年09月 微信小程序-微信小程序实现网页一键登录功能相关推荐

  1. 【愚公系列】2022年01月 华为鸿蒙OS-03-四种模式开发实操

    文章目录 前言 一.使用JS语言开发(传统代码方式) 1.index页面源码 2.details页面源码 二.使用JS语言开发(低代码方式) 1.新建工程:注意选择 2.选择低代码新建页面 3.页面分 ...

  2. 微信小程序一键登录功能,使用uni-app和springboot(JWT鉴权)

    目录 概述 微信登录接口说明 关于获取微信用户的信息 前端代码(uni-app) 后端代码(SpringBoot) 配置文件:application.yml 配置文件:Pom.xml 类:WeChat ...

  3. 【愚公系列】2022年09月 微信小程序-webview内嵌网页的授权认证

    文章目录 前言 一.webview内嵌网页的授权认证 1.内嵌页面 2.登录页面 二.web端相关函数 1.判断是否是小程序环境 前言 随着微信小程序的广泛应用,小程序的用户越来越多,但受其小程序体积 ...

  4. 【愚公系列】2022年10月 微信小程序-电商项目-微信支付功能前申请准备工作

    文章目录 前言 一.微信支付功能实现 1.微信公众平台的准备 2.微信商户平台的准备 前言 微信支付是腾讯集团旗下的第三方支付平台,致力于为用户和企业提供安全.便捷.专业的在线支付服务.以" ...

  5. 【愚公系列】2022年08月 微信小程序项目篇-抽奖轮盘

    文章目录 前言 一.抽奖轮盘 1.标题布局 1.1 CSS 1.2 HTML 1.4 效果 2.轮盘布局 2.1 CSS 2.2 HTML 2.3 效果 3.轮盘分割 3.1 CSS 3.2 HTML ...

  6. 【愚公系列】2022年02月 微信小程序-sitemap站内搜索

    文章目录 前言 1.sitemap.json介绍 2.小程序爬虫特征 一.sitemap 配置 1.rules配置项 1.1 rules 1.1.1 matching 二.配置示例 前言 1.site ...

  7. 【愚公系列】2022年10月 使用win11系统自带远程桌面,远程控制VMware中Windows虚拟机系统

    文章目录 前言 1.cpolar简介 2.cpolar功能 一.使用win11系统自带远程桌面,远程控制VMware虚拟机系统 1.注册cpolar账号 2.下载cpolar客户端 3. 被控端电脑启 ...

  8. 【愚公系列】2022年12月 使用win11系统自带SSH,远程控制VMware中Liunx虚拟机系统

    文章目录 前言 1.cpolar简介 2.cpolar功能 一.使用win11系统自带SSH,远程控制VMware中Liunx虚拟机系统 1.注册cpolar账号 2.下载最新版Ubuntu系统 3. ...

  9. 【愚公系列】2022年12月 使用win11系统自带SSH,远程控制VMware中Windows虚拟机系统

    前言 身为开发人员,虚拟化系统是经常用到的,因为虚拟化能隔绝环境,虚拟出各种各样系统给开发人员测试.不仅仅是VMware虚拟机,还有服务部署docker,k8s等等虚拟化无处不在.本文就尝试使用cpo ...

最新文章

  1. java实现序列化接口6_只有实现 Java.io. 接口的类的对象才能被序列化和反序列化。用关键字 修饰的对象变量将不会序列化。_程序设计基础(C#)答案_学小易找答案...
  2. javascript常用小例子
  3. 使用 go 实现 Proof of Stake 共识机制
  4. java中static{}语句块详解
  5. iOS之深入解析CocoaPods的插件机制和如何加载插件整合开发工具
  6. 通常每个套接字地址只允许使用一次
  7. P5170 【模板】类欧几里得算法(类欧)
  8. CentOS7安装wxWidgets错误解决
  9. python开发板卡驱动开发_一款能让你发挥无限创意的MicroPython开发板—TPYBoard开发板测...
  10. php 同时登录怎么办,php 实现同一个账号同时只能一个人登录
  11. 什么会造成os.chdir not nonetype_Python有什么不为人知的坑?
  12. WPF 设置TextBox和PasswordBox设置光标位置
  13. 数据库 ER图、关系模式相互转换 关系代数表达式 查询树,优化查询树 SQL题目
  14. 用大数据调控旅游市场
  15. usb keyboard找不到驱动程序_台式机也能用上蓝牙,毕亚兹USB蓝牙适配器体验
  16. IT行业为何如此吃香?2019学习IT就业前景分析
  17. treegrid 与java交互_EXTJS实现的TREEGRID(后台java,框架SpringMVC)
  18. NVIDIA Jetson官网资料整理
  19. 计算机cpu选购注意事项,电脑选购指南 购买电脑的注意事项
  20. 邀请码 java_邀请码生成器Java代码详解

热门文章

  1. python中save是什么意思_Python中的numpy.save()和joblib.dump()有什么区别?
  2. excel使用教程_数据分析Excel必备技能:数据透视表使用教程
  3. 自己动手搭建苹果推送Push服务器
  4. “2.17亿中国电信”拿下国家税务局云平台项目,H3C却是最大赢家
  5. 大家查找医疗英文文献都去哪个网?
  6. 绿色养眼桌面壁纸[4P]
  7. 会声会影批量处理素材设置教程
  8. 【python】cholesky
  9. vue3 使用element表格导出excel表格(带图片)
  10. 用ps提取彩色图像的线稿