前端知库

小程序登录流程是怎样的?wx.login、code2Session 和自定义登录态各做什么?

小程序调 wx.login 拿到只能用一次、五分钟有效的 code,交给自己的服务器;服务器带上 appid 和 appSecret 调 code2Session 换回 openid、session_key(绑定了开放平台时还有 unionid),再生成自己的登录态返回给小程序。session_key 不能下发到前端,过期时用 wx.checkSession 检查后重新登录。

参考答案

简答

用户打开小程序,我们要知道他是谁。小程序先调 wx.login(),拿到一个临时登录凭证 code,把它发给自己的服务器。

服务器拿着小程序的 appid、appSecret 和这个 code,调用微信的 code2Session 接口,换回用户在这个小程序里的唯一标识 openid、会话密钥 session_key,小程序绑定了微信开放平台账号时还会返回 unionid。之后服务器按 openid 找到或创建自己的用户,生成一个自定义登录态,比如 token,返回给小程序,后面的请求都带上它。

这样设计是因为 appSecret 和 session_key 都不能出现在前端。code2Session 要求在服务器端调用;官方文档也说 session_key 是对用户数据加密签名的密钥,不应该下发到小程序。

要注意,code 只能用一次,有效期五分钟。session_key 会过期,再次调用 wx.login 也可能生成新的 session_key,让旧的失效。可以用 wx.checkSession 检查当前登录态是否过期,过期了再走一遍登录。

登录要经过哪几步

微信官方文档把流程分成三步:小程序调用 wx.login() 获取临时登录凭证 code,并回传到开发者服务器;服务器调用 code2Session 接口,换取 openid、unionid 和 session_key;之后服务器根据用户标识生成自定义登录态,用于后续前后端交互时识别用户身份。

小程序这一侧的代码很短。下面的写法来自 wx.login 的官方示例,拿到 code 后马上发给自己的接口:

JavaScript
wx.login({
  success(res) {
    if (res.code) {
      wx.request({
        url: 'https://example.com/onLogin',
        data: { code: res.code },
      });
    } else {
      console.log('登录失败!' + res.errMsg);
    }
  },
});

读者可能会问,为什么不直接让小程序去换 openid。因为换取时要用到小程序的 appSecret,而小程序的代码包会下载到用户手机上,放在里面等于公开。所以 code2Session 的文档开头就写明,这个接口应在服务器端调用,不可在小程序、网页、App 等前端直接调用。

服务器怎样调用 code2Session

服务器发一个 GET 请求到 https://api.weixin.qq.com/sns/jscode2session,带上四个参数:appid、secret(也就是 appSecret)、js_code(小程序传来的 code)和固定为 authorization_code 的 grant_type。

text
GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

成功时返回 openid 和 session_key;小程序绑定到微信开放平台账号时还会返回 unionid。失败时返回 errcode 和 errmsg,例如 40029 表示 code 无效,45011 表示调用太频繁。

这里要分清两个标识。openid 是用户在当前这个小程序里的唯一标识,同一个人在不同小程序里的 openid 不同。unionid 是用户在同一个微信开放平台账号下的唯一标识,需要把多个小程序、公众号的用户打通时才用它。

自定义登录态是什么,为什么不直接用 session_key

code2Session 只告诉服务器“这个用户是谁”,并没有替我们管理会话。所以服务器拿到 openid 后,要自己生成一个登录凭证返回给小程序,比如一个随机 token,服务器端记录它对应哪个用户、什么时候过期。小程序把它存起来,之后的请求都带上它,服务器据此识别用户。微信文档把这一步叫作生成自定义登录态。

不能拿 session_key 来充当这个凭证。官方文档说明,session_key 是对用户数据进行加密签名的密钥,为了数据安全,开发者服务器不应该把它下发到小程序,也不应该对外提供。服务器需要解密用户数据时,在服务器端用它就够了。

登录态过期了怎么办

wx.checkSession 的文档说明,通过 wx.login 获得的登录态有一定时效,用户越久没用小程序,越可能过期;一直在用则会保持有效,具体时效由微信维护,开发者看不到。除了过期,再次调用 wx.login 这类获取 code 的操作也可能生成新的 session_key,把旧的顶替掉。

wx.checkSession 检查的是最后一次获取 code 所对应的那个 session_key:调用成功说明它没过期,失败说明已经过期,这时再调用 wx.login 重新登录。如果要校验某个指定的 session_key,要在服务器端调用 checkSessionKey 接口。

JavaScript
wx.checkSession({
  success() {
    // session_key 未过期,继续使用已有的自定义登录态
  },
  fail() {
    // session_key 已过期,重新执行 wx.login 登录流程
  },
});

要注意,自定义登录态是服务器自己发的,它的有效期也由服务器决定,和 session_key 的过期是两件事。所以实际项目里,服务器返回登录态失效时,小程序也要重新走一遍 wx.login。

常见追问

wx.login 拿到的 code 能不能缓存起来重复用?

不能。官方文档说明临时登录凭证 code 只能使用一次,wx.login 的文档还写明它的有效期是五分钟。每次需要换取登录信息时,都要重新调用 wx.login。

为什么不要每次请求前都调用 wx.login?

因为获取 code 的操作可能生成新的 session_key,让旧的失效。如果服务器还在用旧的 session_key 解密数据,就会出错。所以通常先用 wx.checkSession 判断,只有过期或者自定义登录态失效时才重新登录。

openid 和 unionid 该用哪个做用户主键?

只有一个小程序时,用 openid 就够了。同一主体有多个小程序或公众号、需要识别为同一个用户时,把它们绑定到同一个微信开放平台账号,用 unionid 关联;code2Session 只在已绑定时才返回 unionid。