最近学习了不少关于小程序的知识,把登录功能的实现总结一下。
准备
首先我们要微信公众平台中注册小程序开发者账号,并在开发->开发设置中找到小程序ID(AppID)和小程序密钥(AppSecret),其次下载微信开发者工具,然后打开开发工具利用 AppID 创建项目。后端采用 Express + MySQL来实现。
过程
小程序登录的实现流程可以从下面这张官方的流程图来了解,以下将按照流程一步步实现。
官方图
注意:微信改版后不能通过 wx.getUserInfo 和 wx.authorize({scope: “scope.userInfo”}) 来自动弹出授权提示框。
1、小程序的 index.wxml 中添加组件 button 设置 open-type 类型来获取用户数据
<!-- index.html --><button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
1.1 在 index.js 的 page 方法中添加上面 bindgetuserinfo 方法,并将用户数据赋予全局,发起登录 wx.login 获取 code,将 code 和 userInfo 通过 wx.request 往后台服务器(Express)发送请求 loginUrl,将返回的自定义登录状态同步存入 Storage,小程序端完成。
// index.js 伪代码const app = getApp();Page({getUserInfo: function(e) {let userInfo = e.detail.userInfo;app.globalData.userInfo = userInfo;app.doLogin(userInfo, callback);}});
1.2 在 app.js 中实现登录
// app.js 伪代码App({doLogin: function(userInfo, callback = () => {}) {// wechat loginwx.login({// Log in successfully callbacksuccess: res => {if (res.code && userInfo) {// send res.code to server to get openId, sessionKey, unionIdwx.request({url: loginUrl, // 登录 URLdata: {code: res.code, // 临时登录凭证rawData: userInfo.rawData, // 用户非敏感信息signature: userInfo.signature, // 签名encryptedData: userInfo.encryptedData, // 用户敏感信息iv: userInfo.iv // 解密算法的向量},// Log in successfully callbacksuccess: function (res) {console.log('login success', res);res = res.data;if (res.result == 0) {// save login statewx.setStorageSync('loginFlag', res.skey);callback();} else {console.log(res.errmsg);}},fail: function (error) {console.log(error, 'Login request failed');}});}},fail: error => {console.log(error, 'Interface call failed');}})},globalData: {userInfo: null}})
2、后端通过 Express 的路由定义 loginUrl 请求,使用中间件 authorizeMiddleware 处理
// app.js 伪代码const { authorizeMiddleware } = require('./middleware/auth');const loginRouter = require('./routes/login');const express = require('express');let app = new express();app.use('/login', authorizeMiddleware, loginRouter);
2.1 在 authorizeMiddleware,实现登录凭证调用接口,并返回自定义登录状态
// authorizeMiddleware/auth.js 伪代码const $ = require('axios');const {appid, appSecret} = require('../config/config');// get session_key and openidfunction getSessionKey(code, appid, appSecret) {const opt = {method: 'GET',url: 'https://api.weixin.qq.com/sns/jscode2session', // 微信开放接口params: {appid: appid, // 小程序IDsecret: appSecret, // 小程序密钥js_code: code, // 临时登录凭证grant_type: 'authorization_code' // 授权类型, 固定}};return $(opt).then(res => {return res.data;})}
2.2 根据 session_key 和 openid 定义自己登录状态,解密数据并存入数据库
// authorizeMiddleware/auth.js 伪代码const crypto = require('crypto');function authorizeMiddleware (req, res, next) {const {code,encryptedData,iv} = req.query;return getSessionKey(code, appid, appSecret).then(res => {// Custom login state, encrypted session_keyconst skey = crypto.createHash('sha1').update(res.session_key, 'utf8').digest('hex')// Decrypt userInfolet encrypted = new Buffer(encryptedData, 'base64');let session_key = new Buffer(res.session_key, 'base64');let iv = new Buffer(iv, 'base64');const decipher = crypto.createDecipheriv('aes-128-cbc', session_key, iv)let decrypted = decipher.update(encrypted, 'base64', 'utf8')decrypted += decipher.final('utf8');const userInfo = JSON.parse(decrypted);// save userInfo to database, return Promise {skey, userInfo}return saveUserInfo({userInfo, session_key, skey});}).then(result =>{res['auth'] = result;return next();})}module.exports = { authorizeMiddleware }
2.3 根据中间件的处理结果,返回路由处理结果
// login.js 伪代码const express = require('express');const router = express.Router();router.get('/', function(req, res, next) {if(res['auth'] && res['auth']['userInfo']) {res.json(Object.assign(res['auth'], { result: 0 }));}});module.exports = router;
