HarmonyOS鸿蒙Next中Account Kit怎样实现登出
HarmonyOS鸿蒙Next中Account Kit怎样实现登出 目前打算尝试通过撤回授权的方式实现登出,预期逻辑:点击登出->跳转至登录首页->点击登录拉起授权界面(正常无登出情况下点击登录界面后,无授权界面直接跳转至应用首页)
用云真机调试目前问题现象:点击登出->弹窗显示登出成功->跳转至登录页->点击后直接跳转首页(无法拉起授权页)
请各位大佬帮忙看看,怎么才能在这种情况下实现登出、拉起授权页哇🤔我的这套登录代码目前只第一次在云真机调试的时候拉起过授权页,后面再也没有拉起过了
登出代码:
async performLogout() {
try {
// 1. 创建一个取消授权的请求对象
const provider = new authentication.HuaweiIDProvider();
const cancelRequest: authentication.CancelAuthorizationRequest = provider.createCancelAuthorizationRequest();
// 2. 执行该请求
const controller = new authentication.AuthenticationController(getContext(this));
await controller.executeRequest(cancelRequest);
hilog.info(DOMAIN, TAG, 'Account Kit 登出成功');
promptAction.showToast({ message: '已退出登录,即将执行clearLocalState' });
this.clearLocalState();
} catch (err) {
let error = err as BusinessError;
hilog.error(DOMAIN, TAG, `退出登录异常 - code: ${error.code}, msg: ${error.message}`);
const errorTime = new Date().toISOString();
const errObj = err as Record<string, Object>;
const code = (errObj['code'] !== undefined) ? errObj['code'] + '' : '无code';
const message = (errObj['message'] !== undefined) ? errObj['message'] + '' : '无message';
let dataStr = '无data';
if (errObj['data'] !== undefined) {
try {
dataStr = JSON.stringify(errObj['data']);
} catch (e) {
dataStr = 'data序列化失败';
}
}
hilog.error(DOMAIN, TAG,
`❌ CloudDB 操作失败 | 时间: %{public}s | code: %{public}s | message: %{public}s | data: %{public}s`,
errorTime, code, message, dataStr
);
const dialogMsg = `时间: ${errorTime}\ncode: ${code}\nmessage: ${message}`;
promptAction.showDialog({
title: '退出失败',
message: dialogMsg,
buttons: [{ text: '知道了', color: '#FF6B6B' }]
});
}
}
// 清理本地状态并跳转登录页
clearLocalState() {
userState.currentUserId = '';
userState.currentNickname = '';
router.replaceUrl({ url: 'pages/LoginPage' });
}
登录代码:
controller: loginComponentManager.LoginWithHuaweiIDButtonController = new loginComponentManager.LoginWithHuaweiIDButtonController();
@State isLoading: boolean = false;
@State errorMsg: string = '';
aboutToAppear(): void {
// 监听登录结果,直接获取 UnionID
this.controller.onClickLoginWithHuaweiIDButton(async (error: BusinessError, credential: authentication.LoginWithHuaweiIDCredential) => {
if (error) {
hilog.error(DOMAIN, TAG, `登录失败: code=${error.code}, msg=${error.message}`);
promptAction.showToast({ message: '登录失败'+'code'+error.code+'message'+error.message, duration: 4000 });
this.errorMsg = `登录失败: ${error.message}`;
this.isLoading = false;
return;
}
// 3. 登录成功,直接从 credential 中获取 UnionID 和 OpenID
const unionId: string | undefined = credential?.unionID;
const openId: string | undefined = credential?.openID;
hilog.info(DOMAIN, TAG, `登录成功`);
hilog.info(DOMAIN, TAG, `UnionID: ${unionId}`);
hilog.info(DOMAIN, TAG, `OpenID: ${openId}`);
if (unionId) {
try {
const profile = await getOrCreateUserProfile(unionId);
userState.currentUserId = unionId;
userState.currentNickname=profile.nickname;
router.replaceUrl({ url: 'pages/Index' });
} catch (err) {
this.isLoading = false;
}
} else {
this.errorMsg = '登录失败:未能获取到用户身份信息';
promptAction.showToast({message:'登录失败:未能获取到用户身份信息', duration: 4000} );
this.isLoading = false;
}
});
}
build() {
Column() {
// .....其他界面UI
// 登录按钮及状态区域
Column() {
// 错误提示
if (this.errorMsg) {
Text(this.errorMsg)
.fontSize(14)
.fontColor('#FF6B6B')
.textAlign(TextAlign.Center)
.margin({ bottom: 20 })
.width('80%')
}
// 华为登录按钮
if (!this.isLoading) {
LoginWithHuaweiIDButton({
params: {
loginType: loginComponentManager.LoginType.ID, // 标准华为账号登录
style: loginComponentManager.Style.BUTTON_CUSTOM,
extraStyle: {
buttonStyle: new loginComponentManager.ButtonStyle()
//.backgroundColor($r('app.color.theme_purple'))
.backgroundColor($r('app.color.theme_purple'))
}
},
controller: this.controller
})
.height(50)
.width('85%')
.borderRadius(25)
.shadow({ radius: 8, color: '#1A000000', offsetY: 2 })
}
// Loading 动画
if (this.isLoading) {
LoadingProgress()
.width(36)
.height(36)
.margin({ top: 20 })
Text('正在登录...')
.fontSize(13)
.fontColor('#AAAAAA')
.margin({ top: 8 })
}
}
.width('100%')
.alignItems(HorizontalAlign.Center)
.margin({ bottom: 60 })
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.card_bg'))
}
#getOrCreateUserProfile方法
export async function getOrCreateUserProfile(uid: string): Promise<UserProfile> {
const zone =await getAuthenticatedZone();
const query = new cloudDatabase.DatabaseQuery(UserProfile);
query.equalTo('uid', uid);
const result = await zone.query(query);
if (result.length > 0) {
promptAction.showToast({ message: '✅ 用户资料已存在', duration: 7000 });
return result[0] as UserProfile;
}
// ========== 如果用户资料不存在,先刷新凭证,再执行 upsert ==========
const now = Date.now();
let profile = new UserProfile();
try {
profile.uid = uid;
profile.nickname = 'xxxx';
profile.avatarUrl = '';
profile.createAt = now;
profile.updateAt = now;
await zone.upsert(profile);
return profile;
}catch (err){
// 1. 生成精确到毫秒的错误时间戳
const errorTime = new Date().toISOString();
// 2. 将错误对象转为Record,方便安全地提取字段
const errObj = err as Record<string, Object>;
// 3. 安全地提取 code 和 message
const code = (errObj['code'] !== undefined) ? errObj['code'] + '' : '无code';
const message = (errObj['message'] !== undefined) ? errObj['message'] + '' : '无message';
// 4. 如果存在 data,提取出来
let dataStr = '无data';
if (errObj['data'] !== undefined) {
try {
dataStr = JSON.stringify(errObj['data']);
} catch (e) {
dataStr = 'data序列化失败';
}
}
// 5. 使用 hilog.error 打印完整的错误信息
// 注意:必须使用 %{public}s,否则真机日志会显示 <private>
hilog.error(DOMAIN, TAG,
`❌ CloudDB 操作失败 | 时间: %{public}s | code: %{public}s | message: %{public}s | data: %{public}s`,
errorTime, code, message, dataStr
);
// 6. 同时保留弹窗,方便快速查看
const dialogMsg = `时间: ${errorTime}\ncode: ${code}\nmessage: ${message}`;
// 7. 抛出包含完整信息的 Error
throw new Error(`[${errorTime}] code: ${code}, message: ${message}, data: ${dataStr}`);
}
}
更多关于HarmonyOS鸿蒙Next中Account Kit怎样实现登出的实战教程也可以访问 https://www.itying.com/category-93-b0.html
可以的,这里要把“登录”和“授权”分开看。
如果你的目标是重新弹出授权确认页,不建议只用 createLoginWithHuaweiIDRequest() 里的 forceLogin。forceLogin 主要影响“华为账号未登录时是否拉起登录页”,不等于每次都重新展示授权确认。
重新请求授权建议用 AuthorizationWithHuaweiIDRequest:
const provider = new authentication.HuaweiIDProvider();
const req = provider.createAuthorizationWithHuaweiIDRequest();
req.scopes = ['openid'];
req.permissions = ['idtoken', 'serviceauthcode'];
req.forceAuthorization = true;
req.state = util.generateRandomUUID();
req.idTokenSignAlgorithm = authentication.IdTokenSignAlgorithm.PS256;
const controller = new authentication.AuthenticationController(this.getUIContext().getHostContext());
const res = await controller.executeRequest(req) as authentication.AuthorizationWithHuaweiIDResponse;
这里 scopes/permissions 按你实际要取的数据来配:只要 OpenID/UnionID 关联用户,一般 openid 就够;如果服务端需要换取 token 或校验身份,再加 serviceauthcode、idtoken。不要把文档里所有项都堆进去,未申请或不支持的 scope/permission 可能报 1001502014 或 1001500003。
如果用户已经授权过同一组 scopes/permissions,仍想让他重新确认,可以先走 createCancelAuthorizationRequest() 取消本应用授权,再用上面的 AuthorizationWithHuaweiIDRequest + forceAuthorization 重新拉起。
更多关于HarmonyOS鸿蒙Next中Account Kit怎样实现登出的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
我拉起授权登录也是一样的,这是华为帐号登录的逻辑。
可以订阅到华为账号登出事件然后处理
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError, commonEventManager } from '@kit.BasicServicesKit';
// 订阅者信息
const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
events: [commonEventManager.Support.COMMON_EVENT_DISTRIBUTED_ACCOUNT_LOGIN,
commonEventManager.Support.COMMON_EVENT_DISTRIBUTED_ACCOUNT_LOGOUT]
};
let subscriber: commonEventManager.CommonEventSubscriber;
// 创建订阅者
commonEventManager.createSubscriber(subscribeInfo)
.then((commonEventSubscriber: commonEventManager.CommonEventSubscriber) => {
subscriber = commonEventSubscriber;
// 订阅公共事件
commonEventManager.subscribe(subscriber,
(error: BusinessError, data: commonEventManager.CommonEventData) => {
if (error) {
hilog.error(0x0000, 'testTag',
`Failed to subscribe, code is ${error.code}, message is ${error.message}`);
} else {
hilog.info(0x0000, 'testTag', 'Succeeded in subscribing.');
if (data.event === commonEventManager.Support.COMMON_EVENT_DISTRIBUTED_ACCOUNT_LOGIN) {
// 订阅到华为账号登录事件
}
if (data.event === commonEventManager.Support.COMMON_EVENT_DISTRIBUTED_ACCOUNT_LOGOUT) {
// 订阅到华为账号登出事件
}
}
});
})
.catch((err: BusinessError) => {
hilog.error(0x0000, 'testTag', `Failed to createSubscriber. Code: ${err.code}, message: ${err.message}`);
});
可以参考一下官方demo
https://gitcode.com/HarmonyOS_Samples/accountkit-samplecode-clientdemo-arkts


有要学HarmonyOS AI的同学吗,联系我:https://www.itying.com/goods-1206.html
这里要先区分“应用退出登录”和“退出设备上的华为账号”。createCancelAuthorizationRequest() 是取消当前应用的 Account Kit 授权,不等于把系统里的华为账号登出。
所以你看到的现象是可能的:本地状态清掉后回到登录页,但设备上的华为账号仍处于登录状态,下一次调用登录请求时仍可能直接返回登录结果,不一定每次都拉起授权页。
应用侧登出通常建议做三件事:清理本地业务登录态和用户缓存;通知服务端废弃自己的 session/refresh token;回到登录页。Account Kit 的 forceLogin 主要控制“华为账号未登录时是否拉起登录页”,不能保证每次都弹出授权确认界面。
如果你的目标是重新弹出授权确认,可以走授权请求 createAuthorizationWithHuaweiIDRequest(),设置所需 scopes/permissions,并按场景设置 forceAuthorization = true。如果目标是切换华为账号,则需要引导用户到系统账号侧切换,应用内的取消授权不能替用户退出系统账号。
感谢您的解答,我目标是重新弹出授权确认,但是不太懂怎么设置所需 scopes/permissions,可以麻烦您多指导一下吗,谢谢呀谢谢呀🤩
同问
你这个其实不是“登出失败”,而是:
Account Kit 做了单点登录缓存(SSO)
所以即使:
executeRequest(cancelRequest)
成功了,
系统里:
- 华为账号仍然在线
- 授权缓存仍然存在
- 云真机环境尤其容易缓存
因此:
再次点击登录会直接静默登录
不会再弹授权页。
你现在这个行为:
第一次弹授权页
后面永远直接登录
其实是正常现象。
很多平台:
- 华为 Account Kit
- Google Sign In
- Apple Sign In
都是这种逻辑。
关键点
CancelAuthorization
≠
退出系统华为账号
它只是:
撤销应用授权
但:
系统登录态还在
所以:
系统认为:
用户已经是可信用户
会直接返回 credential。
云真机尤其明显
云真机很多时候:
- 账号环境固定
- SSO缓存持久
- 授权页不会频繁弹
所以:
云真机不适合验证“是否弹授权页”
真机表现通常更符合预期。
正确理解“登出”
你现在应该区分:
1. 应用层登出(推荐)
也就是:
clearLocalState()
清掉:
- uid
- token
- 本地缓存
- 用户状态
然后回登录页。
这个其实已经算:
完成登出
了。
很多应用实际上都这么干。
2. 强制重新授权(不一定能做到)
你期待的是:
每次登录都重新弹授权页
但:
Account Kit 默认不是这么设计的
它偏向:
静默登录体验
你现在代码其实没太大问题
你这里:
await controller.executeRequest(cancelRequest);
已经是官方标准登出方式。
问题不在代码。
而在:
系统SSO缓存
如果你真的想强制重新授权
目前只能尝试:
方法1(推荐)
在:
手机系统设置
里:
华为账号
-> 登录设备
-> 应用授权管理
手动撤销。
方法2
卸载应用重新安装。
方法3
换华为账号。
你现在真正推荐做法
其实是:
不要依赖:
“是否再次弹授权页”
来判断登录状态。
而是:
自己维护应用登录态
例如:
userState.isLogin
退出时:
clearLocalState()
即可。
你这个项目更适合这样
推荐:
首次登录:
拉授权
获取 unionId
后续:
直接静默登录
退出:
只清本地状态
这是目前最符合 Account Kit 的方式。
一句话总结
你现在不是代码有问题,而是:
Account Kit 默认支持SSO静默登录
即使:
cancelAuthorization
成功,
系统华为账号登录态仍然存在,
所以不会再次弹授权页。
目前:
“退出应用账号”
和:
“退出系统华为账号”
是两回事。
在HarmonyOS NEXT中,使用Account Kit实现登出:调用accountKit.logout()方法,传入当前用户ID。登出成功后清除本地token及用户缓存,并跳转至登录页。需在主线程执行,捕获异常。
你遇到的问题是因为 CancelAuthorizationRequest 只撤销了应用授权,但设备上的华为账号仍处于已登录状态,因此再次点击登录按钮时,SDK 直接使用已登录账号的缓存授权,不再拉起授权界面。
要实现“登出后重新拉起授权页”,需要做两件事之一:
方案一:登出时调用 signOut(推荐)
将 performLogout 中的取消授权请求改为 signOut,真正退出设备上的华为账号:
async performLogout() {
const provider = new authentication.HuaweiIDProvider();
// 调用 signOut 登出华为账号
await provider.signOut();
// 清理本地状态...
}
signOut 会让设备彻底退出华为账号,下次登录必然重新弹出授权页。
方案二:登录按钮强制授权
不修改登出逻辑,在 LoginWithHuaweiIDButton 的 params 中增加 forceLogin: true:
LoginWithHuaweiIDButton({
params: {
loginType: loginComponentManager.LoginType.ID,
style: loginComponentManager.Style.BUTTON_CUSTOM,
forceLogin: true, // 每次点击都强制弹出授权页
// ... 其他配置
},
controller: this.controller
})
但注意 forceLogin 可能在某些版本不生效,最佳实践仍是调用 signOut。

