【愚公系列】2022年09月 微信小程序-微信小程序实现网页一键登录功能
文章目录
- 前言
- 一、微信小程序实现网页一键登录功能
- 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月 微信小程序-微信小程序实现网页一键登录功能相关推荐
- 【愚公系列】2022年01月 华为鸿蒙OS-03-四种模式开发实操
文章目录 前言 一.使用JS语言开发(传统代码方式) 1.index页面源码 2.details页面源码 二.使用JS语言开发(低代码方式) 1.新建工程:注意选择 2.选择低代码新建页面 3.页面分 ...
- 微信小程序一键登录功能,使用uni-app和springboot(JWT鉴权)
目录 概述 微信登录接口说明 关于获取微信用户的信息 前端代码(uni-app) 后端代码(SpringBoot) 配置文件:application.yml 配置文件:Pom.xml 类:WeChat ...
- 【愚公系列】2022年09月 微信小程序-webview内嵌网页的授权认证
文章目录 前言 一.webview内嵌网页的授权认证 1.内嵌页面 2.登录页面 二.web端相关函数 1.判断是否是小程序环境 前言 随着微信小程序的广泛应用,小程序的用户越来越多,但受其小程序体积 ...
- 【愚公系列】2022年10月 微信小程序-电商项目-微信支付功能前申请准备工作
文章目录 前言 一.微信支付功能实现 1.微信公众平台的准备 2.微信商户平台的准备 前言 微信支付是腾讯集团旗下的第三方支付平台,致力于为用户和企业提供安全.便捷.专业的在线支付服务.以" ...
- 【愚公系列】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 ...
- 【愚公系列】2022年02月 微信小程序-sitemap站内搜索
文章目录 前言 1.sitemap.json介绍 2.小程序爬虫特征 一.sitemap 配置 1.rules配置项 1.1 rules 1.1.1 matching 二.配置示例 前言 1.site ...
- 【愚公系列】2022年10月 使用win11系统自带远程桌面,远程控制VMware中Windows虚拟机系统
文章目录 前言 1.cpolar简介 2.cpolar功能 一.使用win11系统自带远程桌面,远程控制VMware虚拟机系统 1.注册cpolar账号 2.下载cpolar客户端 3. 被控端电脑启 ...
- 【愚公系列】2022年12月 使用win11系统自带SSH,远程控制VMware中Liunx虚拟机系统
文章目录 前言 1.cpolar简介 2.cpolar功能 一.使用win11系统自带SSH,远程控制VMware中Liunx虚拟机系统 1.注册cpolar账号 2.下载最新版Ubuntu系统 3. ...
- 【愚公系列】2022年12月 使用win11系统自带SSH,远程控制VMware中Windows虚拟机系统
前言 身为开发人员,虚拟化系统是经常用到的,因为虚拟化能隔绝环境,虚拟出各种各样系统给开发人员测试.不仅仅是VMware虚拟机,还有服务部署docker,k8s等等虚拟化无处不在.本文就尝试使用cpo ...
最新文章
- java实现序列化接口6_只有实现 Java.io. 接口的类的对象才能被序列化和反序列化。用关键字 修饰的对象变量将不会序列化。_程序设计基础(C#)答案_学小易找答案...
- javascript常用小例子
- 使用 go 实现 Proof of Stake 共识机制
- java中static{}语句块详解
- iOS之深入解析CocoaPods的插件机制和如何加载插件整合开发工具
- 通常每个套接字地址只允许使用一次
- P5170 【模板】类欧几里得算法(类欧)
- CentOS7安装wxWidgets错误解决
- python开发板卡驱动开发_一款能让你发挥无限创意的MicroPython开发板—TPYBoard开发板测...
- php 同时登录怎么办,php 实现同一个账号同时只能一个人登录
- 什么会造成os.chdir not nonetype_Python有什么不为人知的坑?
- WPF 设置TextBox和PasswordBox设置光标位置
- 数据库 ER图、关系模式相互转换 关系代数表达式 查询树,优化查询树 SQL题目
- 用大数据调控旅游市场
- usb keyboard找不到驱动程序_台式机也能用上蓝牙,毕亚兹USB蓝牙适配器体验
- IT行业为何如此吃香?2019学习IT就业前景分析
- treegrid 与java交互_EXTJS实现的TREEGRID(后台java,框架SpringMVC)
- NVIDIA Jetson官网资料整理
- 计算机cpu选购注意事项,电脑选购指南 购买电脑的注意事项
- 邀请码 java_邀请码生成器Java代码详解
热门文章
- python中save是什么意思_Python中的numpy.save()和joblib.dump()有什么区别?
- excel使用教程_数据分析Excel必备技能:数据透视表使用教程
- 自己动手搭建苹果推送Push服务器
- “2.17亿中国电信”拿下国家税务局云平台项目,H3C却是最大赢家
- 大家查找医疗英文文献都去哪个网?
- 绿色养眼桌面壁纸[4P]
- 会声会影批量处理素材设置教程
- 【python】cholesky
- vue3 使用element表格导出excel表格(带图片)
- 用ps提取彩色图像的线稿