您所在的位置:首页 / 知识分享

彻底搞懂小程序登录流程

2018.11.01

5387

男神苟毅

彻底搞懂小程序登录流程

用户登录是大部分完整 App 必备的流程

一个简单的用户系统需要关注至少这些层面

  • 安全性(加密)
  • 持久化登录态(类似cookie)
  • 登录过期处理
  • 确保用户唯一性, 避免出现多账号
  • 授权
  • 绑定用户昵称头像等信息
  • 绑定手机号(实名和密保方式)

很多的业务需求都可以抽象成 Restful 接口配合 CRUD 操作

但登录流程却是错综复杂, 各个平台有各自的流程, 反倒成了项目中费时间的部分, 比如小程序的登录流程


对于一个从零开始的项目来说, 搞定登录流程, 就是一个好的开始, 一个好的开始, 就是成功的一半

本文就以微信小程序这个平台, 讲述一个完整的自定义用户登录流程, 一起来啃这块难啃的骨头

名词解释

先给登录流程时序图中出现的名词简单做一个解释

  • code临时登录凭证, 有效期五分钟, 通过wx.login()获取
  • session_key会话密钥, 服务端通过 code2Session 获取
  • openId用户在该小程序下的用户唯一标识, 永远不变, 服务端通过 code 获取
  • unionId用户在同一个微信开放平台帐号(公众号, 小程序, 网站, 移动应用)下的唯一标识, 永远不变
  • appId小程序唯一标识
  • appSecret小程序的 app secret, 可以和 code, appId 一起换取 session_key

其他名词

  • rawData不包括敏感信息的原始数据字符串,用于计算签名
  • encryptedData包含敏感信息的用户信息, 是加密的
  • signature用于校验用户信息是否无篡改
  • iv加密算法的初始向量

哪些信息是敏感信息呢? 手机号, openId, unionId, 可以看出这些值都可以唯一定位一个用户, 而昵称, 头像这些不能定位用户的都不是敏感信息

小程序登录相关函数

  • wx.login
  • wx.getUserInfo
  • wx.checkSession

小程序的 promise

我们发现小程序的异步接口都是 success 和 fail 的回调, 很容易写出回调地狱

因此可以先简单实现一个 wx 异步函数转成 promise 的工具函数

const promisify = original => { return function(opt) { return new Promise((resolve, reject) => {
      opt = Object.assign({ success: resolve, fail: reject
      }, opt)
      original(opt)
    })
  }
} 复制代码

这样我们就可以这样调用函数了

promisify(wx.getStorage)({key: 'key'}).then(value => { // success }).catch(reason => { // fail }) 复制代码

服务端实现

本 demo 的服务端实现基于 express.js

注意, 为了 demo 的简洁性, 服务端使用 js 变量来保存用户数据, 也就是说如果重启服务端, 用户数据就清空了

如需持久化存储用户数据, 可以自行实现数据库相关逻辑

// 存储所有用户信息 const users = { // openId 作为索引 openId: { // 数据结构如下 openId: '', // 理论上不应该返回给前端 sessionKey: '', nickName: '', avatarUrl: '', unionId: '', phoneNumber: '' }
}

app
  .use(bodyParser.json())
  .use(session({ secret: 'alittlegirl', resave: false, saveUninitialized: true })) 复制代码

小程序登录

我们先实现一个基本的 oauth 授权登录

oauth 授权登录主要是 code 换取 openId 和 sessionKey 的过程

前端小程序登录

写在 app.js 中

login () { console.log('登录') return util.promisify(wx.login)().then(({code}) => { console.log(`code: ${code}`) return http.post('/oauth/login', {
      code, type: 'wxapp' })
  })
} 复制代码

服务端实现 oauth 授权

服务端实现上述/oauth/login这个接口

app
  .post('/oauth/login', (req, res) => { var params = req.body var {code, type} = params if (type === 'wxapp') { // code 换取 openId 和 sessionKey 的主要逻辑 axios.get('https://api.weixin.qq.com/sns/jscode2session', { params: { appid: config.appId, secret: config.appSecret, js_code: code, grant_type: 'authorization_code' }
      }).then(({data}) => { var openId = data.openid var user = users[openId] if (!user) {
          user = {
            openId, sessionKey: data.session_key
          }
          users[openId] = user console.log('新用户', user)
        } else { console.log('老用户', user)
        }
        req.session.openId = user.openId
        req.user = user
      }).then(() => {
        res.send({ code: 0 })
      })
    } else { throw new Error('未知的授权类型')
    }
  }) 复制代码

获取用户信息

登录系统中都会有一个重要的功能: 获取用户信息, 我们称之为getUserInfo

如果已登录用户调用 getUserInfo 则返回用户信息, 比如昵称, 头像等, 如果未登录则返回"用户未登录"

也就是说此接口还有判断用户是否登录的功效...

小程序的用户信息一般存储在app.globalData.userInfo中(模板如此)

我们在服务端加上前置中间件, 通过 session 来获取对应的用户信息, 并放在 req 对象中

app
  .use((req, res, next) => {
    req.user = users[req.session.openId]
    next()
  }) 复制代码

然后实现/user/info接口, 用来返回用户信息

app
  .get('/user/info', (req, res) => { if (req.user) { return res.send({ code: 0, data: req.user
      })
    } throw new Error('用户未登录')
  }) 复制代码

小程序调用用户信息接口

getUserInfo () { return http.get('/user/info').then(response => { let data = response.data if (data && typeof data === 'object') { // 获取用户信息成功则保存到全局 this.globalData.userInfo = data return data
    } return Promise.reject(response)
  })
} 复制代码

专为小程序发请求设计的库

小程序代码通过http.get,http.post这样的 api 来发请求, 背后使用了一个请求库

@chunpu/http 是一个专门为小程序设计的 http 请求库, 可以在小程序上像 axios 一样发请求, 支持拦截器等强大功能, 甚至比 axios 更顺手

初始化方法如下

import http from '@chunpu/http' http.init({ baseURL: 'http://localhost:9999', // 定义 baseURL, 用于本地测试 wx // 标记是微信小程序用 }) 复制代码

具体使用方法可参照文档 github.com/chunpu/http…

自定义登录态持久化

浏览器有 cookie, 然而小程序没有 cookie, 那怎么模仿出像网页这样的登录态呢?

这里要用到小程序自己的持久化接口, 也就是 setStorage 和 getStorage

为了方便各端共用接口, 或者直接复用 web 接口, 我们自行实现一个简单的读 cookie 和种 cookie 的逻辑

先是要根依据返回的 http response headers 来种上 cookie, 此处我们用到了@chunpu/http中的 response 拦截器, 和 axios 用法一样

http.interceptors.response.use(response => { // 种 cookie var {headers} = response var cookies = headers['set-cookie'] || '' cookies = cookies.split(/, */).reduce((prev, item) => {
    item = item.split(/; */)[0] var obj = http.qs.parse(item) return Object.assign(prev, obj)
  }, {}) if (cookies) { return util.promisify(wx.getStorage)({ key: 'cookie' }).catch(() => {}).then(res => {
      res = res || {} var allCookies = res.data || {} Object.assign(allCookies, cookies) return util.promisify(wx.setStorage)({ key: 'cookie', data: allCookies
      })
    }).then(() => { return response
    })
  } return response
}) 复制代码

当然我们还需要在发请求的时候带上所有 cookie, 此处用的是 request 拦截器

http.interceptors.request.use(config => { // 给请求带上 cookie return util.promisify(wx.getStorage)({ key: 'cookie' }).catch(() => {}).then(res => { if (res && res.data) { Object.assign(config.headers, { Cookie: http.qs.stringify(res.data, ';', '=')
      })
    } return config
  })
}) 复制代码

登录态的有效期

我们知道, 浏览器里面的登录态 cookie 是有失效时间的, 比如一天, 七天, 或者一个月

也许有朋友会提出疑问, 直接用 storage 的话, 小程序的登录态有效期怎么办?

问到点上了! 小程序已经帮我们实现好了 session 有效期的判断wx.checkSession

它比 cookie 更智能, 官方文档描述如下

通过 wx.login 接口获得的用户登录态拥有一定的时效性。用户越久未使用小程序,用户登录态越有可能失效。反之如果用户一直在使用小程序,则用户登录态一直保持有效

也就是说小程序还会帮我们自动 renew 咱们的登录态, 简直是人工智能 cookie, 点个赞

相关新闻

微信定制开发-客美美

2015.08.28

4716

微信定制开发-客美美

JS函数节流和函数防抖

2018.09.14

2149

JS函数节流和函数防抖的原理与应用场景

你可能不是那么了解的 CSS Background

2020.01.16

1307

MDN 中对其的定义如下: Background 是一种 CSS 简写属性,一次性定义了所有的背景属性,包括 color, image, origin 还有 size, repeat 方式等等。