内建账户指南
用户系统几乎是每款应用都要加入的功能,我们为此专门提供了一个 LCUser
类来方便应用使用各项用户管理的功能。
LCUser
是 LCObject
的子类,这意味着任何 LCObject
提供的方法也适用于 LCUser
,唯一的区别就是 LCUser
提供一些额外的用户管理相关的功能。
用户的属性
LCUser
相比一个普通的 LCObject
多出了以下属性:
username
:用户的用户名。password
:用户的密码。email
:用户的电子邮箱。emailVerified
:用户的电子邮箱是否已验证。mobilePhoneNumber
:用户的手机号。mobilePhoneVerified
:用户的手机号是否已验证。
在接下来对用户功能的介绍中我们会逐一了解到这些属性。
注册
用户第一次打开应用的时候,可以让用户注册一个账户。下面的代码展示了一个典型的使用用户名和密码注册的流程:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
// 创建实例
LCUser user = new LCUser();
// 等同于 user["username"] = "Tom";
user.Username = "Tom";
user.Password = "cat!@#123";
// 可选
user.Email = "tom@xd.com";
user.Mobile = "+8619201680101";
// 设置其他属性的方法跟 LCObject 一样
user["gender"] = "secret";
await user.SignUp();
新建 LCUser
的操作应使用 SignUp
而不是 Save
,但以后的更新操作就可以用 Save
了。
// 创建实例
LCUser user = new LCUser();
// 等同于 user.put("username", "Tom")
user.setUsername("Tom");
user.setPassword("cat!@#123");
// 可选
user.setEmail("tom@xd.com");
user.setMobilePhoneNumber("+8619201680101");
// 设置其他属性的方法跟 LCObject 一样
user.put("gender", "secret");
user.signUpInBackground().subscribe(new Observer<LCUser>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCUser user) {
// 注册成功
System.out.println("注册成功。objectId:" + user.getObjectId());
}
public void onError(Throwable throwable) {
// 注册失败(通常是因为用户名已被使用)
}
public void onComplete() {}
});
新建 LCUser
的操作应使用 signUpInBackground
而不是 saveInBackground
,但以后的更新操作就可以用 saveInBackground
了。
// 创建实例
LCUser *user = [LCUser user];
// 等同于 [user setObject:@"Tom" forKey:@"username"]
user.username = @"Tom";
user.password = @"cat!@#123";
// 可选
user.email = @"tom@xd.com";
user.mobilePhoneNumber = @"+8619201680101";
// 设置其他属性的方法跟 LCObject 一样
[user setObject:@"secret" forKey:@"gender"];
[user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// 注册成功
NSLog(@"注册成功。objectId:%@", user.objectId);
} else {
// 注册失败(通常是因为用户名已被使用)
}
}];
新建 LCUser
的操作应使用 signUpInBackground
而不是 saveInBackground
,但以后的更新操作就可以用 saveInBackground
了。
do {
// 创建实例
let user = LCUser()
// 等同于 user.set("username", value: "Tom")
user.username = LCString("Tom")
user.password = LCString("cat!@#123")
// 可选
user.email = LCString("tom@xd.com")
user.mobilePhoneNumber = LCString("+8619201680101")
// 设置其他属性的方法跟 LCObject 一样
try user.set("gender", value: "secret")
_ = user.signUp { (result) in
switch result {
case .success:
break
case .failure(error: let error):
print(error)
}
}
} catch {
print(error)
}
新建 LCUser
的操作应使用 signUp
而不是 save
,但以后的更新操作就可以用 save
了。
// 创建实例
LCUser user = LCUser();
// 等同于 user['username'] = 'Tom';
user.username = 'Tom';
user.password = 'cat!@#123';
// 可选
user.email = 'tom@xd.com';
user.mobile = '+8619201680101';
// 设置其他属性的方法跟 LCObject 一样
user['gender'] = 'secret';
await user.signUp();
新建 LCUser
的操作应使用 signUp
而不是 save
,但以后的更新操作就可以用 save
了。
// 创建实例
const user = new AV.User();
// 等同于 user.set('username', 'Tom')
user.setUsername("Tom");
user.setPassword("cat!@#123");
// 可选
user.setEmail("tom@xd.com");
user.setMobilePhoneNumber("+8619201680101");
// 设置其他属性的方法跟 AV.Object 一样
user.set("gender", "secret");
user.signUp().then(
(user) => {
// 注册成功
console.log(`注册成功。objectId:${user.id}`);
},
(error) => {
// 注册失败(通常是因为用户名已被使用)
}
);
新建 AV.User
的操作应使用 signUp
而不是 save
,但以后的更新操作就可以用 save
了。
# 创建实例
user = leancloud.User()
# 等同于 user.set('username', 'Tom')
user.set_username('Tom')
user.set_password('cat!@#123')
# 可选
user.set_email('tom@xd.com')
user.set_mobile_phone_number('+8619201680101')
# 设置其他属性的方法跟 leancloud.Object 一样
user.set('gender', 'secret')
user.sign_up()
新建 leancloud.User
的操作应使用 sign_up
而不是 save
,但以后的更新操作就可以用 save
了。
// 创建实例
$user = new User();
// 等同于 $user->set("username", "Tom")
$user->setUsername("Tom");
$user->setPassword("cat!@#123");
// 可选
$user->setEmail("tom@xd.com");
$user->setMobilePhoneNumber("+8619201680101");
// 设置其他属性的方法跟 LeanObject 一样
$user->set("gender", "secret");
$user->signUp();
新建 User
的操作应使用 signUp
而不是 save
,但以后的更新操作就可以用 save
了。
// 注册用户
user, err := client.Users.SignUp("Tom", "cat!@#123")
if err != nil {
panic(err)
}
// 设置其他属性
if err := client.Users.ID(user.ID).Set("email", "tom@xd.com", leancloud.UseUser(user)); err != nil {
panic(err)
}
如果收到 202
错误码,意味着已经存在使用同一 username
的账号,此时应提示用户换一个用户名。除此之外,每个用户的 email
和 mobilePhoneNumber
也需要保持唯一性,否则会收到 203
或 214
错误。可以考虑在注册时把用户的 username
设为与 email
相同,这样用户可以直接 用邮箱重置密码。
采用「用户名 + 密码」注册时需要注意:密码是以明文方式通过 HTTPS 加密传输给云端,云端会以密文存储密码(云端对密码的长度、复杂度不作限制),并且我们的加密算法是无法通过所谓「彩虹表撞库」获取的,这一点请开发者放心。换言之,用户的密码只可能用户本人知道,开发者不论是通过控制台还是 API 都是无法获取。另外我们需要强调 在客户端,应用切勿再次对密码加密,这会导致 重置密码 等功能失效。
手机号注册
对于移动应用来说,允许用户以手机号注册是个很常见的需求。实现该功能大致分两步,第一步是让用户提供手机号,点击「获取验证码」按钮后,该号码会收到一个六位数的验证码:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await LCSMSClient.RequestSMSCode("+8619201680101");
LCSMSOption option = new LCSMSOption();
option.setSignatureName("sign_name"); // 设置短信签名名称
LCSMS.requestSMSCodeInBackground("+8619201680101", option).subscribe(new Observer<LCNull>() {
@Override
public void onSubscribe(Disposable disposable) {
}
@Override
public void onNext(LCNull avNull) {
Log.d("TAG","Result: succeed to request SMSCode.");
}
@Override
public void onError(Throwable throwable) {
Log.d("TAG","Result: failed to request SMSCode. cause:" + throwable.getMessage());
}
@Override
public void onComplete() {
}
});
LCShortMessageRequestOptions *options = [[LCShortMessageRequestOptions alloc] init];
options.templateName = @"template_name"; // 控制台配置好的模板名称
options.signatureName = @"sign_name"; // 控制台配置好的短信签名名称
[LCSMS requestShortMessageForPhoneNumber:@"+8619201680101" options:options callback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
/* 请求成功 */
} else {
/* 请求失败 */
}
}];
// templateName 是短信模版名称,signatureName 是短信签名名称。可以在控制台 > 短信 > 设置中查看。
_ = LCSMSClient.requestShortMessage(mobilePhoneNumber: "+8619201680101", templateName: "template_name", signatureName: "sign_name") { (result) in
switch result {
case .success:
break
case .failure(error: let error):
print(error)
}
}
await LCSMSClient.requestSMSCode('+8619201680101');
AV.Cloud.requestSmsCode("+8619201680101");
leancloud.cloud.request_sms_code('+8619201680101')
SMS::requestSmsCode("+8619201680101");
// 暂不支持
用户填入验证码后,用下面的方法完成注册:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await LCUser.SignUpOrLoginByMobilePhone("+8619201680101", "123456");
LCUser.signUpOrLoginByMobilePhoneInBackground("+8619201680101", "123456").subscribe(new Observer<LCUser>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCUser user) {
// 注册成功
System.out.println("注册成功。objectId:" + user.getObjectId());
}
public void onError(Throwable throwable) {
// 验证码不正确
}
public void onComplete() {}
});
[LCUser signUpOrLoginWithMobilePhoneNumberInBackground:@"+8619201680101" smsCode:@"123456" block:^(LCUser *user, NSError *error) {
if (user != nil) {
// 注册成功
NSLog(@"注册成功。objectId:%@", user.objectId);
} else {
// 验证码不正确
}
}];
_ = LCUser.signUpOrLogIn(mobilePhoneNumber: "+8619201680101", verificationCode: "123456", completion: { (result) in
switch result {
case .success(object: let user):
print(user)
case .failure(error: let error):
print(error)
}
})
await LCUser.signUpOrLoginByMobilePhone('+8619201680101', '123456');
AV.User.signUpOrlogInWithMobilePhone("+8619201680101", "123456").then(
(user) => {
// 注册成功
console.log(`注册成功。objectId:${user.id}`);
},
(error) => {
// 验证码不正确
}
);
user = leancloud.User.signup_or_login_with_mobile_phone('+8619201680101', '123456')
User::signUpOrLoginByMobilePhone("+8619201680101", "123456");
user, err := client.Users.SignUpByMobilePhone("+8619201680101", "123456")
if err != nil {
panic(err)
}
username
将与 mobilePhoneNumber
相同,password
会由云端随机生成。如果希望让用户指定密码,可以在客户端让用户填写手机号和密码,然后按照上一小节使用用户名和密码注册的流程,将用户填写的手机号作为 username
和 mobilePhoneNumber
的值同时提交。同时根据业务需求,在云服务控制台 > 内建账户 > 设置勾选未验证手机号码的用户,禁止登录、已验证手机号码的用户,允许以短信验证码登录。
手机号格式
云端接受的手机号以 +
和国家代码开头,后面紧跟着剩余的部分。手机号中不应含有任何划线、空格等非数字字符。例如,+15559463664
是一个合法的美国或加拿大手机号(1
是国家代码),+8619201680101
是一个合法的中国手机号(86
是国家代码)。
请参阅官网的价格页面以了解支持的国家和地区。
登录
下面的代码用用户名和密码登录一个账户:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
try {
// 登录成功
LCUser user = await LCUser.Login("Tom", "cat!@#123");
} catch (LCException e) {
// 登录失败(可能是密码错误)
print($"{e.code} : {e.message}");
}
LCUser.logIn("Tom", "cat!@#123").subscribe(new Observer<LCUser>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCUser user) {
// 登录成功
}
public void onError(Throwable throwable) {
// 登录失败(可能是密码错误)
}
public void onComplete() {}
});
[LCUser logInWithUsernameInBackground:@"Tom" password:@"cat!@#123" block:^(LCUser *user, NSError *error) {
if (user != nil) {
// 登录成功
} else {
// 登录失败(可能是密码错误)
}
}];
_ = LCUser.logIn(username: "Tom", password: "cat!@#123") { result in
switch result {
case .success(object: let user):
print(user)
case .failure(error: let error):
print(error)
}
}
try {
// 登录成功
LCUser user = await LCUser.login('Tom', 'cat!@#123');
} on LCException catch (e) {
// 登录失败(可能是密码错误)
print('${e.code} : ${e.message}');
}
AV.User.logIn("Tom", "cat!@#123").then(
(user) => {
// 登录成功
},
(error) => {
// 登录失败(可能是密码错误)
}
);
user = leancloud.User()
user.login(username='Tom', password='cat!@#123')
User::logIn("Tom", "cat!@#123");
user, err := client.Users.LogIn("Tom", "cat!@#123")
if err != nil {
panic(err)
}
邮箱登录
下面的代码用邮箱和密码登录一个账户:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
try {
// 登录成功
LCUser user = await LCUser.LoginByEmail("tom@xd.com", "cat!@#123");
} catch (LCException e) {
// 登录失败(可能是密码错误)
print($"{e.code} : {e.message}");
}
LCUser.loginByEmail("tom@xd.com", "cat!@#123").subscribe(new Observer<LCUser>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCUser user) {
// 登录成功
}
public void onError(Throwable throwable) {
// 登录失败(可能是密码错误)
}
public void onComplete() {}
});
[LCUser loginWithEmail:@"tom@xd.com" password:@"cat!@#123" block:^(LCUser *user, NSError *error) {
if (user != nil) {
// 登录成功
} else {
// 登录失败(可能是密码错误)
}
}];
_ = LCUser.logIn(email: "tom@xd.com", password: "cat!@#123") { result in
switch result {
case .success(object: let user):
print(user)
case .failure(error: let error):
print(error)
}
}
try {
// 登录成功
LCUser user = await LCUser.loginByEmail('tom@xd.com', 'cat!@#123');
} on LCException catch (e) {
// 登录失败(可能是密码错误)
print('${e.code} : ${e.message}');
}
AV.User.loginWithEmail("tom@xd.com", "cat!@#123").then(
(user) => {
// 登录成功
},
(error) => {
// 登录失败(可能是密码错误)
}
);
user = leancloud.User()
user.login(email='tom@xd.com', password='cat!@#123')
User::logInWithEmail("tom@xd.com", "cat!@#123");
user, err := client.LoginByEmail("tom@xd.com", "cat!@#123")
if err != nil {
panic(err)
}
fmt.Println(user)
手机号登录
如果应用允许用户以手机号注册,那么也可以让用户以手机号配合密码或短信验证码登录。下面的代码用手机号和密码登录一个账户:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
try {
// 登录成功
LCUser user = await LCUser.LoginByMobilePhoneNumber("+8619201680101", "cat!@#123");
} catch (LCException e) {
// 登录失败(可能是密码错误)
print($"{e.code} : {e.message}");
}
LCUser.loginByMobilePhoneNumber("+8619201680101", "cat!@#123").subscribe(new Observer<LCUser>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCUser user) {
// 登录成功
}
public void onError(Throwable throwable) {
// 登录失败(可能是密码错误)
}
public void onComplete() {}
});
[LCUser logInWithMobilePhoneNumberInBackground:@"+8619201680101" password:@"cat!@#123" block:^(LCUser *user, NSError *error) {
if (user != nil) {
// 登录成功
} else {
// 登录失败(可能是密码错误)
}
}];
_ = LCUser.logIn(mobilePhoneNumber: "+8619201680101", password: "cat!@#123") { result in
switch result {
case .success(object: let user):
print(user)
case .failure(error: let error):
print(error)
}
}
try {
// 登录成功
LCUser user = await LCUser.loginByMobilePhoneNumber('+8619201680101', 'cat!@#123');
} on LCException catch (e) {
// 登录失败(可能是密码错误)
print('${e.code} : ${e.message}');
}
AV.User.logInWithMobilePhone("+8619201680101", "cat!@#123").then(
(user) => {
// 登录成功
},
(error) => {
// 登录失败(可能是密码错误)
}
);
user = leancloud.User.login_with_mobile_phone('+8619201680101', 'cat!@#123')
User::logInWithMobilePhoneNumber("+8619201680101", "cat!@#123");
user, err := client.LogInByMobilePhoneNumber("+8619201680101", "cat!@#123")
if err != nil {
panic(err)
}
fmt.Println(user)
默认情况下,云服务允许所有关联了手机号的用户直接以手机号登录,无论手机号是否 通过验证。为了让应用更加安全,你可以选择只允许验证过手机号的用户通过手机号登录。可以在 控制台 > 内建账户 > 设置 里面开启该功能。
除此之外,还可以让用户通过短信验证码登录,适用于用户忘记密码且不愿重置密码的情况。和 通过手机号注册 的步骤类似,首先让用户填写与账户关联的手机号码,然后在用户点击「获取验证码」后调用下面的方法:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await LCUser.RequestLoginSMSCode("+8619201680101");
LCUser.requestLoginSmsCodeInBackground("+8619201680101").subscribe(new Observer<LCNull>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCNull null) {
// 成功调用
}
public void onError(Throwable throwable) {
// 调用出错
}
public void onComplete() {}
});
[LCUser requestLoginSmsCode:@"+8619201680101"];
_ = LCUser.requestLoginVerificationCode(mobilePhoneNumber: "+8619201680101") { result in
switch result {
case .success:
break
case .failure(error: let error):
print(error)
}
}
await LCUser.requestLoginSMSCode('+8619201680101');
AV.User.requestLoginSmsCode("+8619201680101");
leancloud.User.request_login_sms_code('+8619201680101')
SMS::requestSmsCode("+8619201680101");
if err := client.Users.RequestLoginSMSCode("+8619201680101"); err != nil {
panic(err)
}
用户填写收到的验证码后,用下面的方法完成登录:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
try {
// 登录成功
await LCUser.SignUpOrLoginByMobilePhone("+8619201680101", "123456");
} catch (LCException e) {
// 验证码不正确
print($"{e.code} : {e.message}");
}
LCUser.signUpOrLoginByMobilePhoneInBackground("+8619201680101", "123456").subscribe(new Observer<LCUser>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCUser user) {
// 登录成功
}
public void onError(Throwable throwable) {
// 验证码不正确
}
public void onComplete() {}
});
[LCUser logInWithMobilePhoneNumberInBackground:@"+8619201680101" smsCode:@"123456" block:^(LCUser *user, NSError *error) {
if (user != nil) {
// 登录成功
} else {
// 验证码不正确
}
}];
_ = LCUser.logIn(mobilePhoneNumber: "+8619201680101", verificationCode: "123456") { result in
switch result {
case .success(object: let user):
print(user)
case .failure(error: let error):
print(error)
}
}
try {
// 登录成功
await LCUser.signUpOrLoginByMobilePhone('+8619201680101', '123456');
} on LCException catch (e) {
// 验证码不正确
print('${e.code} : ${e.message}');
}
AV.User.logInWithMobilePhoneSmsCode("+8619201680101", "123456").then(
(user) => {
// 登录成功
},
(error) => {
// 验证码不正确
}
);
user = leancloud.User.signup_or_login_with_mobile_phone('+8619201680101', '123456')
User::signUpOrLoginByMobilePhone("+8619201680101", "123456");
user, err := client.Users.LogInByMobilePhoneNumber("+8619201680101", "123456")
if err != nil {
panic(err)
}
测试手机号和固定验证码
在开发过程中,可能会因测试目的而需要频繁地用手机号注册登录,然而运营商的发送频率限制往往会导致测试过程耗费较多的时间。
为了解决这个问题,可以在 控制台 > 短信 > 设置 里面设置一个测试手机号,而云端会为该号码生成一个固定验证码。以后进行登录操作时,只要使用的是这个号码,云端就会直接放行,无需经过运营商网络。
测试手机号还可用于将 iOS 应用提交到 App Store 进行审核的场景,因为审核人员可能因没有有效的手机号码而无法登录应用来进行评估审核。如果不提供一个测试手机号,应用有可能被拒绝。
可参阅 短信 SMS 服务使用指南 来了解更多有关短信发送和接收的限制。
单设备登录
某些场景下需要确保用户的账户在同一时间只在一台设备上登录,也就是说当用户在一台设备上登录后,其他设备上的会话全部失效。可以按照以下方案来实现:
- 新建一个专门用于记录用户登录信息和当前设备信息的 class。
- 每当用户在新设备上登录时,将该 class 中该用户对应的设备更新为该设备。
- 在另一台设备上打开客户端时,检查该设备是否与云端保存的一致。若不一致,则将用户 登出。
账户锁定
输入错误的密码或验证码会导致用户登录失败。如果在 15 分钟内,同一个用户登录失败的次数大于 6 次,该用户账户即被云端暂时锁定,此时云端会返回错误码 { "code": 1, "error": "You have exceeded the maximum number of login attempts, please try again later, or consider resetting your password." }
,开发者可在客户端进行必要提示。
锁定将在最后一次错误登录的 15 分钟之后由云端自动解除,开发者无法通过 SDK 或 REST API 进行干预。在锁定期间,即使用户输入了正确的验证信息也不允许登录。这个限制在 SDK 和云引擎中都有效。
验证邮箱
可以通过要求用户在登录或使用特定功能之前验证邮箱的方式防止恶意注册。默认情况下,当用户注册或变更邮箱后,emailVerified
会被设为 false
。在应用的 云服务控制台 > 内建账户 > 设置 中,可以开启 启用邮箱验证功能 选项,这样当用户注册或变更邮箱时,会收到一封含有验证链接的邮件。在同一设置页面还可找到阻止未验证邮箱的用户登录的选项。
如果用户忘记点击链接并且在未来某一时刻需要进行验证,可以用下面的代码发送一封新的邮件:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await LCUser.RequestEmailVerify("tom@xd.com");
LCUser.requestEmailVerifyInBackground("tom@xd.com").subscribe(new Observer<LCNull>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCNull null) {
// 成功调用
}
public void onError(Throwable throwable) {
// 调用出错
}
public void onComplete() {}
});
[LCUser requestEmailVerify:@"tom@xd.com"];
_ = LCUser.requestVerificationMail(email: "tom@xd.com") { result in
switch result {
case .success:
break
case .failure(error: let error):
print(error)
}
}
await LCUser.requestEmailVerify('tom@xd.com');
AV.User.requestEmailVerify("tom@xd.com");
leancloud.User.request_email_verify('tom@xd.com')
User::requestEmailVerify("tom@xd.com");
if err := client.Users.RequestEmailVerify("tom@xd.com"); err != nil {
panic(err)
}
用户点击邮件内的链接后,emailVerified
会变为 true
。如果用户的 email
属性为空,则该属性永远不会为 true
。
验证手机号
和 验证邮箱 类似,应用还可以要求用户在登录或使用特定功能之前验证手机号。默认情况下,当用户注册或变更手机号后,mobilePhoneVerified
会被设为 false
。在应用的 控制台 > 内建账户 > 设置 中,可以开启阻止未验证手机号的用户登录的选项。
可以用下面的代码发送一条新的验证码(如果相应用户的 mobilePhoneVerified
已经为 true
,那么验证短信不会发送):
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await LCUser.RequestMobilePhoneVerify("+8619201680101");
LCUser.requestMobilePhoneVerifyInBackground("+8619201680101").subscribe(new Observer<LCNull>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCNull null) {
// 成功调用
}
public void onError(Throwable throwable) {
// 调用出错
}
public void onComplete() {}
});
[LCUser requestMobilePhoneVerify:@"+8619201680101" withBlock:^(BOOL succeeded, NSError * _Nullable error) {
if(succeeded){
// 请求成功
}else{
// 请求失败
}
}];
_ = LCUser.requestVerificationCode(mobilePhoneNumber: "+8619201680101") { result in
switch result {
case .success:
break
case .failure(error: let error):
print(error)
}
}
await LCUser.requestMobilePhoneVerify('+8619201680101');
AV.User.requestMobilePhoneVerify("+8619201680101");
leancloud.User.request_mobile_phone_verify('+8619201680101')
User::requestMobilePhoneVerify("+8619201680101");
if err := client.Users.RequestMobilePhoneVerify("+8619201680101"); err != nil {
panic(err)
}
用户填写验证码后,调用下面的方法来完成验证。mobilePhoneVerified
将变为 true
:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await LCUser.VerifyMobilePhone("+8619201680101", "123456");
LCUser.verifyMobilePhoneInBackground("123456").subscribe(new Observer<LCNull>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCNull null) {
// mobilePhoneVerified 将变为 true
}
public void onError(Throwable throwable) {
// 验证码不正确
}
public void onComplete() {}
});
[LCUser verifyMobilePhone:@"123456" withBlock:^(BOOL succeeded, NSError * _Nullable error) {
if(succeeded){
// mobilePhoneVerified 将变为 true
}else{
// 验证码不正确
}
}];
_ = LCUser.verifyMobilePhoneNumber(mobilePhoneNumber: "+8619201680101", verificationCode: "123456") { result in
switch result {
case .success:
// mobilePhoneVerified 将变为 true
break
case .failure(error: let error):
// 验证码不正确
print(error)
}
}
await LCUser.verifyMobilePhone('+8619201680101','123456');
AV.User.verifyMobilePhone("123456").then(
() => {
// mobilePhoneVerified 将变为 true
},
(error) => {
// 验证码不正确
}
);
leancloud.User.verify_mobile_phone_number('123456')
User::verifyMobilePhone("123456");
// 暂不支持
绑定、修改手机号之前先验证
除了在用户绑定、修改手机号之后进行验证,云服务也支持在用户绑定或修改手机号之前先通过短信验证。也就是说,绑定手机号或修改手机号时先请求发送验证码(用户需处于登录状态),再凭短信验证码完成绑定或修改操作。
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await LCUser.RequestSMSCodeForUpdatingPhoneNumber("+8619201680101");
await LCUser.VerifyCodeForUpdatingPhoneNumber("+8619201680101", "123456");
// 更新本地数据
LCUser currentUser = await LCUser.GetCurrent();
user.Mobile = "+8619201680101";
LCUser.requestSMSCodeForUpdatingPhoneNumberInBackground("+8619201680101",null).subscribe(new Observer<LCNull>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull LCNull lcNull) {
// 成功调用
}
@Override
public void onError(@NonNull Throwable e) {
// 调用出错
}
@Override
public void onComplete() {
}
});
LCUser.verifySMSCodeForUpdatingPhoneNumberInBackground("123456", "+8619201680101").subscribe(new Observer<LCNull>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull LCNull lcNull) {
// 更新本地数据
LCUser currentUser = LCUser.getCurrentUser();
currentUser.setMobilePhoneNumber("+8619201680101");
}
@Override
public void onError(@NonNull Throwable e) {
// 验证码不正确
}
@Override
public void onComplete() {
}
});
[LCUser requestVerificationCodeForUpdatingPhoneNumber:@"+8619201680101" withBlock:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
// 请求成功
} else {
// 请求失败
}
}];
[LCUser verifyCodeToUpdatePhoneNumber:@"+8619201680101" code:@"123456" withBlock:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
// mobilePhoneNumber 变为 +8619201680101
// mobilePhoneVerified 变为 true
} else {
// 验证码不正确
}
}];
_ = LCUser.requestVerificationCode(forUpdatingMobilePhoneNumber: "+8619201680101") { result in
switch result {
case .success:
break
case .failure(error: let error):
print(error)
}
}
_ = LCUser.verifyVerificationCode("123456", toUpdateMobilePhoneNumber:"+8619201680101") { result in
switch result {
case .success:
// mobilePhoneNumber 变为 +8619201680101
// mobilePhoneVerified 变为 true
break
case .failure(error: let error):
// 验证码不正确
print(error)
}
}
await LCUser.requestSMSCodeForUpdatingPhoneNumber('+8619201680101');
await LCUser.verifyCodeForUpdatingPhoneNumber('+8619201680101', '123456');
// 更新本地数据
LCUser currentUser = await LCUser.getCurrent();
user.mobile = '+8619201680101';
AV.User.requestChangePhoneNumber("+8619201680101");
AV.User.changePhoneNumber("+8619201680101", "123456").then(
() => {
// 更新本地数据
const currentUser = AV.User.current();
currentUser.setMobilePhoneNumber("+8619201680101");
},
(error) => {
// 验证码不正确
}
);
User.request_change_phone_number("+8619201680101")
User.change_phone_number("123456", "+8619201680101")
# 更新本地数据
current_user = leancloud.User.get_current()
current_user.set_mobile_phone_number("+8619201680101")
User::requestChangePhoneNumber("+8619201680101");
User::changePhoneNumber("123456", "+8619201680101");
// 更新本地数据
$currentUser = User::getCurrentUser();
$user->setMobilePhoneNumber("+8619201680101");
if err := client.Users.requestChangePhoneNumber("+8619201680101"); err != nil {
panic(err)
}
if err := client.Users.ChangePhoneNumber("123456", "+8619201680101"); err != nil {
panic(err)
}
当前用户
用户登录后,SDK 会自动将会话信息存储到客户端,这样用户在下次打开客户端时无需再次登录。下面的代码检查是否有已经登录的用户:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
LCUser currentUser = await LCUser.GetCurrent();
if (currentUser != null) {
// 跳到首页
} else {
// 显示注册或登录页面
}
LCUser currentUser = LCUser.getCurrentUser();
if (currentUser != null) {
// 跳到首页
} else {
// 显示注册或登录页面
}
LCUser *currentUser = [LCUser currentUser];
if (currentUser != nil) {
// 跳到首页
} else {
// 显示注册或登录页面
}
if let user = LCApplication.default.currentUser {
// 跳到首页
} else {
// 显示注册或登录页面
}
LCUser currentUser = await LCUser.getCurrent();
if (currentUser != null) {
// 跳到首页
} else {
// 显示注册或登录页面
}
const currentUser = AV.User.current();
if (currentUser) {
// 跳到首页
} else {
// 显示注册或登录页面
}
current_user = leancloud.User.get_current()
if current_user is not None:
# 跳到首页
pass
else:
# 显示注册或登录页面
pass
$currentUser = User::getCurrentUser();
if ($currentUser != null) {
// 跳到首页
} else {
// 显示注册或登录页面
}
// 暂不支持
会话信息会长期有效,直到用户主动登出:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await LCUser.Logout();
// currentUser 变为 null
LCUser currentUser = await LCUser.GetCurrent();
LCUser.logOut();
// currentUser 变为 null
LCUser currentUser = LCUser.getCurrentUser();
[LCUser logOut];
// currentUser 变为 nil
LCUser *currentUser = [LCUser currentUser];
LCUser.logOut()
// currentUser 变为 nil
let currentUser = LCApplication.default.currentUser
await LCUser.logout();
// currentUser 变为 null
LCUser currentUser = await LCUser.getCurrent();
AV.User.logOut();
// currentUser 变为 null
const currentUser = AV.User.current();
user.logout()
current_user = leancloud.User.get_current() # None
User::logOut();
// currentUser 变为 null
$currentUser = User::getCurrentUser();
// 暂不支持
设置当前用户
用户登录后,云端会返回一个 session token 给客户端,它会由 SDK 缓存起来并用于日后同一用户的鉴权请求。session token 会被包含在每个客户端发起的 HTTP 请求的 header 里面,这样云端就知道是哪个用户发起的请求了。
以下是一些应用可能需要用到 session token 的场景:
- 应用根据以前缓存的 session token 登录。
- 应用内的某个 WebView 需要知道当前登录的用户。
- 在服务端登录后,返回 session token 给客户端,客户端根据返回的 session token 登录。
下面的代码使用 session token 登录一个用户(云端会验证 session token 是否有效):
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await LCUser.BecomeWithSessionToken("anmlwi96s381m6ca7o7266pzf");
LCUser.becomeWithSessionTokenInBackground("anmlwi96s381m6ca7o7266pzf").subscribe(new Observer<LCUser>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCUser user) {
// 修改 currentUser
LCUser.changeCurrentUser(user, true);
}
public void onError(Throwable throwable) {
// session token 无效
}
public void onComplete() {}
});
[LCUser becomeWithSessionTokenInBackground:@"anmlwi96s381m6ca7o7266pzf" block:^(LCUser * _Nullable user, NSError * _Nullable error) {
if (user != nil) {
// 登录成功
} else {
// session token 无效
}
}];
_ = LCUser.logIn(sessionToken: "anmlwi96s381m6ca7o7266pzf") { (result) in
switch result {
case .success(object: let user):
// 登录成功
print(user)
case .failure(error: let error):
// session token 无效
print(error)
}
}
await LCUser.becomeWithSessionToken('anmlwi96s381m6ca7o7266pzf');
AV.User.become("anmlwi96s381m6ca7o7266pzf").then(
(user) => {
// 登录成功
},
(error) => {
// session token 无效
}
);
user = leancloud.User.become('anmlwi96s381m6ca7o7266pzf')
User::become("anmlwi96s381m6ca7o7266pzf");
user, err := client.Users.Become("anmlwi96s381m6ca7o7266pzf")
if err != nil {
panic(err)
}
请避免在外部浏览器使用 URL 来传递 session token,以防范信息泄露风险。
如果在 控制台 > 内建账户 > 设置 中勾选了 密码修改后,强制客户端重新登录,那么当一个用户修改密码后,该用户的 session token 会被重置。此时需要让用户重新登录,否则会遇到 403 (Forbidden)
错误。
下面的代码检查 session token 是否有效:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
LCUser currentUser = await LCUser.GetCurrent();
bool isAuthenticated = await currentUser.IsAuthenticated();
if (isAuthenticated) {
// session token 有效
} else {
// session token 无效
}
boolean authenticated = LCUser.getCurrentUser().isAuthenticated();
if (authenticated) {
// session token 有效
} else {
// session token 无效
}
LCUser *currentUser = [LCUser currentUser];
NSString *token = currentUser.sessionToken;
[currentUser isAuthenticatedWithSessionToken:token callback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
// session token 有效
} else {
// session token 无效
}
}];
if let sessionToken = LCApplication.default.currentUser?.sessionToken?.value {
_ = LCUser.logIn(sessionToken: sessionToken) { (result) in
if result.isSuccess {
// session token 有效
} else {
// session token 无效
}
}
}
LCUser currentUser = await LCUser.getCurrent();
bool isAuthenticated = await currentUser.isAuthenticated();
if (isAuthenticated) {
// session token 有效
} else {
// session token 无效
}
const currentUser = AV.User.current();
currentUser.isAuthenticated().then((authenticated) => {
if (authenticated) {
// session token 有效
} else {
// session token 无效
}
});
authenticated = leancloud.User.get_current().is_authenticated()
if authenticated:
# session token 有效
pass
else:
# session token 无效
pass
$authenticated = User::isAuthenticated();
if ($authenticated) {
// session token 有效
} else {
// session token 无效
}
// 暂不支持
重置密码
我们都知道,应用一旦加入账户密码系统,那么肯定会有用户忘记密码的情况发生。对于这种情况,我们为用户提供了多种重置密码的方法。
邮箱重置密码的流程如下:
- 用户输入注册的电子邮箱,请求重置密码;
- 云端向该邮箱发送一封包含重置密码的特殊链接的电子邮件;
- 用户点击重置密码链接后,一个特殊的页面会打开,让他们输入新密码;
- 用户的密码已被重置为新输入的密码。
首先让用户填写注册账户时使用的邮箱,然后调用下面的方法:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await LCUser.RequestPasswordReset("tom@xd.com");
LCUser.requestPasswordResetInBackground("tom@xd.com").subscribe(new Observer<LCNull>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCNull null) {
// 成功调用
}
public void onError(Throwable throwable) {
// 调用出错
}
public void onComplete() {}
});
[LCUser requestPasswordResetForEmailInBackground:@"tom@xd.com"];
_ = LCUser.requestPasswordReset(email: "tom@xd.com") { (result) in
switch result {
case .success:
break
case .failure(error: let error):
print(error)
}
}
await LCUser.requestPasswordReset('tom@xd.com');
AV.User.requestPasswordReset("tom@xd.com");
leancloud.User.request_password_reset('tom@xd.com')
User::requestPasswordReset("tom@xd.com");
if err := client.Users.RequestPasswordReset("tom@xd.com"); err != nil {
panic(err)
}
上面的代码会查询是否有用户的 email
属性与前面提供的邮箱匹配。如果有的话,则向该邮箱发送一封密码重置邮件。之前提到过,应用可以让 username
与 email
保持一致,也可以单独收集用户的邮箱并将其存为 email
。
密码重置邮件的内容可在应用的 云服务控制台 > 内建账户 > 邮件模版 中自定义。更多关于自定义邮件模板和验证链接的内容,请参考《自定义邮件验证和重设密码页面》。
除此之外,还可以用手机号重置密码:
- 用户输入注册的手机号,请求重置密码;
- 云端向该号码发送一条包含验证码的短信;
- 用户输入验证码和新密码。
下面的代码向用户发送含有验证码的短信:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await LCUser.RequestPasswordRestBySmsCode("+8619201680101");
LCUser.requestPasswordResetBySmsCodeInBackground("+8619201680101").subscribe(new Observer<LCNull>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCNull null) {
// 成功调用
}
public void onError(Throwable throwable) {
// 调用出错
}
public void onComplete() {}
});
[LCUser requestPasswordResetWithPhoneNumber:@"+8619201680101"];
_ = LCUser.requestPasswordReset(mobilePhoneNumber: "+8619201680101") { (result) in
switch result {
case .success:
break
case .failure(error: let error):
print(error)
}
}
await LCUser.requestPasswordRestBySmsCode('+8619201680101');
AV.User.requestPasswordResetBySmsCode("+8619201680101");
leancloud.User.request_password_reset_by_sms_code('+8619201680101')
User::requestPasswordResetBySmsCode("+8619201680101");
if err := client.Users.RequestPasswordResetBySmsCode("+8619201680101"); err != nil {
panic(err)
}
上面的代码会查询是否有用户的 mobilePhoneNumber
属性与前面提供的手机号匹配。如果有的话,则向该号码发送验证码短信。
可以在 云服务控制台 > 内建账户 > 设置 中设置只有在 mobilePhoneVerified
为 true
的情况下才能用手机号重置密码。
用户输入验证码和新密码后,用下面的代码完成密码重置:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await LCUser.ResetPasswordBySmsCode("+8619201680101", "123456", "cat!@#123");
LCUser.resetPasswordBySmsCodeInBackground("123456", "cat!@#123").subscribe(new Observer<LCNull>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCNull null) {
// 密码重置成功
}
public void onError(Throwable throwable) {
// 验证码不正确
}
public void onComplete() {}
});
[LCUser resetPasswordWithSmsCode:@"123456" newPassword:@"cat!@#123" block:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// 密码重置成功
} else {
// 验证码不正确
}
}];
_ = LCUser.resetPassword(mobilePhoneNumber: "+8619201680101", verificationCode: "123456", newPassword: "cat!@#123") { result in
switch result {
case .success:
// 密码重置成功
break
case .failure(error: let error):
// 验证码不正确
print(error)
}
}
await LCUser.resetPasswordBySmsCode('+8619201680101', '123456', 'cat!@#123');
AV.User.resetPasswordBySmsCode("123456", "cat!@#123").then(
() => {
// 密码重置成功
},
(error) => {
// 验证码不正确
}
);
leancloud.User.reset_password_by_sms_code('123456', 'cat!@#123')
User::resetPasswordBySmsCode("123456", "cat!@#123");
if err := client.Users.ResetPasswordBySmsCode("+8619201680101", "123456", "cat!@#123"); err != nil {
panic(err)
}
用户的查询
使用下面的代码来查询用户:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
LCQuery<LCUser> userQuery = LCUser.GetQuery();
LCQuery<LCUser> userQuery = LCUser.getQuery();
LCQuery *userQuery = [LCUser query];
let userQuery = LCQuery(className: "_User")
LCQuery<LCUser> userQuery = LCUser.getQuery();
const userQuery = new AV.Query("_User");
user_query = leancloud.Query('_leancloud.User')
$userQuery = new Query("_User");
userQuery := client.Users.NewUserQuery()
为了安全起见,新创建的应用的 _User
表默认关闭了 find
权限,这样每位用户登录后只能查询到自己在 _User
表中的数据,无法查询其他用户的数据。如果需要让其查询其他用户的数据,建议单独创建一张表来保存这类数据,并开放这张表的 find
查询权限。除此之外,还可以在 云引擎 里封装用户查询相关的方法。
可以参见 用户对象的安全 来了解 _User
表的一些限制,还可以阅读《数据和安全》来了解更多 class 级权限设置的方法。
关联用户对象
关联用户的方法和对象是一样的。下面的代码为一名作者保存了一本书,然后获取所有该作者写的书:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
LCObject book = new LCObject("Book");
LCUser author = await LCUser.GetCurrent();
book["title"] = "我的第五本书";
book["author"] = author;
await book.Save();
LCQuery<LCObject> query = new LCQuery<LCObject>("Book");
query.WhereEqualTo("author", author);
// books 是包含同一作者所有 Book 对象的数组
ReadOnlyCollection<LCObject> books = await query.Find();
LCObject book = new LCObject("Book");
LCUser author = LCUser.getCurrentUser();
book.put("title", "我的第五本书");
book.put("author", author);
book.saveInBackground().subscribe(new Observer<LCObject>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCObject book) {
// 获取所有该作者写的书
LCQuery<LCObject> query = new LCQuery<>("Book");
query.whereEqualTo("author", author);
query.findInBackground().subscribe(new Observer<List<LCObject>>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(List<LCObject> books) {
// books 是包含同一作者所有 Book 对象的数组
}
public void onError(Throwable throwable) {}
public void onComplete() {}
});
}
public void onError(Throwable throwable) {}
public void onComplete() {}
});
LCObject *book = [LCObject objectWithClassName:@"Book"];
LCUser *author = [LCUser currentUser];
[book setObject:@"我的第五本书" forKey:@"title"];
[book setObject:author forKey:@"author"];
[book saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
// 获取所有该作者写的书
LCQuery *query = [LCQuery queryWithClassName:@"Book"];
[query whereKey:@"author" equalTo:author];
[query findObjectsInBackgroundWithBlock:^(NSArray *books, NSError *error) {
// books 是包含同一作者所有 Book 对象的数组
}];
}];
do {
guard let author = LCApplication.default.currentUser else {
return
}
let book = LCObject(className: "Book")
try book.set("title", value: "我的第五本书")
try book.set("author", value: author)
_ = book.save { result in
switch result {
case .success:
// 获取所有该作者写的书
let query = LCQuery(className: "Book")
query.whereKey("author", .equalTo(author))
_ = query.find { result in
switch result {
case .success(objects: let books):
// books 是包含同一作者所有 Book 对象的数组
break
case .failure(error: let error):
print(error)
}
}
case .failure(error: let error):
print(error)
}
}
} catch {
print(error)
}
LCObject book = LCObject('Book');
LCUser author = await LCUser.getCurrent();
book['title'] = '我的第五本书';
book['author'] = author;
await book.save();
LCQuery<LCObject> query = LCQuery('Book');
query.whereEqualTo('author', author);
// books 是包含同一作者所有 Book 对象的数组
List<LCObject> books = await query.find();
const Book = AV.Object.extend("Book");
const book = new Book();
const author = AV.User.current();
book.set("title", "我的第五本书");
book.set("author", author);
book.save().then((book) => {
// 获取所有该作者写的书
const query = new AV.Query("Book");
query.equalTo("author", author);
query.find().then((books) => {
// books 是包含同一作者所有 Book 对象的数组
});
});
Book = leancloud.Object.extend('Book')
book = Book()
author = leancloud.User.get_current()
book.set('title', '我的第五本书')
book.set('author', author)
book.save()
# 获取所有该作者写的书
query = Book.query
query.equal_to('author', author)
book_list = query.find()
$book = new LeanObject("Book");
$author = User::getCurrentUser();
$book->set("title", "我的第五本书");
$book->set("author", $author);
$book->save();
// 获取所有该作者写的书
$query = new Query("Book");
$query->equalTo("author", $author);
$books = $query->find();
// 暂不支持
用户对象的安全
用户对象自带安全保障,只有通过经过鉴权的方法获取到的用户对象才能进行更新或删除操作,保证每个用户只能修改自己的数据。
这样设计是因为用户对象中存储的大多数数据都比较敏感,包括手机号、社交网络账号等等。为了用户的隐私安全,即使是应用的开发者也应避免直接接触这些数据。
下面的代码展现了这种安全措施:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
try {
LCUser user = await LCUser.Login("Tom", "cat!@#123");
// 试图修改用户名
user["username"] = "Jerry";
// 密码已被加密,这样做会获取到空字符串
string password = user["password"];
// 可以执行,因为用户已鉴权
await user.Save();
// 绕过鉴权直接获取用户
LCQuery<LCUser> userQuery = LCUser.GetQuery();
LCUser unauthenticatedUser = await userQuery.Get(user.ObjectId);
unauthenticatedUser["username"] = "Toodle";
// 会出错,因为用户未鉴权
unauthenticatedUser.Save();
} catch (LCException e) {
print($"{e.code} : {e.message}");
}
LCUser.logIn("Tom", "cat!@#123").subscribe(new Observer<LCUser>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCUser user) {
// 试图修改用户名
user.put("username", "Jerry");
// 密码已被加密,这样做会获取到空字符串
String password = user.getString("password");
// 可以执行,因为用户已鉴权
user.save();
// 绕过鉴权直接获取用户
LCQuery<LCUser> query = new LCQuery<>("_User");
query.getInBackground(user.getObjectId()).subscribe(new Observer<LCUser>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(LCUser unauthenticatedUser) {
unauthenticatedUser.put("username", "Toodle");
// 会出错,因为用户未鉴权
unauthenticatedUser.save();
}
public void onError(Throwable throwable) {}
public void onComplete() {}
});
}
public void onError(Throwable throwable) {}
public void onComplete() {}
});
[LCUser logInWithUsernameInBackground:@"Tom" password:@"cat!@#123" block:^(LCUser *user, NSError *error) {
if (user != nil) {
// 试图修改用户名
[user setObject:@"Jerry" forKey:@"username")];
// 密码已被加密,这样做会获取到空字符串
NSString *password = user[@"password"];
// 保存更改
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// 可以执行,因为用户已鉴权
// 绕过鉴权直接获取用户
LCQuery *query = [LCQuery queryWithClassName:@"_User"];
[query getObjectInBackgroundWithId:user.objectId block:^(LCObject *unauthenticatedUser, NSError *error) {
[unauthenticatedUser setObject:@"Toodle" forKey:@"username"];
[unauthenticatedUser saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// 无法执行,因为用户未鉴权
} else {
// 操作失败
}
}];
}];
} else {
// 错误处理
}
}];
} else {
// 错误处理
}
}];
_ = LCUser.logIn(username: "Tom", password: "cat!@#123") { result in
switch result {
case .success(object: let user):
// 试图修改用户名
try! user.set("username", "Jerry")
// 密码已被加密,这样做会获取到空字符串
let password = user.get("password")
// 可以执行,因为用户已鉴权
user.save()
// 绕过鉴权直接获取用户
let query = LCQuery(className: "_User")
_ = query.get(user.objectId) { result in
switch result {
case .success(object: let unauthenticatedUser):
try! unauthenticatedUser.set("username", "Toodle")
_ = unauthenticatedUser.save { result in
switch result {
.success:
// 无法执行,因为用户未鉴权
.failure:
// 操作失败
}
}
case .failure(error: let error):
print(error)
}
}
case .failure(error: let error):
print(error)
}
}
try {
LCUser user = await LCUser.login('Tom', 'cat!@#123');
// 试图修改用户名
user['username'] = 'Jerry';
// 密码已被加密,这样做会获取到空字符串
String password = user['password'];
// 可以执行,因为用户已鉴权
await user.save();
// 绕过鉴权直接获取用户
LCQuery<LCUser> userQuery = LCQuery('_User');
LCUser unauthenticatedUser = await userQuery.get(user.objectId);
unauthenticatedUser['username'] = 'Toodle';
// 会出错,因为用户未鉴权
unauthenticatedUser.save();
} on LCException catch (e) {
print('${e.code} : ${e.message}');
}
const user = AV.User.logIn("Tom", "cat!@#123").then((user) => {
// 试图修改用户名
user.set("username", "Jerry");
// 密码已被加密,这样做会获取到空字符串
const password = user.get("password");
// 保存更改
user.save().then((user) => {
// 可以执行,因为用户已鉴权
// 绕过鉴权直接获取用户
const query = new AV.Query("_User");
query.get(user.objectId).then((unauthenticatedUser) => {
unauthenticatedUser.set("username", "Toodle");
unauthenticatedUser.save().then(
(unauthenticatedUser) => {},
(error) => {
// 会出错,因为用户未鉴权
}
);
});
});
});
leancloud.User.login('Tom', 'cat!@#123')
current_user = leancloud.User.get_current()
# 试图修改用户名
current_user.set('username', 'Jerry')
# 密码已被加密,这样做会获取到空字符串
password = current_user.get('password')
# 可以执行,因为用户已鉴权
current_user.save()
# 绕过鉴权直接获取用户
query = leancloud.Query('_User')
unauthenticated_user = query.get(current_user.id)
unauthenticated_user.set('username', 'Toodle')
# 会出错,因为用户未鉴权
unauthenticated_user.save()
User::logIn("Tom", "cat!@#123");
$currentUser = User::getCurrentUser();
// 试图修改用户名
$currentUser->set("username", "Jerry");
// 密码已被加密,这样做会获取到空字符串
$password = $currentUser->get("password");
// 可以执行,因为用户已鉴权
$currentUser->save();
// 绕过鉴权直接获取用户
$query = new Query("_User");
$unauthenticatedUser = $query->get($currentUser->getObjectId())
$unauthenticatedUser->set("username", "Toodle");
// 会出错,因为用户未鉴权
$unauthenticatedUser->save()
user, err := client.Users.LogIn("Tom", "cat!@#123")
if err != nil {
panic(err)
}
// 试图修改用户名,未鉴权将失败
if err := client.User(user).Set("username", "Jerry"); err != nil {
panic(err)
}
// 密码已被加密,这样做会获取到空字符串
password := user.String("password")
// 可以执行,因为用户已鉴权
if err := client.User(user).Set("username", "Jerry", leancloud.UseUser(user)); err != nil {
panic(err)
}
// 绕过鉴权直接获取用户
unauthenticatedUser := User{}
if err := client.Users.NewUserQuery().EqualTo("objectId", user.ID).First(&unauthenticatedUser); err != nil {
panic(err)
}
// 会出错,因为用户未鉴权
if err := client.User(unauthenticatedUser).Set("username", "Toodle"); err != nil {
panic(err)
}
通过调用 当前用户 相关方法获取的用户总是经过鉴权的。
要查看一个用户对象是否经过鉴权,可以调用如下方法。通过经过鉴权的方法获取到的用户对象无需进行该检查。
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
IsAuthenticated
isAuthenticated
isAuthenticatedWithSessionToken
// 暂不支持
isAuthenticated
isAuthenticated;
is_authenticated
isAuthenticated
// 暂不支持
注意,用户的密码只能在注册的时候进行设置,日后如需修改,只能通过 重置密码 的方式进行。密码不会被缓存在本地。如果尝试直接获取已登录用户的密码,会得到 null
。
其他对象的安全
对于给定的一个对象,可以指定哪些用户有权限读取或修改它。为实现该功能,每个对象都有一个由 ACL
对象组成的访问控制表。请参阅ACL 权限管理开发指南。
第三方账户登录
云服务支持应用层直接使用第三方社交平台(例如微信、微博、QQ 等)的账户信息来创建自己的账户体系并完成登录,也允许将既有账户与第三方账户绑定起来,这样终端用户后续可以直接用第三方账户信息来便捷登录。
例如以下的代码展示了终端用户使用微信登录的处理流程:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
Dictionary<string, object> thirdPartyData = new Dictionary<string, object> {
// 必须
{ "openid", "OPENID" },
{ "access_token", "ACCESS_TOKEN" },
{ "expires_in", 7200 },
// 可选
{ "refresh_token", "REFRESH_TOKEN" },
{ "scope", "SCOPE" }
};
LCUser currentUser = await LCUser.LoginWithAuthData(thirdPartyData, "weixin");
Map<String, Object> thirdPartyData = new HashMap<String, Object>();
// 必须
thirdPartyData.put("expires_in", 7200);
thirdPartyData.put("openid", "OPENID");
thirdPartyData.put("access_token", "ACCESS_TOKEN");
// 可选
thirdPartyData.put("refresh_token", "REFRESH_TOKEN");
thirdPartyData.put("scope", "SCOPE");
LCUser.loginWithAuthData(thirdPartyData, "weixin").subscribe(new Observer<LCUser>() {
public void onSubscribe(Disposable disposable) {
}
public void onNext(LCUser user) {
System.out.println("成功登录");
}
public void onError(Throwable throwable) {
System.out.println("尝试使用第三方账号登录,发生错误。");
}
public void onComplete() {
}
});
NSDictionary *thirdPartyData = @{
// 必须
@"openid":@"OPENID",
@"access_token":@"ACCESS_TOKEN",
@"expires_in":@7200,
// 可选
@"refresh_token":@"REFRESH_TOKEN",
@"scope":@"SCOPE",
};
LCUser *user = [LCUser user];
LCUserAuthDataLoginOption *option = [LCUserAuthDataLoginOption new];
option.platform = LeanCloudSocialPlatformWeiXin;
[user loginWithAuthData:thirdPartyData platformId:LeanCloudSocialPlatformWeiXin options:option callback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
NSLog(@"登录成功");
}else{
NSLog(@"登录失败:%@",error.localizedFailureReason);
}
}];
let thirdPartyData: [String: Any] = [
// 必须
"openid": "OPENID",
"access_token": "ACCESS_TOKEN",
"expires_in": 7200,
// 可选
"refresh_token": "REFRESH_TOKEN",
"scope": "SCOPE"
]
let user = LCUser()
user.logIn(authData: thirdPartyData, platform: .weixin) { (result) in
switch result {
case .success:
assert(user.objectId != nil)
case .failure(error: let error):
print(error)
}
}
var thirdPartyData = {
// 必须
'openid': 'OPENID',
'access_token': 'ACCESS_TOKEN',
'expires_in': 7200,
// 可选
'refresh_token': 'REFRESH_TOKEN',
'scope': 'SCOPE'
};
LCUser currentUser = await LCUser.loginWithAuthData(thirdPartyData, 'weixin');
const thirdPartyData = {
// 必须
openid: "OPENID",
access_token: "ACCESS_TOKEN",
expires_in: 7200,
// 可选
refresh_token: "REFRESH_TOKEN",
scope: "SCOPE",
};
AV.User.loginWithAuthData(thirdPartyData, "weixin").then(
(user) => {
// 登录成功
},
(error) => {
// 登录失败
}
);
# 暂不支持
// 暂不支持
// 暂不支持
loginWithAuthData
系列方法需要两个参数来唯一确定一个账户:
- 第三方平台的名字,就是前例中的
weixin
,该名字由应用层自己决定。 - 第三方平台的授权信息,就是前例中的
thirdPartyData
(一般包括uid
、access_token
、expires_in
等信息,与具体的第三方平台有关)。
云端会使用第三方平台的鉴权信息来查询是否已经存在与之关联的账户。如果存在的话,则返回 200 OK
状态码,同时附上用户的信息(包括 sessionToken
)。如果第三方平台的信息没有和任何账户关联,客户端会收到 201 Created
状态码,意味着新账户被创建,同时附上用户的 objectId
、createdAt
、sessionToken
和一个自动生成的 username
,例如:
{
"username": "k9mjnl7zq9mjbc7expspsxlls",
"objectId": "5b029266fb4ffe005d6c7c2e",
"createdAt": "2018-05-21T09:33:26.406Z",
"updatedAt": "2018-05-21T09:33:26.575Z",
"sessionToken": "…",
// authData 通常不会返回,继续阅读以了解其中原因
"authData": {
"weixin": {
"openid": "OPENID",
"access_token": "ACCESS_TOKEN",
"expires_in": 7200,
"refresh_token": "REFRESH_TOKEN",
"scope": "SCOPE"
}
}
// …
}
这时候我们会看到 _User
表中出现了一条新的账户记录,账户中有一个名为 authData
的列,保存了第三方平台的授权信息。出于安全考虑,authData
不会被返回给客户端,除非它属于当前用户。
开发者需要自己完成第三方平台的鉴权流程(一般通过 OAuth 1.0 或 2.0),以获取鉴权信息,继而到云端来登录。
Sign in with Apple
如果你需要开发 Sign in with Apple,云服务可以帮你校验 identityToken
,并获取 Apple 的 access_token
。Apple Sign In 的 authData
结构如下:
{
"lc_apple": {
"uid": "从 Apple 获取到的 User Identifier",
"identity_token": "从 Apple 获取到的 identityToken",
"code": "从 Apple 获取到的 Authorization Code"
}
}
authData
中的 key 的作用:
lc_apple
:只有 platform 为lc_apple
时,云服务才会执行identity_token
和code
的逻辑。uid
:必填。云服务通过uid
判断是否存在用户。identity_token
:可选。authData
中有identity_token
时云端会自动校验identity_token
的有效性。开发者需要在 云服务控制台 > 内建账户 > 设置 > 第三方集成 中填写 Apple 的相关信息。code
:可选。authData
中有code
时云端会自动用该code
向 Apple 换取access_token
和refresh_token
。开发者需要在 云服务控制台 > 内建账户 > 设置 > 第三方集成 中填写 Apple 的相关信息。
获取 Client ID
Client ID 用于校验 identity_token
及获取 access_token
,指的是 Apple 应用的 identifier,也就是 AppID
或 serviceID
。对于原生应用来说,指的是 Xcode 中的 Bundle Identifier,例如 com.mytest.app
。详情请参考 Apple 的文档。
获取 Private Key 及 Private Key ID
Private Key 用于获取 access_token
。登录 Apple 开发者平台,在左侧的「Certificates, Identifiers & Profiles」中选择「Keys」,添加一个用于 Apple Sign In 的 Private Key,下载 .p8
文件,同时在下载 Key 的页面获得 Private Key ID。详情请参考 Apple 的文档。
将 Key ID 填写到控制台,将下载下来的 Private Key 文件上传到控制台。控制台只能上传 Private Key 文件,无法查看及下载其内容。
获取 Team ID
Team ID 用于获取 access_token
。登录 Apple 开发者平台,在右上角或 Membership 页面即可看到自己所属开发团队的 Team ID。注意选择 Bundle ID 对应的 Team。
使用 Apple Sign In 登录云服务
在控制台填写完成所有信息后,使用以下代码登录。
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
Dictionary<string, object> appleAuthData = new Dictionary<string, object> {
// 必须
{ "uid", "USER IDENTIFIER" },
// 可选
{ "identity_token", "IDENTITY TOKEN" },
{ "code", "AUTHORIZATION CODE" }
};
LCUser currentUser = await LCUser.LoginWithAuthData(appleAuthData, "lc_apple");
// 不支持
NSDictionary *appleAuthData = @{
// 必须
@"uid":@"USER IDENTIFIER",
// 可选
@"identity_token":@"IDENTITY TOKEN",
@"code":@"AUTHORIZATION CODE",
};
LCUser *user = [LCUser user];
[user loginWithAuthData:appleAuthData platformId:"lc_apple" options:nil callback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
NSLog(@"登录成功");
}else{
NSLog(@"登录失败:%@",error.localizedFailureReason);
}
}];
let appleData: [String: Any] = [
// 必须
"uid": "USER IDENTIFIER",
// 可选
"identity_token": "IDENTITY TOKEN",
"code": "AUTHORIZATION CODE"
]
let user = LCUser()
user.logIn(authData: appleData, platform: .apple) { (result) in
switch result {
case .success:
assert(user.objectId != nil)
case .failure(error: let error):
print(error)
}
}
var appleData = {
// 必须
"uid": "USER IDENTIFIER",
// 可选
"identity_token": "IDENTITY TOKEN",
"code": "AUTHORIZATION CODE"
};
LCUser currentUser = await LCUser.loginWithAuthData(appleData, 'lc_apple');
// 不支持
# 不支持
// 不支持
// 不支持
鉴权数据的保存
每个用户的 authData
是一个以平台名为键名,鉴权信息为键值的 JSON 对象。
一个关联了微信账户的用户应该会有下列对象作为 authData
:
{
"weixin": {
"openid": "…",
"access_token": "…",
"expires_in": 7200,
"refresh_token": "…",
"scope": "…"
}
}
而一个关联了微博账户的用户,则会有如下的 authData
:
{
"weibo": {
"refresh_token": "2.0xxx",
"uid": "271XFEFEW273",
"expires_in": 115057,
"access_token": "2.00xxx"
}
}
我们允许一个账户绑定多个第三方平台的鉴权数据,这样如果某个用户同时关联了微信和微博账户,则其 authData
可能会是这样的:
{
"weixin": {
"openid": "…",
"access_token": "…",
"expires_in": 7200,
"refresh_token": "…",
"scope": "…"
},
"weibo": {
"refresh_token": "2.0xxx",
"uid": "271XFEFEW273",
"expires_in": 115057,
"access_token": "2.00xxx"
}
}
理解 authData
的数据结构至关重要。一个终端用户通过如下的鉴权信息来登录的时候,
"platform": {
"openid": "OPENID",
"access_token": "ACCESS_TOKEN",
"expires_in": 7200,
"refresh_token": "REFRESH_TOKEN",
"scope": "SCOPE"
}
云端首先会查找账户系统,看看是否存在 authData.platform.openid
等于 OPENID
的账户,如果存在,则返回现有账户,如果不存在那么就创建一个新账户,同时将上面的鉴权信息写入新账户的 authData
属性中,并将新账户的数据当成结果返回。
云端会自动为每个用户的 authData.<PLATFORM>.<uid>
创建唯一索引,从而避免重复数据。
<uid>
在微信等部分云服务内建支持的第三方平台上为 openid
字段,在其他第三方平台(包括部分云服务专门支持的第三方平台和所有云服务没有专门支持的第三方平台)上为 uid
字段。
自动验证第三方平台授权信息
为了确保账户数据的有效性,云端还支持对部分平台的 Access Token 的有效性进行自动验证,以防止伪造账户数据。如果有效性验证不通过,云端会返回 invalid authData
错误,关联不会被建立。对于云端无法识别的服务,开发者需要自己去验证 Access Token 的有效性。
比如,注册、登录时分别通过云引擎的 beforeSave
hook、beforeUpdate
hook 来验证 Access Token 有效性。
如果希望使用这一功能,则在开始使用前,需要在 云服务控制台 > 内建账户 > 设置 配置相应平台的 应用 ID 和 应用 Secret Key。
如果不希望云端自动验证 Access Token,可以在 云服务控制台 > 内建账户 > 设置 里面取消勾选 第三方登录时,验证用户 AccessToken 合法性。
配置平台账号的目的在于创建用户对象时,云端会使用相关信息去校验请求参数 thirdPartyData
的合法性,确保用户对象实际对应着一个合法真实的用户,确保平台安全性。
绑定第三方账户
如果用户已经登录,也可以在当前账户上绑定或解绑更多第三方平台信息。
绑定成功后,新的第三方账户信息会被添加到用户对象的 authData
字段里。
例如,下面的代码可以关联微信账户:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
await currentUser.AssociateAuthData(weixinData, "weixin");
user.associateWithAuthData(weixinData, "weixin").subscribe(new Observer<LCUser>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(LCUser user) {
System.out.println("绑定成功");
}
@Override
public void onError(Throwable e) {
System.out.println("绑定失败:" + e.getMessage());
}
@Override
public void onComplete() {
}
});
[user associateWithAuthData:weixinData platformId:LeanCloudSocialPlatformWeiXin options:nil callback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
NSLog(@"成功");
} else{
NSLog(@"失败:%@",error.localizedFailureReason);
}
}];
currentUser.associate(authData: weixinData, platform: .weixin) { (result) in
switch result {
case .success:
// 关联成功
case .failure(error: let error):
// 关联失败
}
}
await currentUser.associateAuthData(weixinData, 'weixin');
user
.associateWithAuthData(weixinData, "weixin")
.then(function (user) {
// 成功绑定
})
.catch(function (error) {
console.error("error: ", error);
});
user.link_with("weixin", weixin_data)
$user->linkWith("weixin", $weixinData);
// 暂不支持
为节省篇幅,上面的代码示例中没有给出具体的平台授权信息,相关内容请参考上面的「第三方账户登录」一节。
解除与第三方账户的关联
类似地,可以解绑第三方账户。
例如,下面的代码可以解除用户和微信账户的关联:
- .NET
- Java
- Objective-C
- Swift
- Flutter
- JavaScript
- Python
- PHP
- Go
LCUser currentUser = await LCUser.GetCurrent();
await currentUser.DisassociateWithAuthData("weixin");
LCUser user = LCUser.currentUser();
user.dissociateWithAuthData("weixin").subscribe(new Observer<LCUser>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(LCUser user) {
System.out.println("解绑成功");
}
@Override
public void onError(Throwable e) {
System.out.println("解绑失败:" + e.getMessage());
}
@Override
public void onComplete() {
}
});
[user disassociateWithPlatform:LeanCloudSocialPlatformWeiXin callback:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded) {
NSLog(@"成功");
} else{
NSLog(@"失败:%@",error.localizedFailureReason);
}
}];
currentUser.disassociate(authData: .weixin) { (result) in
switch result {
case .success:
// 解除关联成功
case .failure(error: let error):
// 解除关联失败