最近学习了不少关于小程序的知识,把登录功能的实现总结一下。

准备

首先我们要微信公众平台中注册小程序开发者账号,并在开发->开发设置中找到小程序ID(AppID)和小程序密钥(AppSecret),其次下载微信开发者工具,然后打开开发工具利用 AppID 创建项目。后端采用 Express + MySQL来实现。

过程

小程序登录的实现流程可以从下面这张官方的流程图来了解,以下将按照流程一步步实现。
微信小程序登录实战 - 图1
官方图
注意:微信改版后不能通过 wx.getUserInfo 和 wx.authorize({scope: “scope.userInfo”}) 来自动弹出授权提示框。

1、小程序的 index.wxml 中添加组件 button 设置 open-type 类型来获取用户数据

  1. <!-- index.html -->
  2. <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,小程序端完成。

  1. // index.js 伪代码
  2. const app = getApp();
  3. Page({
  4. getUserInfo: function(e) {
  5. let userInfo = e.detail.userInfo;
  6. app.globalData.userInfo = userInfo;
  7. app.doLogin(userInfo, callback);
  8. }
  9. });

1.2 在 app.js 中实现登录

  1. // app.js 伪代码
  2. App({
  3. doLogin: function(userInfo, callback = () => {}) {
  4. // wechat login
  5. wx.login({
  6. // Log in successfully callback
  7. success: res => {
  8. if (res.code && userInfo) {
  9. // send res.code to server to get openId, sessionKey, unionId
  10. wx.request({
  11. url: loginUrl, // 登录 URL
  12. data: {
  13. code: res.code, // 临时登录凭证
  14. rawData: userInfo.rawData, // 用户非敏感信息
  15. signature: userInfo.signature, // 签名
  16. encryptedData: userInfo.encryptedData, // 用户敏感信息
  17. iv: userInfo.iv // 解密算法的向量
  18. },
  19. // Log in successfully callback
  20. success: function (res) {
  21. console.log('login success', res);
  22. res = res.data;
  23. if (res.result == 0) {
  24. // save login state
  25. wx.setStorageSync('loginFlag', res.skey);
  26. callback();
  27. } else {
  28. console.log(res.errmsg);
  29. }
  30. },
  31. fail: function (error) {
  32. console.log(error, 'Login request failed');
  33. }
  34. });
  35. }
  36. },
  37. fail: error => {
  38. console.log(error, 'Interface call failed');
  39. }
  40. })
  41. },
  42. globalData: {
  43. userInfo: null
  44. }
  45. })

2、后端通过 Express 的路由定义 loginUrl 请求,使用中间件 authorizeMiddleware 处理

  1. // app.js 伪代码
  2. const { authorizeMiddleware } = require('./middleware/auth');
  3. const loginRouter = require('./routes/login');
  4. const express = require('express');
  5. let app = new express();
  6. app.use('/login', authorizeMiddleware, loginRouter);

2.1 在 authorizeMiddleware,实现登录凭证调用接口,并返回自定义登录状态

  1. // authorizeMiddleware/auth.js 伪代码
  2. const $ = require('axios');
  3. const {appid, appSecret} = require('../config/config');
  4. // get session_key and openid
  5. function getSessionKey(code, appid, appSecret) {
  6. const opt = {
  7. method: 'GET',
  8. url: 'https://api.weixin.qq.com/sns/jscode2session', // 微信开放接口
  9. params: {
  10. appid: appid, // 小程序ID
  11. secret: appSecret, // 小程序密钥
  12. js_code: code, // 临时登录凭证
  13. grant_type: 'authorization_code' // 授权类型, 固定
  14. }
  15. };
  16. return $(opt).then(res => {
  17. return res.data;
  18. })
  19. }

2.2 根据 session_key 和 openid 定义自己登录状态,解密数据并存入数据库

  1. // authorizeMiddleware/auth.js 伪代码
  2. const crypto = require('crypto');
  3. function authorizeMiddleware (req, res, next) {
  4. const {
  5. code,
  6. encryptedData,
  7. iv
  8. } = req.query;
  9. return getSessionKey(code, appid, appSecret)
  10. .then(res => {
  11. // Custom login state, encrypted session_key
  12. const skey = crypto.createHash('sha1').update(res.session_key, 'utf8').digest('hex')
  13. // Decrypt userInfo
  14. let encrypted = new Buffer(encryptedData, 'base64');
  15. let session_key = new Buffer(res.session_key, 'base64');
  16. let iv = new Buffer(iv, 'base64');
  17. const decipher = crypto.createDecipheriv('aes-128-cbc', session_key, iv)
  18. let decrypted = decipher.update(encrypted, 'base64', 'utf8')
  19. decrypted += decipher.final('utf8');
  20. const userInfo = JSON.parse(decrypted);
  21. // save userInfo to database, return Promise {skey, userInfo}
  22. return saveUserInfo({userInfo, session_key, skey});
  23. })
  24. .then(result =>{
  25. res['auth'] = result;
  26. return next();
  27. })
  28. }
  29. module.exports = { authorizeMiddleware }

2.3 根据中间件的处理结果,返回路由处理结果

  1. // login.js 伪代码
  2. const express = require('express');
  3. const router = express.Router();
  4. router.get('/', function(req, res, next) {
  5. if(res['auth'] && res['auth']['userInfo']) {
  6. res.json(Object.assign(res['auth'], { result: 0 }));
  7. }
  8. });
  9. module.exports = router;