微信小程序 unionid 登录解决方案

avatar
技术支持 @LeanCloud

第三方登录模块使开发者能快捷灵活的拥有自己的用户系统,是 LeanCloud 最受欢迎的功能之一。随着第三方平台的演化,特别是微信小程序的流行,LeanCloud 第三方登录模块也一直在改进:

  • v2.0*:增加微信小程序一键登录功能。支持开发者不写任何后端代码实现微信小程序用户系统与 LeanCloud 用户系统的关联。
  • v3.6:增加 unionid 登录接口。支持开发者使用 unionid 关联一个微信开发者帐号下的多个应用从而共享一套 LeanCloud 用户系统。 这两个功能各自都非常简单可靠,但是其中重叠的部分需求却是一个难题:「如何在小程序中支持 unionid 登录,既能得到 unionid 登录机制的灵活性,又保留一键登录功能的便利性」。

在最近发布的 JavaScript SDK v3.13 中包含了微信小程序 unionid 登录支持。我们根据不同的需求设计了不同的解决方案。

* 这里的版本指开始支持该功能的 JavaScript SDK 版本。

一键登录

LeanCloud 的用户系统支持一键使用微信用户身份登录。要使用一键登录功能,需要先设置小程序的 AppID 与 AppSecret:

1.登录 微信公众平台,在 设置 > 开发设置 中获得 AppID 与 AppSecret。 前往 LeanCloud 控制台 > 组件 > 社交,保存「微信小程序」的 AppID 与 AppSecret。 这样你就可以在应用中使用AV.User.loginWithWeapp()方法来使用当前用户身份登录了。

AV.User.loginWithWeapp().then(user => {
  this.globalData.user = user;
}).catch(console.error);

使用一键登录方式登录时,LeanCloud 会将该用户的小程序 openidsession_key 等信息保存在对应的 user.authData.lc_weapp 属性中,你可以在控制台的 _User 表中看到:

{
  "authData": {
    "lc_weapp": {
      "session_key": "2zIDoEEUhkb0B5pUTzsLVg==",
      "expires_in": 7200,
      "openid": "obznq0GuHPxdRYaaDkPOHk785DuA"
    }
  }
}

如果用户是第一次使用此应用,调用登录 API 会创建一个新的用户,你可以在 控制台 > 存储 中的 _User 表中看到该用户的信息,如果用户曾经使用该方式登录过此应用(存在对应 openid 的用户),再次调用登录 API 会返回同一个用户。

用户的登录状态会保存在客户端中,可以使用 AV.User.current() 方法来获取当前登录的用户,下面的例子展示了如何为登录用户保存额外的信息:

// 假设已经通过 AV.User.loginWithWeapp() 登录
// 获得当前登录用户
const user = AV.User.current();
// 调用小程序 API,得到用户信息
wx.getUserInfo({
  success: ({userInfo}) => {
    // 更新当前用户的信息
    user.set(userInfo).save().then(user => {
      // 成功,此时可在控制台中看到更新后的用户信息
      this.globalData.user = user;
    }).catch(console.error);
  }
});

authData 默认只有对应用户可见,开发者可以使用 masterKey 在云引擎中获取该用户的 openidsession_key 进行支付、推送等操作。详情的示例请参考 支付

小程序的登录态(session_key)存在有效期,可以通过 wx.checkSession() 方法检测当前用户登录态是否有效,失效后可以通过调用 AV.User.loginWithWeapp() 重新登录。

使用 unionid

微信开放平台使用 unionid 来区分用户的唯一性,也就是说同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的 unionid 都是同一个,而 openid 会是多个。如果你想要实现多个小程序之间,或者小程序与使用微信开放平台登录的应用之间共享用户系统的话,则需要使用 unionid 登录。

要在小程序中使用 unionid 登录,请先确认已经在 微信开放平台 绑定了该小程序

在小程序中有很多途径可以 获取到 unionid。不同的 unionid 获取方式,接入 LeanCloud 用户系统的方式也有所不同。

一键登录时静默获取 unionid

当满足以下条件时,一键登录 API AV.User.loginWithWeapp() 能静默地获取到用户的 unionid 并用 unionid + openid 进行匹配登录。

  • 微信开放平台帐号下存在同主体的公众号,并且该用户已经关注了该公众号。
  • 微信开放平台帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用。 要启用这种方式,需要在一键登录时指定参数 preferUnionId 为 true:
AV.User.loginWithWeapp({
  preferUnionId: true,
});

使用 unionid 登录后,用户的 authData 中会增加 _weixin_unionid 一项(与 lc_weapp 平级):

{
  "authData": {
    "lc_weapp": {
      "session_key": "2zIDoEEUhkb0B5pUTzsLVg==",
      "expires_in": 7200,
      "openid": "obznq0GuHPxdRYaaDkPOHk785DuA",
      "unionid": "ox7NLs5BlEqPS4glxqhn5kkO0UUo"
    },
    "_weixin_unionid": {
      "uid": "ox7NLs5BlEqPS4glxqhn5kkO0UUo"
    }
  }
}

用 unionid + openid 登录时,会按照下面的步骤进行用户匹配:

  1. 如果已经存在对应 unionid(authData._weixin_unionid.uid)的用户,则会直接作为这个用户登录,并将所有信息(openidsession_keyunionid 等)更新到该用户的 authData.lc_ewapp 中。
  2. 如果不存在匹配 unionid 的用户,但存在匹配 openid(authData.lc_weapp.openid)的用户,则会直接作为这个用户登录,并将所有信息(session_keyunionid 等)更新到该用户的 authData.lc_ewapp 中,同时将 unionid 保存到 authData._weixin_unionid.uid 中。
  3. 如果不存在匹配 unionid 的用户,也不存在匹配 openid 的用户,则创建一个新用户,将所有信息(session_keyunionid 等)更新到该用户的 authData.lc_ewapp 中,同时将 unionid 保存到 authData._weixin_unionid.uid 中。

不管匹配的过程是如何的,最终登录用户的 authData 都会是上面这种结构。

LeanTodo Demo 便是使用这种方式登录的,如果你已经关注了其关联的公众号(搜索 AVOSCloud,或通过小程序关于页面的相关公众号链接访问),那么你在登录后会在 LeanTodo Demo 的 设置 - 用户 页面看到当前用户的 authData 中已经绑定了 unionid。

微信扫描二维码进入 Demo

需要注意的是:

  • 如果用户不符合上述静默获取 unionid 的条件,那么就算指定了 preferUnionId 也不会使用 unionid 登录。
  • 如果用户符合上述静默获取 unionid 的条件,但没有指定 preferUnionId,那么该次登录不会使用 unionid 登录,但仍然会将获取到的 unionid 作为一般字段写入该用户的 authData.lc_weapp 中。此时用户的 authData 会是这样的:
{
  "authData": {
    "lc_weapp": {
      "session_key": "2zIDoEEUhkb0B5pUTzsLVg==",
      "expires_in": 7200,
      "openid": "obznq0GuHPxdRYaaDkPOHk785DuA",
      "unionid": "ox7NLs5BlEqPS4glxqhn5kkO0UUo"
    }
  }
}
通过其他方式获取 unionid 后登录

如果开发者自行获得了用户的 unionid(例如通过解密 wx.getUserInfo 获取到的用户信息),可以在小程序中调用 AV.User.loginWithWeappWithUnionId() 投入 unionid 完成登录授权:

AV.User.loginWithWeappWithUnionId(unionid, {
  asMainAccount: true
}).then(console.log, console.error);
通过其他方式获取 unionid 与 openid 后登录

如果开发者希望更灵活的控制小程序的登录流程,也可以自行在服务端实现 unionid 与 openid 的获取,然后调用通用的第三方 unionid 登录接口指定平台为 lc_weapp 来登录:

const unionid = '';
const authData = {
  openid: '',
  session_key: ''
};
const platform = 'lc_weapp';
AV.User.loginWithAuthDataAndUnionId(authData, platform, unionid, {
  asMainAccount: true
}).then(console.log, console.error);

相对上面提到的一些 Weapp 相关的登录 API,loginWithAuthDataAndUnionId 是更加底层的第三方登录接口,不依赖小程序运行环境,因此这种方式也提供了更高的灵活度:

  • 可以在服务端获取到 unionid 与 openid 等信息后返回给小程序客户端,在客户端调用 AV.User.loginWithAuthDataAndUnionId 来登录。
  • 也可以在服务端获取到 unionid 与 openid 等信息后直接调用 AV.User.loginWithAuthDataAndUnionId 登录,成功后得到登录用户的 sessionToken 后返回给客户端,客户端再使用该 sessionToken 直接登录。
关联第二个小程序

这种用法的另一种常见场景是关联同一个开发者帐号下的第二个小程序。

因为一个 LeanCloud 应用默认关联一个微信小程序(对应的平台名称是 lc_weapp),使用小程序系列 API 的时候也都是默认关联到 authData.lc_weapp 字段上。如果想要接入第二个小程序,则需要自行获取到 unionid 与 openid,然后将其作为一个新的第三方平台登录。这里同样需要用到 AV.User.loginWithAuthDataAndUnionId 方法,但与关联内置的小程序平台(lc_weapp)有一些不同:

  • 需要指定一个新的 platform
  • 需要将 openid 保存为 uid(内置的微信平台做了特殊处理可以直接用 openid 而这里是作为通用第三方 OAuth 平台保存因此需要使用标准的 uid 字段)。

这里我们以新的平台 weapp2 为例:

const unionid = '';
const openid = '';
const authData = {
  uid: openid,
  session_key: ''
};
const platform = 'weapp2';
AV.User.loginWithAuthDataAndUnionId(authData, platform, unionid, {
  asMainAccount: true
}).then(console.log, console.error);
获取 unionid 后与现有用户关联

如果一个用户已经登录,现在通过某种方式获取到了其 unionid(一个常见的使用场景是用户完成了支付操作后在服务端通过 getPaidUnionId 得到了 unionid)希望与之关联,可以在小程序中使用 AV.User#associateWithWeappWithUnionId()

const user = AV.User.current(); // 获取当前登录用户
user.associateWithWeappWithUnionId(unionid, {
  asMainAccount: true
}).then(console.log, console.error);
启用其他登录方式

上述的登录 API 对接的是小程序的用户系统,所以使用这些 API 创建的用户无法直接在小程序之外的平台上登录。如果需要使用 LeanCloud 用户系统提供的其他登录方式,如用手机号验证码登录、邮箱密码登录等,在小程序登录后设置对应的用户属性即可:

// 小程序登录
AV.User.loginWithWeapp().then(user => {
  // 设置并保存手机号
  user.setMobilePhoneNumber('13000000000');
  return user.save();
}).then(user => {
  // 发送验证短信
  return AV.User.requestMobilePhoneVerify(user.getMobilePhoneNumber());
}).then({
  // 用户填写收到短信验证码后再调用 AV.User.verifyMobilePhone(code) 完成手机号的绑定
  // 成功后用户的 mobilePhoneVerified 字段会被置为 true
  // 此后用户便可以使用手机号加动态验证码登录了
}).catch(console.error);

验证手机号码功能要求在 控制台 > 存储 > 设置 > 用户账号 启用「用户注册时,向注册手机号码发送验证短信」。

绑定现有用户

如果你的应用已经在使用 LeanCloud 的用户系统,或者用户已经通过其他方式注册了你的应用(比如在 Web 端通过用户名密码注册),可以通过在小程序中调用 AV.User#associateWithWeapp() 来关联已有的账户:

// 首先,使用用户名与密码登录一个已经存在的用户
AV.User.logIn('username', 'password').then(user => {
  // 将当前的微信用户与当前登录用户关联
  return user.associateWithWeapp();
}).catch(console.error);

更多内容欢迎查看《在微信小程序与小游戏中使用 LeanCloud》