今年 2 月,微信团队针对小程序登录和用户信息获取进行了一次接口调整,这一举动史无前例地撼动了几乎所有小程序开发者,在小程序社区产生了不小的反响。
作为接入方,本文将从产品和技术两个角度,讨论微信新授权登录机制的设计目的、适配方案以及对产品带来的影响。
历史渊源:授权、登录与获取用户信息
在微信问世之前,来自 Twitter 的 OAuth 模型就已经足够普及。在 OAuth 模型中,B 业务想要操作用户在 A 业务中的数据,需要首先跳转到 A 业务的页面,让用户直接与 A 业务进行确认,称为授权(Authorization),得到 A 业务颁发给 B 业务的「兑换码」(一般称为 code),并回跳到 B 业务;之后,B 业务使用这个兑换码,通过后端直接联系 A 业务,一次性获得用于操作 A 业务数据的一系列凭证,并进行保存,后续则可以使用这些凭证调用 A 业务中的操作。
在 OAuth 模型中,Auth 这一缩写代表了两层含义:「授权(Authorization)」指用户直接联系 A 业务,对操作进行确认的过程;而「验证(Authentication)」则多指 A 业务对 B 业务的访问权进行校验的后续流程。
微信公众号网页开发实现了这一模型:
对于授权,公众号网页需要先跳转到微信提供的授权页面,由用户点击确认授权(如果无需获取用户信息,或用户已经授权,或已经关注过服务号,则无需确认,但仍然需要进行这一次跳转),授权页面会回跳到公众号网页业务,并带上 code 到网页参数;之后,业务方则需要通过后端拦截或前端 Ajax 请求等方式,经由业务自己的后端换取微信颁发的 OpenID/UnionID 以及一个 access_token 凭证;
对于登录,其本质是区分用户、颁发登录态。OpenID/UnionID 代表了用户在微信的身份信息,让业务后端能够区分相同和不同的用户,业务后端可以针对相同的用户颁发已有的登录态信息,访问同一个用户之前产生的数据;针对不同的用户,则可以创建新的帐号信息,下发新的登录态等;
对于获取用户信息,后端可以用 access_token 向微信请求获得。
小程序老授权登录
在微信小程序中,由于微信客户端拥有控制力,第三方业务与微信之间不再需要跳转,只要调用微信提供的接口,微信客户端可以保证授权的有效性,因此授权登录相比网页开发较为简单,但这同时也意味着微信随时可以改变接口的行为。直到目前,小程序授权登录的行为发生过两次改动:
小程序刚推出时,微信提供了 wx.login
和 wx.getUserInfo
两个接口,分别用于登录和授权获取用户信息;其中 wx.login
是静默的,会直接返回可换取 OpenID 的 code 和用于解密数据的 session_key;wx.getUserInfo
会弹出授权弹窗,经过授权后,会返回加密的用户数据,可以由后端凭 session_key 进行解密。加密的目的是为了防止用户篡改信息,从而限制违规头像昵称的产生,这一点不在本文的讨论范围内。
此后不久,wx.getUserInfo
被改为不再主动弹出授权,而是会在未授权情况下静默失败;授权则需要使用微信提供的专门 button 组件进行触发,这是为了遏止部分小程序一启动就弹出授权,或在本来不需要用户信息的情况下超标获取的问题。对于一些使用 WebView 的小程序,由于 WebView 页面中无法承载原生的 button 组件,这次改动就需要使用一个原生的授权页面来适配。
老的这一套授权登录流程虽然有它繁琐的地方,但长久以来,我们都忘记了其中最大的一个方便点:经过一次授权之后,小程序可以随时静默获取用户的信息。当用户更改了昵称或头像,只要打开已授权的小程序,小程序就能直接获取到修改后的信息,而无需用户再次授权。
新授权登录的产品形态
从官方文档中可以看出,小程序授权登录机制调整后,主要存在两个改动点:
- 在
wx.login
接口中,现在可以直接获取到用户的 UnionID,而无需通过授权获得,这其实是修复了之前的 Bug,同时鼓励各小程序业务尽可能不超标获取用户信息,遵循权限最小化原则; - 新增了
wx.getUserProfile
接口,代替原来的wx.getUserInfo
,新的接口会主动弹出授权,但只能一次性获取当前的昵称头像,而且仍然需要在用户点击后调用。
从产品角度而言,这两点分别带来了不同的变化:
第一点对于需要获取 UnionID 的业务来说,可以让静默登录的能力真正可用。可以让用户在不授权昵称头像的情况下,使用尽可能多的功能,在需要时才提示授权,对于产品本身登录过程的转化率有很大帮助,同时也帮助微信保护用户隐私,是一个双赢的举措;
而第二点变化则可以看作是微信对用户信息匿名化的一个尝试。在此之前,微信推出了自定义昵称头像的功能,可以在授权时设置自定义的昵称头像,区别于微信本身的昵称头像,实现一定的匿名使用;但由于老授权登录接口是一次授权,用户一旦设置了自定义的昵称头像就无法修改。因此,为了能将一次授权改为每次授权,微信通过新增一个接口,让开发者对这一行为进行主动适配。
根据微信的要求,4 月 13 日之后,新发布的版本在支持 wx.getUserProfile
的情况下,不能再使用 wx.getUserInfo
,否则将收到默认的昵称头像。从微信开发者工具中也可以提前看到这一改动:获取到的头像会变成一张默认头像,昵称变为「微信用户」。
而适配这个接口也意味着产品交互需要发生改变,因为在此之后,一次授权只能获得一次性的昵称和头像,当用户对微信昵称头像进行了修改,小程序业务不但不能静默获取到修改后的昵称头像,完全无从得知头像昵称发生了变化,都必须通过重新授权才能实现。
对于本来需要获取用户昵称头像的小程序业务,我们探讨了一种比较合理的适配方式:模拟原来的一次授权、并且提供手动修改头像昵称的入口。通过对登录逻辑的改造,可以实现尽可能模拟原有的授权交互,只在首次使用时进行授权,但经过这一次授权后,昵称头像不会再更新;因此需要提供手动修改头像昵称的入口,让用户对「头像昵称不会自动同步、可以手动更新或修改」的行为有预期。
技术适配方案:以文档小程序为例
在文档小程序中,原有的登录接口是二合一的,即前端先调用完 wx.login
和 wx.getUserInfo
,得到 code 和加密的用户信息,然后一起调用后端接口,后端查询已有用户或创建新用户,并对用户头像昵称信息进行更新。
在新授权登录的背景下,这样的后端接口已经无法满足要求。为了尽可能还原原有的一次授权交互,我们需要先判断用户是否已授权,然后根据是否已有昵称头像,决定是否调用授权并上传用户的昵称头像。另外,在用户选择手动更新昵称头像时,也需要有接口负责上传更新用户的昵称头像。
因此,为了保持接口设计的合理性,我们将登录接口拆分成两个接口,并搭配获取业务用户信息的接口进行实现:
- 前端调用
wx.login
,得到 code,并调用后端登录接口,得到提前颁发的登录态; - 前端调用现有的获取业务用户信息接口,判断业务是否已获得昵称头像,如果已有昵称头像,直接保存 1 中的登录态,登录完成;
- 如果还未获得昵称头像,则弹出授权,授权后调用后端上传昵称头像接口,将得到的昵称头像进行保存;
- 根据产品交互设计,在用户选择更新昵称头像时,重复 3 中的步骤,授权并更新昵称头像。
由于 wx.getUserProfile
只返回了明文的昵称头像等信息,我们还需要对上传昵称头像的接口接入内容安全保护,防止用户任意上传不合规的昵称头像信息。
除此之外,在 QQ 小程序以及 PC/Mac 微信小程序的情况下,新的授权登录接口可能并没有支持,需要对不支持 wx.getUserProfile
接口的情况进行兼容,改用原有的授权登录逻辑。
总结
本文探讨了微信小程序新的授权登录机制。新的机制下,授权被局限于获取用户信息的一次性操作之内,从而在鼓励开发者尽可能少要求授权的同时,让用户隐私得到一定程度的保护。对于产品形态而言,这要求产品在交互上强化用户头像昵称的可定制性;从开发角度,则需要根据具体业务情况,投入一定的精力进行适配。
与此同时,我们也能看出在小程序授权登录内外的一些细节问题:接口频繁变动、适配期限太匆忙,会导致开发者疲于适配,对平台产生消极情绪;另外,比起头像昵称这些常规信息的定制,真正属于用户隐私的手机号码、实名身份等信息仍然需要更严格的管控,才能在隐私安全的方向上走得更远。
小程序是一个为控制力而生的平台,但我们更希望这样的控制力去约束不守规矩的开发者,管住一味营销、缺乏实用性的产品,放手让用心打磨的项目走得更远。
Bacteria 2021 年 10 月 10 日
目前很多小程序会要求第一次使用的用户授权微信账号、绑定的手机号码、位置服务等,部分小程序提供的功能根本用不到这些,但还是会申请(说是申请但拒绝就没法用)