首先我们来谈谈在小程序开发中,如何利用微信的能力来识别用户。
微信官方提供了两种logo:
这是小程序/公众号的用户ID,开发者可以通过此ID识别用户。
是同一主体微信小程序/公众号/APP的用户标识,开发者需要在微信开放平台绑定同一账号的主体,开发者可以通过它实现多个小程序、公众号甚至APP之间的数据互通。
同一个用户的两个ID对于同一个小程序来说是永久的,即使用户删除了小程序,开发者在用户下次进入小程序时仍然可以通过后台记录识别用户,那么如何求和呢?
早期的小程序设计(2018年4月之前)都是使用wx.接口来获取用户信息,设计这个接口的初衷是希望开发者在真正需要用户信息(比如头像、昵称、手机号等)的时候,才调用这个接口。但是很多开发者为了获取,会在小程序启动的时候直接调用这个接口,给用户使用小程序带来了困扰。总结起来有几点:
当开发者在小程序首页直接调用wx.进行授权,弹窗获取用户信息时,部分用户会点击“拒绝”按钮。
如果开发者不对用户拒绝弹窗进行处理,用户必须授权头像、昵称等信息才能继续使用小程序,这会导致部分用户放弃使用小程序。
用户重新授权没有什么好办法,虽然微信官方已经增加了设置页面,可以让用户选择重新授权,但是很多用户并不知道可以这么做。
微信官方也意识到了这个问题,更新了三项获取用户信息的能力:
使用组件获取用户信息。
如果用户满足一定条件,可以通过wx.获取的code直接进行兑换。
wx. 可以不依赖wx来获取数据。
本文主要讲第二种能力,微信官方鼓励开发者在不骚扰用户的情况下合理获取,只在必要时才弹窗给用户申请使用昵称和头像,由此衍生出“静默登录”和“用户登录”的概念。
2.什么是静默登录?
小程序可以通过微信官方登录能力轻松获取微信提供的用户身份标识,快速建立小程序内的用户体系。
很多开发者会把wx.和wx.绑定调用当做,其实wx.已经可以完成了,wx.只是额外获取了用户信息而已。
wx.获取code之后会发送给开发者后台,开发者后台通过接口和微信后台交换code(现在也会一起返回),然后返回自定义的登录态(本服务中名为auth-)给前端,登录行为就完成了。wx.行为是静默的,不需要授权,用户根本察觉不到。
wx. 的存在只是为了提供更好的服务,比如获取用户手机号注册会员,或者展示头像昵称,判断性别等。开发者可以结合其他公众号上已有的用户画像,提供历史数据。这样开发者就不必在用户刚进入小程序时就强制用户授权。
2.1 静默登录流程顺序
wx.官方给出的最佳实践如下:
静默登录的英文缩写如下:
private async silentLogin(): Promise { try { this.status.silentLogin.ing(); // 获取临时登录凭证code const code = await getWxLoginCode(); // 将code发送给服务端 const res = await API.login(code); // 保存登录信息,如auth-token storage.setSync(constant.STORAGE_SESSION_KEY, res.data); this.status.silentLogin.success(); } catch (error) { logger.error('静默登录失败', error); this.status.silentLogin.fail(error); throw error; } } 复制代码
概括起来可以分为以下三个步骤:
小程序调用wx.()获取临时登录凭证码并返回给开发者服务器。
服务端调用auth.接口获取用户唯一标识和会话密钥。
开发者服务器可以根据用户ID生成自定义的登录态(例如auth-),用于后续业务逻辑中前后端交互时识别用户。
2.2 开发者后端验证及解密开放数据
静默登录成功之后,微信服务器会发送消息给服务器,这个消息在需要获取微信开放数据的时候会用到。
为了保证开放接口返回的用户数据的安全,微信会对明文数据进行签名,开发者可以根据业务需求对数据包进行签名校验,保证数据的完整性。
小程序在调用接口(如wx.)获取数据时,若用户已经授权,接口会同时返回以下字段,若用户未授权,会先弹出弹窗,若用户点击同意授权,接口会同时返回以下字段,反之,若用户拒绝授权,则调用失败。
属性类型描述
用户信息对象,不包含敏感信息
不含敏感信息的原始数据字符串,用于计算签名
使用sha1(+)获取字符串来验证用户信息
包含敏感数据在内的完整用户信息加密数据
四
加密算法初始化向量
只有开启云端开发的小程序才会返回敏感数据对应的云端ID,开放数据可通过云端调用直接获取。
开发者将数据发送到开发者服务器进行验证,服务器使用同样的算法计算用户的签名并进行比对,验证数据的完整性,开发者服务器告诉前端开发者该数据是可信的,可以安全使用用户信息数据。

如果开发者想要获取敏感数据(比如,),则会将和iv发送到开发者的服务器,服务器使用(对称解密密钥)进行对称解密,获取敏感数据进行存储并返回给前端开发者。
注意:因为获取手机号是需要用户主动触发接口的,所以该函数不是API调用的(也就是上面提到的wx.get无法获取手机号),需要通过点击组件来触发。get和iv,同时也发送给开发者服务器,开发者服务器使用(对称解密密钥)进行对称解密,得到对应的手机号。
值得注意的是,2021年2月23日,微信团队发布《小程序登录及用户信息相关接口调整说明》,作出以下调整:
自2021年2月23日起,通过wx.接口获取的登录凭证可以直接兑换。
2021年4月13日后发布的新版本小程序将无法通过wx.接口获取用户个人信息(头像、昵称、性别、地域),直接获取匿名数据,接口获取加密数据的能力不会进行调整。
新增获取用户头像、昵称、性别、地域信息的接口(基础库2.10.4版本支持),开发者每次通过此接口获取用户个人信息时均需进行确认。
即开发者通过组件调用wx.时,不会有任何弹窗,直接返回匿名用户个人信息。如果要获取用户头像、昵称、性别、地域信息,需要将其改造成wx.的接口。
2.3 有效期
若开发者遇到因操作不正确导致签名验证失败或者解密失败的情况,请留意以下相关注意事项。
当调用 wx. 时,用户的账号可能会被更新,旧的账号可能会失效(刷新机制有最小周期,如果同一个用户在短时间内多次调用 wx.,并不是每次调用都会刷新),开发者应该只在明确需要重新登录时才调用 wx.,并及时通过 auth. 接口更新服务器存储。
微信不会告知开发者有效期,我们会根据用户使用小程序的行为更新有效期,用户使用小程序的频率越高,有效期越长。
当登录失败时,开发者可以通过重新执行登录流程来获取一个有效的,可以使用wx.接口来验证是否有效,从而避免小程序重复执行登录流程。
开发者在实现自定义登录态时,可以考虑将有效期作为自己的登录态有效期,也可以实现自定义的时效性策略。
3. 登录架构
用户登录架构
“登录”解决方案架构如上图所示,所有登录相关的功能都抽象到“层”(以此项目命名)中,供“业务层”调用。本文主要讲述灰色部分内容,其他模块将在下一篇《小程序用户登录设计》中讲述。
3.- 提供登录相关的类方法供“业务层”调用
封装类,提供类方法供“业务层”调用。主要有以下几个方法:
方法名称 功能 使用场景
启动静默登录
登录,方法的一层封装
用于在小程序启动时启动静默登录
刷新登录态,方法的一层封装
用于在登录状态过期时启动静默登录
验证是否过期,如果过期则刷新登录状态
绑定微信授权手机号时,验证是否过期,如果过期,需要重新弹授权窗口。
装饰者:
4.何时调用静默登录 4.1 小程序启动时调用
由于大多数情况下需要依赖登录态,因此在小程序启动时(app.())调用静默登录是最常见的方法。这里我们封装一个函数如下所示。首先调用wx.判断是否过期,如果没有过期,并且本地存在自定义的登录态,则说明当前的静默登录态还有效,不需要进行其他操作。否则,说明静默登录态无效或者新用户从未发起过静默登录,因此发起静默登录流程。
public async login(): Promise { // 调用wx.checkSession判断session_key是否过期 const hasSession = await checkSession(); // 本地已有可用登录态且session_key未过期,resolve。 if (this.getAuthToken() && hasSession) return Promise.resolve(); // 否则,发起静默登录 await this.silentLogin(); } 复制代码
但在原生小程序启动过程中,App 和 Page 的生命周期钩子函数并不支持异步阻塞,因此很有可能出现小程序页面加载完成后,静默登录流程尚未完成,从而导致后续一些依赖登录态的操作(如发起请求)出错。
4.2 发起接口请求时调用
为了保险起见,如果某些接口需要携带自定义的登录态进行认证,那么就需要在发起请求的时候进行拦截,验证登录态,然后刷新登录,刷新登录的代码如下:
public async refreshLogin(): Promise { try { // 清除 Session this.clearSession(); // 发起静默登录 await this.silentLogin(); } catch (error) { throw error; } } 复制代码
整个流程如下图所示:
4.3 wx. 罢工之谜
基于上述接口请求发起时调用的流程,很多人会有疑问,既然服务端会返回auth-状态码,那为什么不在请求发送前进行拦截,使用wx.接口来检查登录态是否过期呢(如下图所示,添加红框内的步骤)?
这是因为我们通过实验发现,wx.在登录态过期时,有一定概率返回true。也就是说,增加wx.这个步骤并不能100%保证登录态不会过期,后面还是需要对不同的状态码做处理。