HarmonyOS 登录态治理实战:启动校验、会话续期与多端退出

登录态问题通常不是“登录接口坏了”,而是多个入口没有统一规则:启动时读到旧 token 就放行,切后台回来没有校验,接口 401 后每个页面各自跳登录,多设备退出后本机仍然显示旧数据。用户看到的是频繁掉线、重复登录或退出不干净,研发排查时却发现日志分散在页面、网络层和缓存层。

请添加图片描述

本文只解决一个工程问题:在 HarmonyOS 应用中把登录态做成一条可追踪链路,让启动校验、token 续期、接口鉴权、多端退出和本地清理有统一边界。

一、登录态先分四类,不要只看 token 是否存在

很多项目的第一版登录态判断只有一句:本地有 token 就认为已登录。这个判断太粗。token 可能过期、用户可能被后台禁用、其他设备可能已经退出登录。

状态 含义 页面策略
anonymous 本地没有会话 进入登录页或游客态
verifying 本地有会话,正在轻量校验 显示启动页或骨架屏
authenticated 会话有效 进入业务首页
expired 会话过期或被撤销 清理本地并跳登录

请添加图片描述

这个状态表要放在应用入口和网络层共同遵守的位置,不能散落在每个页面。

二、资料与版本边界:本文写应用层会话治理

本文示例面向 HarmonyOS NEXT / Stage 模型 / ArkTS 工程,重点在应用层:本地会话模型、启动校验、续期策略、401 收敛、多端退出和清理验收。真实项目还需要结合团队的账号系统、OAuth 或自研 token 方案、加密存储能力、隐私要求和后端接口约定。

会话治理层 本文处理的职责 接入项目时要确认
入口层 启动时会话校验和路由选择 UIAbility 启动链路
存储层 accessToken、refreshToken、过期时间 加密存储与清理策略
网络层 401 统一处理、续期队列 网络库拦截器实现
多端层 远端退出、本机清理 账号系统事件通知方式
验收层 弱网、过期、并发、退出测试 真机和测试账号

请添加图片描述

三、会话模型:token、用户和过期时间要拆开

会话模型不要只保存一个字符串。至少要区分用户、token、过期时间和刷新窗口。

export interface AccountSession {
  userId: string;
  accessToken: string;
  refreshToken: string;
  accessExpireAt: number;
  refreshExpireAt: number;
  lastVerifyAt: number;
}

export function sessionAccessExpired(session: AccountSession, now: number): boolean {
  return now >= session.accessExpireAt;
}

export function sessionCanRefresh(session: AccountSession, now: number): boolean {
  return now < session.refreshExpireAt;
}

这段模型的边界是“描述本机会话事实”。它不负责跳转页面,也不直接请求接口。这样做可以避免页面层拿 token 字符串做各种临时判断。

四、启动校验:本地命中后也要轻量确认

启动阶段适合做轻量校验,而不是拉全量用户资料。目标是快速判断能不能进入业务页。

export type StartupAuthRoute = 'LoginPage' | 'HomePage' | 'SplashVerifyingPage';

export interface StartupAuthDecision {
  route: StartupAuthRoute;
  reason: string;
}

export function decideStartupAuthRoute(
  session: AccountSession | undefined,
  now: number
): StartupAuthDecision {
  if (session === undefined) {
    return { route: 'LoginPage', reason: '本地没有会话' };
  }
  if (sessionAccessExpired(session, now) && !sessionCanRefresh(session, now)) {
    return { route: 'LoginPage', reason: '会话已超过可刷新时间' };
  }
  if (Date.now() - session.lastVerifyAt > 10 * 60 * 1000) {
    return { route: 'SplashVerifyingPage', reason: '需要进行启动校验' };
  }
  return { route: 'HomePage', reason: '本地会话仍在可信窗口内' };
}

这段代码的输入是本地会话和当前时间,输出是启动路由建议。它预防的是“旧 token 直接进首页,接口再一片 401”的体验。

五、续期队列:多个接口同时 401 时只刷新一次

登录态治理最容易写乱的地方是 token 续期。多个接口同时返回 401,如果每个请求都刷新一次,会造成并发风暴。

export class TokenRefreshGate {
  private refreshing = false;
  private waiters: Array<(success: boolean) => void> = [];

  begin(): boolean {
    if (this.refreshing) {
      return false;
    }
    this.refreshing = true;
    return true;
  }

  wait(callback: (success: boolean) => void): void {
    this.waiters.push(callback);
  }

  finish(success: boolean): void {
    this.refreshing = false;
    const callbacks = this.waiters;
    this.waiters = [];
    for (const callback of callbacks) {
      callback(success);
    }
  }
}

这个 Gate 的职责是“并发收敛”。第一个 401 请求负责刷新,后续请求等待结果。刷新成功后重试原请求,失败后统一清理会话并跳登录。

六、401 处理:网络层只发事件,不直接操作页面

网络层不应该直接弹窗或跳页面。更稳的做法是把鉴权失败封装成事件,由应用状态中心决定如何处理。

export type AuthEventType = 'tokenExpired' | 'refreshFailed' | 'remoteLogout';

export interface AuthEvent {
  type: AuthEventType;
  traceId: string;
  message: string;
  timestamp: number;
}

export function createAuthEvent(type: AuthEventType, message: string): AuthEvent {
  return {
    type,
    traceId: `auth_${type}_${Date.now()}`,
    message,
    timestamp: Date.now()
  };
}

事件模型让网络层、页面层和登录模块解耦。接口只负责告诉系统发生了什么,不决定 UI 怎么跳。

七、多端退出:清理范围要比退出按钮更大

多端退出不仅要删 token,还要清理用户资料、草稿、缓存、订阅和正在执行的后台任务。否则下一个账号登录后可能看到前一个账号的数据。

export interface LogoutCleanTask {
  name: string;
  required: boolean;
  done: boolean;
}

export function buildLogoutCleanTasks(): LogoutCleanTask[] {
  return [
    { name: '清理会话 token', required: true, done: false },
    { name: '清理用户资料缓存', required: true, done: false },
    { name: '取消账号相关订阅', required: true, done: false },
    { name: '停止待执行上传任务', required: false, done: false },
    { name: '清空敏感草稿', required: true, done: false }
  ];
}

export function logoutCleanCompleted(tasks: LogoutCleanTask[]): boolean {
  return tasks.every(task => !task.required || task.done);
}

这段清理任务表适合放在退出流程里做逐项执行和记录。必需项没完成时,不应该直接进入登录页。

八、登录态问题排查表

登录异常表现 优先怀疑的会话环节 排查入口 修复方向
启动后首页立刻跳登录 启动未做可信窗口判断 查看 StartupAuthDecision.reason 增加校验页或续期流程
多个接口重复刷新 token 没有续期并发收敛 检查 TokenRefreshGate.begin 只允许一个刷新请求
退出后仍看到旧头像 用户缓存未清理 对照 LogoutCleanTask 扩大退出清理范围
401 后页面各跳各的 网络层直接控制页面 查网络拦截器 改为发 AuthEvent
多设备退出不同步 缺少远端退出事件 查账号事件或轮询校验 收到远端退出后清理本机
日志无法串接口和页面 缺少 traceId 查鉴权事件字段 统一 AuthEvent.traceId

排查顺序是:先看本地会话,再看续期队列,再看网络事件,最后看页面跳转。不要一开始就改登录页 UI。

九、登录态上线前验收表

登录态验收点 可交付标准
冷启动校验 无 token、有效 token、过期 token 都有明确路径
并发续期 多个接口 401 时只发起一次刷新
远端退出 其他设备退出后本机能感知并清理
本地清理 token、用户资料、订阅、敏感草稿都清掉
弱网恢复 续期失败有提示,不会卡在空白页
日志追踪 登录、续期、失败、退出都能用 traceId 串联
隐私保护 日志不输出 accessToken、refreshToken

验收时至少准备两个账号、两台设备和一个可控过期 token。只测正常登录,覆盖不了会话治理的关键风险。

十、登录态相关官方资料

  1. 华为开发者文档:Stage 模型应用开发
    https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/stage-model-development-overview
  2. 华为开发者文档:Want
    https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-ability-want
  3. 华为开发者文档:应用文件与数据管理
    https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-file-access
  4. 华为开发者文档:应用安全与隐私
    https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/security-privacy-overview

十一、把登录态做成全局基础设施

登录态治理的稳定做法,是把“是否登录”从页面判断升级为全局基础设施。会话模型记录事实,启动决策控制入口,续期队列收敛并发,鉴权事件串起网络层和页面层,退出清理保证数据边界。

会话治理问题 推荐处理方式
本地有 token 就能进首页吗 不能,还要看过期时间和可信窗口
多接口 401 怎么办 用续期 Gate 收敛为一次刷新
网络层能跳登录页吗 不建议,发鉴权事件给应用状态层
退出要清理什么 会话、缓存、订阅、草稿和任务
怎么复盘问题 用 traceId 串登录、续期、退出链路
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐