Skip to main content
Version: v3

TDS Authentication Guide

Starting from TapSDK 3.0, there will be a built-in account system for you to use in your game. You can generate user accounts (TDSUser) in your game with the results of TapTap OAuth. You can also link the authentication results of third-party platforms to this account. The Friends and Achievements services provided by the TapSDK also depend on this account system.

Initialization

See TapSDK Quickstart for how to initialize the SDK.

TDSUser and LCUser

TDSUser is inherited from the LCUser class. LCUser is the account system provided by LeanCloud, and TDSUser basically inherited all the interfaces provided by LCUser. TDSUser includes some minor adjustments we made on functionalities and interfaces to fulfill the needs of TDS, so we recommend that you implement the account system in your game with TDSUser.

TapTap Login

See Integrate TapTap Login for more details.

Guest Login

To create a guest account in the account system:

try{
// tdsUSer will hold a unique identifier of the user, if it exists
var tdsUser = await TDSUser.LoginAnonymously();
}catch(Exception e){
// Failed to log in
Debug.Log($"{e.code} : {e.message}");
}
info

The guest account ensures that the player will have access to the same account on different logins. However, if the player deletes the game and then reinstalls the game, it is not guaranteed that the player will still have access to the same account.

Current User

Once the user has logged in, the SDK will automatically save the session to the client so that the user won’t need to log in again the next time they open the client. The code below checks if there is a logged-in user:

TDSUser currentUser = await TDSUser.GetCurrent();
if (currentUser != null) {
// Go to homepage
} else {
// Show the sign-up or the log-in page
}

The session will remain valid until the user logs out:

await TDSUser.Logout();

// currentUser becomes null
TDSUser currentUser = await TDSUser.GetCurrent();

Setting the Current User

A session token will be returned to the client after a user is logged in. It will be cached by our SDK and will be used for authenticating requests made by the same TDSUser in the future. The session token will be included in the header of each HTTP request made by the client, which helps the cloud identify the TDSUser sending the request.

Below are the situations when you may need to log a user in with their session token:

  • A session token is already cached on the client which can be used to automatically log the user in (you can get the session token of the current user by accessing the sessionToken property; you can also get the sessionToken of any user on the server side with your Master Key (also called Server Secret)).
  • A WebView within the app needs to know the current user.
  • The user is logged in on the server side using your own authentication routines and the server is able to provide the session token to the client.

The code below logs a user in with a session token (the session token will be validated before proceeding):

await TDSUser.BecomeWithSessionToken("anmlwi96s381m6ca7o7266pzf");

For security reasons, please avoid passing URLs containing session tokens in non-private environments. This increases the risk of your session tokens being captured by attackers.

If Log out the user when password is updated is enabled on Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings, the session token of a user will be reset in the cloud after this user changes the password and the client needs to prompt the user to log in again. Otherwise, 403 (Forbidden) will be returned as an error.

The code below checks if a session token is valid:

TDSUser currentUser = await TDSUser.GetCurrent();
bool isAuthenticated = await currentUser.IsAuthenticated();
if (isAuthenticated) {
// session token is valid
} else {
// session token is invalid
}

Setting Other User Properties

The account system allows you to store nickname and avatar data associated with users. For example, you can store users’ nicknames by using nickname field.

var currentUser = await TDSUser.GetCurrent();  // Get the instance of the current user
currentUser["nickname"] = "Tarara";
await currentUser.Save();

The account system supports only two extra fields besides the built-in ones: nickname and avatar. Adding other new fields will cause an error.

The account system contains users’ authentication data as well as emails and phone numbers, so there will be strict permission settings imposed on it to prevent the leak of the data.

Besides the security concerns, having too much data in the account system can also lead to performance issues like the occurrence of slow queries.

Therefore, we restrict the use of fields. If you want to store other user information, we suggest that you create a dedicated class (like UserProfile) to store it.

tip

We recommend that you store users’ nicknames with the nickname field because TDS’s Friends module uses the data in this field when looking up friends with nicknames or generating invitation links.

If you log a user in with the result of TapTap OAuth, the SDK will automatically set the nickname of the user to be the username of their TapTap account.

Queries on Users

TDSUser is a subclass of LCObject. This means that you can create, read, update, and delete user objects in the same way as you do with LCObjects. See Data Storage Overview for more details.

For security reasons, the account system (the _User table) has its find permission disabled by default. Each user can only access their own data in the _User table and cannot access that of others. If you need to allow each user to view other users’ data, we recommend that you create a new table to store such data and enable the find permission of this table. You may also encapsulate queries on users within Cloud Engine and avoid opening up find permissions of _User tables.

See Security of User Objects for other restrictions applied to the _User table and Data Security for more information regarding class-level permission settings.

Associations

Associations involving TDSUsers work in the same way as that of LCObjects. The code below saves a new book for an author and retrieves all the books written by that author:

LCObject book = new LCObject("Book");
TDSUser author = await LCUser.GetCurrent();
book["title"] = "My Fifth Book";
book["author"] = author;
await book.Save();

LCQuery<LCObject> query = new LCQuery<LCObject>("Book");
query.WhereEqualTo("author", author);
// books is an array of Book objects by the same author
ReadOnlyCollection<LCObject> books = await query.Find();

Security of User Objects

The TDSUser class is secured by default. You are not able to invoke any save- or delete-related methods unless the TDSUser was obtained using an authenticated method like logging in. This ensures that each user can only update their own data.

The reason behind this is that most data stored in TDSUser can be very personal and sensitive, such as mobile phone numbers, social network account IDs, etc. Even the app’s owner should avoid tampering with these data for the sake of users’ privacy.

The code below illustrates this security policy:

try {
TDSUser tdsUser = await TDSUser.LoginWithTapTap();
// Attempt to change username
user["username"] = "Jerry";
// This will work since the user is authenticated
await user.Save();

// Get the user with a non-authenticated method
LCQuery<TDSUser> userQuery = TDSUser.GetQuery();
TDSUser unauthenticatedUser = await userQuery.Get(user.ObjectId);
unauthenticatedUser["username"] = "Toodle";

// This will cause an error since the user is unauthenticated
unauthenticatedUser.Save();
} catch (LCException e) {
print($"{e.code} : {e.message}");
}

The LCUser obtained from TDSUser.GetCurrent() will always be authenticated.

To check if a TDSUser is authenticated, you can invoke the isAuthenticated method. You do not need to check if TDSUser is authenticated if it is obtained via an authenticated method.

Security of Other Objects

For each given object, you can specify which users are allowed to read it and which are allowed to modify it. To support this type of security, each object has an access control list implemented by an ACL object. More details can be found in ACL Guide.

Third-Party Sign-on

We have already introduced how to implement quick log-in with TapTap.

Besides TapTap, you can also use other services (like Apple, WeChat, and QQ) to implement your account system. You can also associate existing accounts with these services so that the users can quickly log in with their existing accounts on these services.

Technically, we have implemented our interfaces in an open-ended manner. You can specify your own platform identifiers and authorization data, which means that our account system supports whatever third-party services you wish to connect to. For example, once you get the authorization data from Facebook, you can use TDSUser.loginWithAuthData to log the user in (you may set the platform name to be facebook).

The code below shows how you can log a user in with WeChat:

Dictionary<string, object> thirdPartyData = new Dictionary<string, object> {
// Optional
{ "openid", "OPENID" },
{ "access_token", "ACCESS_TOKEN" },
{ "expires_in", 7200 },
{ "refresh_token", "REFRESH_TOKEN" },
{ "scope", "SCOPE" }
};
TDSUser currentUser = await TDSUser.LoginWithAuthData(thirdPartyData, "weixin");

loginWithAuthData requires two arguments to locate a unique account:

  • The name of the third-party platform, which is weixin in the example above. You can decide this name on your own.
  • The authorization data from the third-party platform, which is the thirdPartyData in the example above (depending on the platform, it usually includes uid, access_token, and expires_in).

The cloud will then verifies that the provided authData is valid and checks if a user is already associated with it. If so, it returns the status code 200 OK along with the details (including a sessionToken of the user). If the authData is not linked to any accounts, you will instead receive the status code 201 Created, indicating that a new user has been created. The body of the response contains objectId, createdAt, sessionToken, and an automatically-generated unique username. For example:

{
"username": "k9mjnl7zq9mjbc7expspsxlls",
"objectId": "5b029266fb4ffe005d6c7c2e",
"createdAt": "2018-05-21T09:33:26.406Z",
"updatedAt": "2018-05-21T09:33:26.575Z",
"sessionToken": "…",
// authData won't be returned in most cases; see explanations below
"authData": {
"weixin": {
"openid": "OPENID",
"access_token": "ACCESS_TOKEN",
"expires_in": 7200,
"refresh_token": "REFRESH_TOKEN",
"scope": "SCOPE"
}
}
// …
}

Now we will see a new record showing up in the _User table that has an authData field. Within this field is the authorization data from the third-party platform. For security reasons, the authData field won’t be returned to the client unless the current user owns it.

You will need to implement the authentication process involving the third-party platform yourself (usually with OAuth 1.0 or 2.0) to obtain the authentication data, which will be used to log a user in.

Sign in with Apple

If you plan to implement Sign in with Apple, the cloud can help you verify identityTokens and obtain access_tokens from Apple. Below is the structure of authData for Sign in with Apple:

{
"lc_apple": {
"uid": "The User Identifier obtained from Apple",
"identity_token": "The identityToken obtained from Apple",
"code": "The Authorization Code obtained from Apple"
}
}

Each authData has the following fields:

  • lc_apple: The cloud will run the logic related to identity_token and code only when the platform name is lc_apple.
  • uid: Required. The cloud tells if the user exists with uid.
  • identity_token: Optional. The cloud will automatically validate identity_token if this field exists. Please make sure you have provided relevant information on Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings > Third-party accounts.
  • code: Optional. The cloud will automatically obtain access_token and refresh_token from Apple if this field exists. Please make sure you have provided relevant information on Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings > Third-party accounts.

Getting Client ID

Client ID is used to verify identity_token and to obtain access_token. It is the identifier of an Apple app (AppID or serviceID). For native apps, it is the Bundle Identifier in Xcode, which looks like com.mytest.app. See Apple’s docs for more details.

Getting Private Key and Private Key ID

Private Key is used to obtain access_token. You can go to Apple Developer, select “Keys” from “Certificates, Identifiers & Profiles”, add a Private Key for Sign in with Apple, and then download the .p8 file. You will also obtain the Private Key ID from the page you download the key. See Apple’s docs for more details.

The last step is to fill in the Key ID on the Developer Center and upload the downloaded Private Key. You can only upload Private Keys, but cannot view or download them.

Getting Team ID

Team ID is used to obtain access_token. You can view your team’s Team ID by going to Apple Developer and looking at the top-right corner or the Membership page. Make sure to select the team matching the selected Bundle ID.

Logging in to Cloud Services With Sign in with Apple

After you have filled in all the information on the Developer Center, you can log a user in with the following code:

Dictionary<string, object> appleAuthData = new Dictionary<string, object> {
// Required
{ "uid", "USER IDENTIFIER" },

// Optional
{ "identity_token", "IDENTITY TOKEN" },
{ "code", "AUTHORIZATION CODE" }
};
TDSUser currentUser = await TDSUser.LoginWithAuthData(appleAuthData, "lc_apple");

Storing Authentication Data

The authData of each user is a JSON object with platform names as keys and authentication data as values.

A user associated with a WeChat account will have the following object as its authData:

{
"weixin": {
"openid": "…",
"access_token": "…",
"expires_in": 7200,
"refresh_token": "…",
"scope": "…"
}
}

A user associated with a Weibo account will have the following object as its authData:

{
"weibo": {
"refresh_token": "2.0xxx",
"uid": "271XFEFEW273",
"expires_in": 115057,
"access_token": "2.00xxx"
}
}

A user can be associated with multiple third-party platforms. If a user is associated with both WeChat and Weibo, their authData may look like this:

{
"weixin": {
"openid": "…",
"access_token": "…",
"expires_in": 7200,
"refresh_token": "…",
"scope": "…"
},
"weibo": {
"refresh_token": "2.0xxx",
"uid": "271XFEFEW273",
"expires_in": 115057,
"access_token": "2.00xxx"
}
}

It’s important to understand the data structure of authData. When a user logs in with the following authentication data:

"platform": {
"openid": "OPENID",
"access_token": "ACCESS_TOKEN",
"expires_in": 7200,
"refresh_token": "REFRESH_TOKEN",
"scope": "SCOPE"
}

The cloud will first look at the account system to see if there is an account that has its authData.platform.openid to be the OPENID. If there is, return the existing account. If not, create a new account and write the authentication data into the authData field of this new account, and then return the new account’s data as the result.

The cloud will automatically create a unique index for the authData.<PLATFORM>.<uid> of each user, which prevents the formation of duplicate data. For some of the platforms specially supported by us, <uid> refers to the openid field. For others (the other platforms specially supported by us, and those not specially supported by us), it refers to the uid field.

Automatically Validating Third-Party Authorization Data

The cloud can automatically validate access tokens for certain platforms, which prevents counterfeit account data from entering your app’s account system. If the validation fails, the cloud will return the invalid authData error, and the association will not be created. For those services that are not recognized by the cloud, you need to validate the access tokens yourself. You can validate access tokens when a user signs up or logs in by using LeanEngine’s beforeSave hook and beforeUpdate hook.

To enable the feature, please configure the platforms’ App IDs and Secret Keys on Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings.

To disable the feature, please uncheck Validate access tokens when logging in with third-party accounts on Developer Center > Your game > Game Services > Cloud Services > TDS Authentication > Settings.

The reason for configuring the platforms is that when a TDSUser is created, the cloud will use the relevant data to validate the thirdPartyData to ensure that the TDSUser matches a real user, which ensures the security of your app.

Linking Third-Party Accounts

If a user is already logged in, you can link third-party accounts to this user. For example, if a user first logs in as a guest and then links their TapTap or other third-party accounts, the user will be able to access the same account when they log in with the same TapTap or third-party accounts in the future.

After a user links their third-party account, the account information will be added to the authData field of the corresponding TDSUser.

The following code links a WeChat account to a user:

await currentUser.AssociateAuthData(weixinData, "weixin");

The code above omitted the authorization data of the platform. See Third-Party Sign-on for more details.

Unlinking

Similarly, a third-party account can be unlinked.

For example, the code below unlinks a user’s WeChat account:

TDSUser currentUser = await TDSUser.GetCurrent();
await currentUser.DisassociateWithAuthData("weixin");
扩展:接入 UnionID 体系,打通不同子产品的账号系统

随着第三方平台的账户体系变得日渐复杂,它们的第三方鉴权信息出现了一些较大的变化。下面我们以最典型的微信开放平台为例来进行说明。

当一个用户在移动应用内登录微信账号时,会被分配一个 OpenID;在微信小程序内登录账号时,又会被分配另一个不同的 OpenID。这样的架构会导致的问题是,使用同一个微信号的用户,也无法在微信开放平台下的移动应用和小程序之间互通。

微信官方为了解决这个问题,引入了 UnionID 的体系,以下为其官方说明:

通过获取用户基本信息接口,开发者可通过 OpenID 来获取用户基本信息,而如果开发者拥有多个公众号,可使用以下办法通过 UnionID 机制来在多公众号之间进行用户帐号互通。只要是同一个微信开放平台帐号下的公众号,用户的 UnionID 是唯一的。换句话说,同一用户,对同一个微信开放平台帐号下的不同应用,UnionID 是相同的。

其他平台,如 QQ 和微博,与微信的设计也基本一致。

云服务支持 UnionID 体系。你只需要给 loginWithauthDataassociateWithauthData 接口传入更多的第三方鉴权信息,即可完成新 UnionID 体系的集成。新增加的第三方鉴权登录选项包括:

  • unionId,指第三方平台返回的 UnionId 字符串。
  • unionId platform,指 unionId 对应的 platform 字符串,由应用层自己指定,后面会详述。
  • asMainAccount,指示是否把当前平台的鉴权信息作为主账号来使用。如果作为主账号,那么就由当前用户唯一占有该 unionId,以后其他平台使用同样的 unionId 登录的话,会绑定到当前的用户记录上来;否则,当前应用的鉴权信息会被绑定到其他账号上去。

下面让我们通过一个例子来说明如何使用这些参数完成 UnionID 登录。

假设云服务在微信开放平台上有两个应用,一个是「云服务通讯」,一个是「云服务技术支持」,这两个应用在接入第三方鉴权的时候,分别使用了 wxleanofficewxleansupport 作为 platform 来进行登录。现在我们开启 UnionID 的用户体系,希望同一个微信用户在这两个应用中都能对应到同一个账户系统(_User 表中的同一条记录),同时我们决定将 wxleanoffice 平台作为主账号平台。

假设对于用户 A,微信给 ta 为云服务分配的 UnionId 为 unionid4a,而对两个应用的授权信息分别为:

"wxleanoffice": {
"access_token": "officetoken",
"openid": "officeopenid",
"expires_in": 1384686496
},
"wxleansupport": {
"openid": "supportopenid",
"access_token": "supporttoken",
"expires_in": 1384686496
}

现在,用户 A 在「云服务通讯」中通过微信登录,其调用请求为:

Dictionary<string, object> thirdPartyData = new Dictionary<string, object> {
// 必须
{ "uid", "officeopenid" },
{ "access_token", "officetoken" },
{ "expires_in", 1384686496 },
{ "unionId", "unionid4a" }, // 新增属性

// 可选
{ "refresh_token", "..." },
{ "scope", "SCOPE" }
};
LCUserAuthDataLoginOption option = new LCUserAuthDataLoginOption();
option.AsMainAccount = true;
option.UnionIdPlatform = "weixin";
TDSUser currentUser = await TDSUser.LoginWithAuthDataAndUnionId(
thirdPartyData, "wxleanoffice", "unionid4a",
option: option);

注意代码中将微信传回来的 openid 属性改为了 uid,这是因为云端要求对于自定义的 platform,只能使用 uid 这样的属性名,才能保证自动建立 authData.<PLATFORM>.uid 的唯一索引,具体可以参考数据存储 REST API 使用详解的《连接用户账户和第三方平台》一节。

如果用户 A 是第一次在「云服务通讯」中通过微信登录,那么 _User 表中会增加一个新用户(假设其 objectIdThisIsUserA),其 authData 的结果如下:

{
"wxleanoffice": {
"platform": "weixin",
"uid": "officeopenid",
"expires_in": 1384686496,
"main_account": true,
"access_token": "officetoken",
"unionid": "unionid4a"
},

// 新增键值对
"_weixin_unionid": {
"uid": "unionid4a"
}
}

可以看到,与之前的第三方登录 API 相比,这里由于登录时指定了 asMainAccounttrue,所以 authData 的第一级子目录中增加了 _weixin_unionid 的键值对,这里的 weixin 就是我们指定的 unionIdPlatform 的值。_weixin_unionid 这个增加的键值对非常重要,以后我们判断是否存在同样 UnionID 的账户就是依靠它来查找的,而是否增加这个键值对,则是由登录时指定的 asMainAccount 的值决定的:

  • asMainAccounttrue 时,云端会在 authData 下面增加名为 _{unionIdPlatform}_unionid 的键值对,当前账号就会作为这一个 UnionID 对应的主账号被唯一确定。
  • asMainAccountfalse 时,云端不会在 authData 下面增加名为 _{unionIdPlatform}_unionid 的键值对,此时如果通过提供的 UnionID 可以找到主账号,则会将当前的鉴权信息合并进主账号的 authData 属性里,同时返回主账号对应的 _User 表记录;如果通过提供的 UnionID 找不到主账号,则会根据平台的 openid 去查找账户,找到匹配的账户就返回匹配的,找不到就新建一个账户,此时的处理逻辑与不使用 UnionID 时的逻辑完全一致。

接下来,用户 A 继续在「云服务技术支持」中进行微信登录,其登录逻辑为:

Dictionary<string, object> thirdPartyData = new Dictionary<string, object> {
// 必须
{ "uid", "supportopenid" },
{ "access_token", "supporttoken" },
{ "expires_in", 1384686496 },
{ "unionId", "unionid4a" },

// 可选
{ "refresh_token", "..." },
{ "scope", "SCOPE" }
};
LCUserAuthDataLoginOption option = new LCUserAuthDataLoginOption();
option.AsMainAccount = false;
option.UnionIdPlatform = "weixin"; // 这里指定 unionIdPlatform,使用「weixin」来指代微信平台。
TDSUser currentUser = await TDSUser.LoginWithAuthDataAndUnionId(
thirdPartyData, "wxleansupport", "unionid4a",
option: option);

与「云服务通讯」中的登录过程相比,在「云服务技术支持」这个应用中,我们在登录时只是将 asMainAccount 设为了 false。 这时我们看到,本次登录得到的还是 objectIdThisIsUserA_User 表记录(同一个账户),同时该账户的 authData 属性中发生了变化,多了 wxleansupport 的数据,如下:

{
"wxleanoffice": {
"platform": "weixin",
"uid": "officeopenid",
"expires_in": 1384686496,
"main_account": true,
"access_token": "officetoken",
"unionid": "unionid4a"
},
"_weixin_unionid": {
"uid": "unionid4a"
},
"wxleansupport": {
"platform": "weixin",
"uid": "supportopenid",
"expires_in": 1384686496,
"main_account": false,
"access_token": "supporttoken",
"unionid": "unionid4a"
}
}

在新的登录方式中,当一个用户以「平台名为 wxleanoffice、uid 为 officeopenid、UnionID 为 unionid4a」的第三方鉴权信息登录得到新的 TDSUser 后,接下来这个用户以「平台名为 wxleansupport、uid 为 supportopenid、UnionID 为 unionid4a」的第三方鉴权信息登录时,云端判定是同样的 UnionID,就直接把来自 wxleansupport 的新用户数据加入到已有账户的 authData 里了,不会再创建新的账户。

这样一来,云端通过识别平台性的用户唯一标识 UnionID,让来自同一个 UnionID 体系内的应用程序、小程序等不同平台的用户都绑定到了一个 TDSUser 上,实现互通。

为 UnionID 建立索引

云端会为 UnionID 自动建立索引,不过因为自动创建基于请求的抽样统计,可能会滞后。 因此,我们推荐自行创建相关索引,特别是用户量(_User 表记录数)很大的应用,更需要预先创建索引,否则用户使用 UnionID 账号登录时可能超时失败。 以上面的微信 UnionID 为例,建议在控制台预先创建下列唯一索引(允许缺失值):

  • authData.wxleanoffice.uid
  • authData.wxleansupport.uid
  • authData._weixin_unionid.uid

该如何指定 unionIdPlatform

从上面的例子可以看出,使用 UnionID 登录的时候,需要指定 unionIdPlatform 的主要目的,就是为了便于查找已经存在的唯一主账号。云端会在主账号对应账户的 authData 属性中增加一个 _{unionIdPlatform}_unionid 键值对来标记唯一性,终端用户在其他应用中登录的时候,云端会根据参数中提供的 uniondId + unionIdPlatform 的组合,在 _User 表中进行查找,这样来确定唯一的既存主账号。

本来 unionIdPlatform 的取值,应该是开发者可以自行决定的,但是 JavaScript SDK 基于易用性的目的,在 loginWithAuthDataAndUnionId 之外,还额外提供了两个接口:

  • AV.User.loginWithQQAppWithUnionId,这里默认使用 qq 作为 unionIdPlatform
  • AV.User.loginWithWeappWithUnionId,这里默认使用 weixin 作为 unionIdPlatform

从我们的统计来看,这两个接口已经被很多开发者接受,在大量线上产品中产生着实际数据。所以为了避免数据在不同平台(例如 Android 和 iOS 应用)间发生冲突,建议大家统一按照 JavaScript SDK 的默认值来设置 unionIdPlatform,即:

  • 微信平台的多个应用,统一使用 weixin 作为 unionIdPlatform
  • QQ 平台的多个应用,统一使用 qq 作为 unionIdPlatform
  • 微博平台的多个应用,统一使用 weibo 作为 unionIdPlatform
  • 除此之外的其他平台,开发者可以自行定义 unionIdPlatform 的名字,只要自己保证多个应用间统一即可。

主副应用不同登录顺序出现的不同结果

上面的流程是用户先登录了「云服务通讯」这个主应用,然后再登录「云服务技术支持」这个副应用,所以账号都被通过 UnionID 有效关联起来了。可能有人会想到另外一个问题,如果用户 B 先登录副应用,后登录主应用,这时候会发生什么事情呢?

用户 B 首先登录副应用的时候,提供了「平台名为 wxleansupport、uid 为 supportopenid、UnionID 为 unionid4a」的第三方鉴权信息,并且指定「UnionIDPlatform 为 weixinasMainAccountfalse」(与上面的调用完全一致),此时云端由于找不到存在的 UnionID,会新建一个 TDSUser 对象,该账户 authData 结果为:

{
"wxleansupport": {
"platform": "weixin",
"uid": "supportopenid",
"expires_in": 1384686496,
"main_account": false,
"access_token": "supporttoken",
"unionid": "unionid4a"
}
}

用户 B 接着又使用了主应用,ta 再次通过微信登录,此时以「平台名为 wxleanoffice、uid 为 officeopenid、UnionID 为 unionid4a」的第三方鉴权信息,以及「UnionIDPlatform 为 weixinasMainAccounttrue」的参数进行登录,此时云端由于找不到存在的 UnionID,会再次新建一个 TDSUser 对象,该账户 authData 结果为:

{
"wxleanoffice": {
"platform": "weixin",
"uid": "officeopenid",
"expires_in": 1384686496,
"main_account": true,
"access_token": "officetoken",
"unionid": "unionid4a"
},
"_weixin_unionid": {
"uid": "unionid4a"
}
}

还有更复杂的情况。如果某公司的产品之前就接入了微信登录,产生了很多存量用户,并且分散在不同的子产品中,这时候怎么办?我们接下来专门讨论此时的解决方案。

存量账户如何通过 UnionID 实现关联

还是以我们的两个子产品「云服务通讯」(后续以「产品 1」指代)和「云服务技术支持为例」(后续以「产品 2」指代)为例,在接入 UnionID 之前,我们就接入了之前版本的微信平台登录,这时候账户系统内可能存在多种账户:

  • 只使用产品 1 的微信用户 A
  • 只使用产品 2 的微信用户 B
  • 同时使用两个产品的微信用户 C

此时的存量账户表如下所示:

objectId微信用户authData.{platform}authData._{platform}_unionid
1UserAopenid1(对应产品 1)N/A
2UserBopenid2(对应产品 2)N/A
3UserCopenid3(对应产品 1)N/A
4UserCopenid4(对应产品 2)N/A

现在我们对两个子产品进行升级,接入 UnionID 体系。这时因为已经有同一个微信用户在不同子产品中创建了不同的账户(例如 objectId 为 3 和 4 的账户),我们需要确定以哪个平台的账号为主。比如决定使用「云服务通讯」上生成的账号为主账号,则在该应用程序更新版本时,使用 asMainAccount=true 参数。这个应用带着 UnionID 登录匹配或创建的账号将作为主账号,之后所有这个 UnionID 的登录都会匹配到这个账号。请注意这时 _User 表里会剩下一些用户数据,也就是没有被选为主账号的、其他平台的同一个用户的旧账号数据(例如 objectId 为 2 和 4 的账户)。这部分数据会继续服务于已经发布的但仍然使用 OpenID 登录的旧版应用。

接下来我们看一下,如果以产品 1 的账户作为「主账户」,按照前述的方式同时提供 openid/unionid 完成登录,则最后达到的结果是:

  1. 使用老版本的用户,不管在哪个产品里面,都可以和往常一样通过 openid 登录到正确的账户;
  2. 使用产品 1 的新版本的老用户,通过 openid/unionid 组合,还是绑定到原来的账户。例如 UserC 在产品 1 中通过 openid3/unionId3 还是会绑定到 objectId=3 的账户(会增加 uniondId 记录);而 UserC 在产品 2 的新版本中,通过 openid4/unionId3 的组合则会绑定到 objectId=3 的账户,而不再是原来的 objectId=4 的账户。
  3. 使用产品 1 的新版本的新用户,通过 openid/unionid 组合,会创建新的账户;之后该用户再使用产品 2 的新版本,也会绑定到刚才创建的新账户上。

以上面的三个用户为例,他们分别升级到两个产品的最新版,且最终都会启用两个产品,则账户表的最终结果如下:

objectId微信用户authData.{platform}authData._{platform}_unionid
1UserAopenid1(对应产品 1)/openid6(对应产品 2)unionId_user_A
2UserBopenid2(对应产品 2)N/A
3UserCopenid3(对应产品 1)/openid4(对应产品 2)unionId_user_C
4UserCopenid4(对应产品 2)N/A
5UserBopenid5(对应产品 1)/openid2(对应产品 2)unionId_user_B

之后有新的用户 D,分别在两个产品的新版本中登录,则账户表中会增加一条新的 objectId=6 的记录,结果如下:

objectId微信用户authData.{platform}authData._{platform}_unionid
1UserAopenid1(对应产品 1)/openid6(对应产品 2)unionId_user_A
2UserBopenid2(对应产品 2)N/A
3UserCopenid3(对应产品 1)/openid4(对应产品 2)unionId_user_C
4UserCopenid4(对应产品 2)N/A
5UserBopenid5(对应产品 1)/openid2(对应产品 2)unionId_user_B
6UserDopenid7(对应产品 1)/openid8(对应产品 2)unionId_user_D

如果之后我们增加了新的子产品 3,这些用户在子产品 3 中也进行微信登录的话,那么四个用户还是会继续绑定到 objectId 为 1/3/5/6 的主账户。此时账户表的结果会变为:

objectId微信用户authData.{platform}authData._{platform}_unionid
1UserAopenid1(对应产品 1)/openid6(对应产品 2)/openid9(对应产品 3)unionId_user_A
2UserBopenid2(对应产品 2)N/A
3UserCopenid3(对应产品 1)/openid4(对应产品 2)/openid10(对应产品 3)unionId_user_C
4UserCopenid4(对应产品 2)N/A
5UserBopenid5(对应产品 1)/openid2(对应产品 2)/openid11(对应产品 3)unionId_user_B
6UserDopenid7(对应产品 1)/openid8(对应产品 2)/openid12(对应产品 3)unionId_user_D