HarmonyOS开发:用户认证Authentication

举报
Jack20 发表于 2026/06/25 20:58:52 2026/06/25
【摘要】 HarmonyOS开发:用户认证Authentication📌 核心要点:Authentication Kit让你不用自己搞登录系统,华为账号、手机号、邮箱、第三方账号一站式集成,账号绑定与解绑开箱即用。 背景与动机你做了一款App,需要用户登录。看起来简单——一个输入框加一个按钮嘛。然后呢?手机号登录要发验证码,验证码有频率限制、有效期、防刷问题。邮箱登录要发确认邮件,邮件可能进垃圾箱...

HarmonyOS开发:用户认证Authentication

📌 核心要点:Authentication Kit让你不用自己搞登录系统,华为账号、手机号、邮箱、第三方账号一站式集成,账号绑定与解绑开箱即用。

背景与动机

你做了一款App,需要用户登录。看起来简单——一个输入框加一个按钮嘛。然后呢?

手机号登录要发验证码,验证码有频率限制、有效期、防刷问题。邮箱登录要发确认邮件,邮件可能进垃圾箱。第三方登录要对接微信、QQ、微博的SDK,每个SDK的接入方式都不一样。账号合并要处理同一个手机号绑定了微信和邮箱的情况。Token管理要处理过期刷新。安全要防暴力破解、防重放攻击……

一个"登录"功能,背后是一个完整的认证系统。你自己搞,至少一个月,而且安全性还不一定过关。

Authentication Kit就是来解决这个问题的。它把华为账号登录、手机号认证、邮箱认证、第三方账号绑定全封装好了,你集成SDK就能用。

但Authentication Kit不是"一个按钮搞定登录"。你得理解认证流程、Token管理、账号绑定的逻辑,不然用户登录了却拿不到数据、换了手机登不上、第三方账号绑定冲突——每个都是线上事故。

核心原理

Authentication Kit的核心流程:用户选择登录方式 → 认证服务验证身份 → 返回Token → 客户端用Token访问资源。

flowchart TD
    A[用户点击登录] --> B{选择登录方式}
    B -->|华为账号| C[拉起华为账号授权页]
    B -->|手机号| D[发送验证码]
    B -->|邮箱| E[发送验证链接]
    B -->|第三方| F[拉起第三方授权]
    
    C --> G[用户确认授权]
    D --> H[用户输入验证码]
    E --> I[用户点击验证链接]
    F --> J[第三方返回授权码]
    
    G --> K[认证服务验证]
    H --> K
    I --> K
    J --> K
    
    K --> L{验证结果}
    L -->|新用户| M[自动创建账号]
    L -->|老用户| N[匹配已有账号]
    
    M --> O[返回Token]
    N --> O
    
    O --> P[AccessToken: 访问资源]
    O --> Q[RefreshToken: 刷新Token]
    O --> R[IdToken: 用户身份信息]
    
    P --> S[用Token访问Cloud DB/Storage/Functions]
    
    classDef login fill:#1565C0,color:#fff,stroke:#0D47A1
    classDef verify fill:#E65100,color:#fff,stroke:#BF360C
    classDef token fill:#2E7D32,color:#fff,stroke:#1B5E20
    classDef access fill:#6A1B9A,color:#fff,stroke:#4A148C

    class A,B,C,D,E,F,login
    class G,H,I,J,K,L,M,N,verify
    class O,P,Q,R,token
    class S,access

Token体系

Authentication Kit使用三Token体系:

  • AccessToken:访问资源的凭证,有效期短(通常1小时),过期后需要刷新
  • RefreshToken:刷新AccessToken的凭证,有效期长(通常30天),过期后需要重新登录
  • IdToken:包含用户身份信息的JWT,可以解析出用户ID、昵称等

账号绑定

一个用户可以用华为账号登录,也可以用手机号登录。如果两种方式对应的是同一个用户,就需要"账号绑定"。

Authentication Kit支持一个主账号绑定多个认证方式:华为账号 + 手机号 + 邮箱 + 微信。用户用任何一种方式登录,都能访问同一份数据。

代码实战

基础用法:华为账号登录

最简单的登录方式——华为账号一键登录。

// AuthManager.ets
import { authentication } from '@kit.AuthenticationKit';
import { hmsAccount } from '@kit.HmsAccountKit';

export class AuthManager {
  private static instance: AuthManager;
  private currentUser: authentication.User | null = null;
  private authServiceImpl: authentication.AuthService | null = null;

  private constructor() {}

  static getInstance(): AuthManager {
    if (!AuthManager.instance) {
      AuthManager.instance = new AuthManager();
    }
    return AuthManager.instance;
  }

  /**
   * 初始化认证服务
   */
  init(): void {
    try {
      this.authServiceImpl = authentication.getService();
      console.info('[Auth] 认证服务初始化完成');
    } catch (error) {
      console.error(`[Auth] 初始化失败: ${JSON.stringify(error)}`);
    }
  }

  /**
   * 华为账号登录
   * 一键登录,最简单的接入方式
   */
  async signInWithHuaweiId(): Promise<authentication.SignInResult | null> {
    if (!this.authServiceImpl) {
      console.error('[Auth] 认证服务未初始化');
      return null;
    }

    try {
      // 构建登录请求
      const signInRequest = new authentication.HuaweiIdAuthProvider()
        .signIn();

      // 执行登录
      const result = await this.authServiceImpl.signIn(signInRequest);

      // 保存当前用户信息
      this.currentUser = result.getUser();
      
      console.info(`[Auth] 登录成功: uid=${this.currentUser?.getUid()}, ` +
        `nickname=${this.currentUser?.getDisplayName()}`);

      return result;
    } catch (error) {
      const authError = error as authentication.AuthError;
      switch (authError.code) {
        case authentication.AuthErrorCode.SIGN_IN_CANCELLED:
          console.info('[Auth] 用户取消登录');
          break;
        case authentication.AuthErrorCode.SIGN_IN_FAILED:
          console.error('[Auth] 登录失败');
          break;
        default:
          console.error(`[Auth] 登录异常: ${JSON.stringify(error)}`);
      }
      return null;
    }
  }

  /**
   * 获取当前登录用户
   */
  getCurrentUser(): authentication.User | null {
    return this.currentUser;
  }

  /**
   * 判断是否已登录
   */
  isSignedIn(): boolean {
    return this.currentUser !== null;
  }

  /**
   * 退出登录
   */
  async signOut(): Promise<boolean> {
    if (!this.authServiceImpl) return false;

    try {
      await this.authServiceImpl.signOut();
      this.currentUser = null;
      console.info('[Auth] 退出登录成功');
      return true;
    } catch (error) {
      console.error(`[Auth] 退出登录失败: ${JSON.stringify(error)}`);
      return false;
    }
  }
}

进阶用法:手机号/邮箱认证与账号绑定

华为账号登录最简单,但有些用户没有华为账号。手机号和邮箱认证覆盖面更广。

// PhoneAuth.ets
import { authentication } from '@kit.AuthenticationKit';

export class PhoneAuthService {
  private authService: authentication.AuthService;
  private verifyId: string = ''; // 验证ID,验证码校验时需要

  constructor(authService: authentication.AuthService) {
    this.authService = authService;
  }

  /**
   * 发送手机验证码
   */
  async sendVerificationCode(
    phoneNumber: string,
    countryCode: string = '+86'
  ): Promise<boolean> {
    try {
      // 请求发送验证码
      const result = await this.authService.requestVerifyCode(
        countryCode + phoneNumber,
        authentication.VerifyCodeAction.SIGN_IN_OR_SIGN_UP
      );

      // 保存验证ID,后续校验时需要
      this.verifyId = result.getVerifyId();
      
      console.info(`[PhoneAuth] 验证码已发送到 ${countryCode}${phoneNumber}`);
      return true;
    } catch (error) {
      const authError = error as authentication.AuthError;
      switch (authError.code) {
        case authentication.AuthErrorCode.VERIFY_CODE_TOO_FREQUENT:
          console.error('[PhoneAuth] 验证码发送太频繁,请稍后再试');
          break;
        case authentication.AuthErrorCode.PHONE_NUMBER_INVALID:
          console.error('[PhoneAuth] 手机号格式不正确');
          break;
        default:
          console.error(`[PhoneAuth] 发送失败: ${JSON.stringify(error)}`);
      }
      return false;
    }
  }

  /**
   * 用手机验证码登录/注册
   */
  async signInWithPhone(
    phoneNumber: string,
    verifyCode: string,
    countryCode: string = '+86'
  ): Promise<authentication.SignInResult | null> {
    try {
      const provider = new authentication.PhoneAuthProvider();
      const credential = provider.credential(
        countryCode + phoneNumber,
        verifyCode,
        this.verifyId
      );

      const result = await this.authService.signIn(credential);
      console.info(`[PhoneAuth] 手机号登录成功: ${phoneNumber}`);
      return result;
    } catch (error) {
      console.error(`[PhoneAuth] 手机号登录失败: ${JSON.stringify(error)}`);
      return null;
    }
  }

  /**
   * 绑定手机号到当前账号
   * 已登录用户可以绑定手机号,之后可以用手机号登录
   */
  async bindPhoneNumber(
    phoneNumber: string,
    verifyCode: string,
    countryCode: string = '+86'
  ): Promise<boolean> {
    try {
      const provider = new authentication.PhoneAuthProvider();
      const credential = provider.credential(
        countryCode + phoneNumber,
        verifyCode,
        this.verifyId
      );

      await this.authService.link(credential);
      console.info(`[PhoneAuth] 手机号绑定成功: ${phoneNumber}`);
      return true;
    } catch (error) {
      const authError = error as authentication.AuthError;
      if (authError.code === authentication.AuthErrorCode.LINK_ACCOUNT_CONFLICT) {
        // 该手机号已被其他账号绑定
        console.error('[PhoneAuth] 该手机号已被其他账号使用');
      } else {
        console.error(`[PhoneAuth] 绑定失败: ${JSON.stringify(error)}`);
      }
      return false;
    }
  }

  /**
   * 解绑手机号
   */
  async unbindPhoneNumber(): Promise<boolean> {
    try {
      await this.authService.unlink(authentication.ProviderType.PHONE);
      console.info('[PhoneAuth] 手机号解绑成功');
      return true;
    } catch (error) {
      console.error(`[PhoneAuth] 解绑失败: ${JSON.stringify(error)}`);
      return false;
    }
  }
}

// 邮箱认证类似,只是验证方式从短信变成邮件
export class EmailAuthService {
  private authService: authentication.AuthService;
  private verifyId: string = '';

  constructor(authService: authentication.AuthService) {
    this.authService = authService;
  }

  /**
   * 发送邮箱验证码
   */
  async sendEmailVerifyCode(email: string): Promise<boolean> {
    try {
      const result = await this.authService.requestVerifyCode(
        email,
        authentication.VerifyCodeAction.SIGN_IN_OR_SIGN_UP
      );
      this.verifyId = result.getVerifyId();
      console.info(`[EmailAuth] 验证码已发送到 ${email}`);
      return true;
    } catch (error) {
      console.error(`[EmailAuth] 发送失败: ${JSON.stringify(error)}`);
      return false;
    }
  }

  /**
   * 邮箱验证码登录
   */
  async signInWithEmail(email: string, verifyCode: string): Promise<authentication.SignInResult | null> {
    try {
      const provider = new authentication.EmailAuthProvider();
      const credential = provider.credential(email, verifyCode, this.verifyId);
      const result = await this.authService.signIn(credential);
      console.info(`[EmailAuth] 邮箱登录成功: ${email}`);
      return result;
    } catch (error) {
      console.error(`[EmailAuth] 登录失败: ${JSON.stringify(error)}`);
      return null;
    }
  }

  /**
   * 绑定邮箱
   */
  async bindEmail(email: string, verifyCode: string): Promise<boolean> {
    try {
      const provider = new authentication.EmailAuthProvider();
      const credential = provider.credential(email, verifyCode, this.verifyId);
      await this.authService.link(credential);
      console.info(`[EmailAuth] 邮箱绑定成功: ${email}`);
      return true;
    } catch (error) {
      console.error(`[EmailAuth] 绑定失败: ${JSON.stringify(error)}`);
      return false;
    }
  }
}

完整示例:生产级认证系统

把华为账号、手机号、邮箱、第三方账号统一管理,处理Token刷新和账号冲突。

// ProductionAuthService.ets
import { authentication } from '@kit.AuthenticationKit';

// 认证状态
enum AuthState {
  SIGNED_OUT = 'signed_out',
  SIGNING_IN = 'signing_in',
  SIGNED_IN = 'signed_in',
  TOKEN_EXPIRED = 'token_expired'
}

// 用户信息模型
interface UserProfile {
  uid: string;
  displayName: string;
  email: string | null;
  phoneNumber: string | null;
  photoUrl: string | null;
  providerIds: string[];  // 已绑定的认证方式
  isAnonymous: boolean;
}

export class ProductionAuthService {
  private static instance: ProductionAuthService;
  private authService: authentication.AuthService | null = null;
  private authState: AuthState = AuthState.SIGNED_OUT;
  private userProfile: UserProfile | null = null;
  private stateListeners: Array<(state: AuthState) => void> = [];

  private constructor() {}

  static getInstance(): ProductionAuthService {
    if (!ProductionAuthService.instance) {
      ProductionAuthService.instance = new ProductionAuthService();
    }
    return ProductionAuthService.instance;
  }

  /**
   * 初始化
   */
  async init(): Promise<void> {
    try {
      this.authService = authentication.getService();

      // 检查是否有已登录的用户(Token持久化)
      const currentUser = this.authService.getCurrentUser();
      if (currentUser) {
        this.userProfile = this.convertUser(currentUser);
        this.authState = AuthState.SIGNED_IN;
        console.info(`[ProdAuth] 恢复登录状态: ${this.userProfile.uid}`);
      }

      // 监听Token过期事件
      this.authService.addTokenListener((event: authentication.TokenEvent) => {
        if (event.type === authentication.TokenEventType.TOKEN_EXPIRED) {
          console.warn('[ProdAuth] Token已过期');
          this.authState = AuthState.TOKEN_EXPIRED;
          this.refreshToken();
        }
      });

      this.notifyStateChange();
    } catch (error) {
      console.error(`[ProdAuth] 初始化失败: ${JSON.stringify(error)}`);
    }
  }

  /**
   * 华为账号登录
   */
  async signInWithHuaweiId(): Promise<UserProfile | null> {
    return this.executeSignIn(async () => {
      const request = new authentication.HuaweiIdAuthProvider().signIn();
      return await this.authService!.signIn(request);
    });
  }

  /**
   * 手机号登录
   */
  async signInWithPhone(phone: string, code: string, verifyId: string): Promise<UserProfile | null> {
    return this.executeSignIn(async () => {
      const credential = new authentication.PhoneAuthProvider()
        .credential(phone, code, verifyId);
      return await this.authService!.signIn(credential);
    });
  }

  /**
   * 邮箱登录
   */
  async signInWithEmail(email: string, code: string, verifyId: string): Promise<UserProfile | null> {
    return this.executeSignIn(async () => {
      const credential = new authentication.EmailAuthProvider()
        .credential(email, code, verifyId);
      return await this.authService!.signIn(credential);
    });
  }

  /**
   * 匿名登录
   * 不需要任何信息,先进入App再引导绑定
   */
  async signInAnonymously(): Promise<UserProfile | null> {
    return this.executeSignIn(async () => {
      return await this.authService!.signInAnonymously();
    });
  }

  /**
   * 退出登录
   */
  async signOut(): Promise<void> {
    if (!this.authService) return;

    try {
      await this.authService.signOut();
      this.userProfile = null;
      this.authState = AuthState.SIGNED_OUT;
      this.notifyStateChange();
      console.info('[ProdAuth] 退出登录成功');
    } catch (error) {
      console.error(`[ProdAuth] 退出失败: ${JSON.stringify(error)}`);
    }
  }

  /**
   * 删除账号
   */
  async deleteAccount(): Promise<boolean> {
    if (!this.authService) return false;

    try {
      await this.authService.deleteUser();
      this.userProfile = null;
      this.authState = AuthState.SIGNED_OUT;
      this.notifyStateChange();
      console.info('[ProdAuth] 账号已删除');
      return true;
    } catch (error) {
      console.error(`[ProdAuth] 删除账号失败: ${JSON.stringify(error)}`);
      return false;
    }
  }

  /**
   * 绑定第三方账号
   */
  async linkProvider(providerType: authentication.ProviderType, credential: authentication.AuthCredential): Promise<boolean> {
    if (!this.authService || !this.userProfile) return false;

    try {
      await this.authService.link(credential);
      // 更新用户信息
      const updatedUser = this.authService.getCurrentUser();
      if (updatedUser) {
        this.userProfile = this.convertUser(updatedUser);
      }
      console.info(`[ProdAuth] 绑定成功: ${providerType}`);
      return true;
    } catch (error) {
      const authError = error as authentication.AuthError;
      if (authError.code === authentication.AuthErrorCode.LINK_ACCOUNT_CONFLICT) {
        console.error('[ProdAuth] 该认证方式已被其他账号使用');
        // 可以提示用户是否要合并账号
      }
      return false;
    }
  }

  /**
   * 获取当前用户信息
   */
  getUserProfile(): UserProfile | null {
    return this.userProfile;
  }

  /**
   * 获取认证状态
   */
  getAuthState(): AuthState {
    return this.authState;
  }

  /**
   * 注册状态变化监听
   */
  addStateListener(listener: (state: AuthState) => void): void {
    this.stateListeners.push(listener);
  }

  /**
   * 执行登录的通用方法
   */
  private async executeSignIn(
    signInAction: () => Promise<authentication.SignInResult>
  ): Promise<UserProfile | null> {
    if (!this.authService) return null;

    this.authState = AuthState.SIGNING_IN;
    this.notifyStateChange();

    try {
      const result = await signInAction();
      const user = result.getUser();
      this.userProfile = this.convertUser(user);
      this.authState = AuthState.SIGNED_IN;
      this.notifyStateChange();

      console.info(`[ProdAuth] 登录成功: uid=${this.userProfile.uid}`);
      return this.userProfile;
    } catch (error) {
      this.authState = AuthState.SIGNED_OUT;
      this.notifyStateChange();
      console.error(`[ProdAuth] 登录失败: ${JSON.stringify(error)}`);
      return null;
    }
  }

  /**
   * 刷新Token
   */
  private async refreshToken(): Promise<void> {
    if (!this.authService) return;

    try {
      await this.authService.refreshToken();
      this.authState = AuthState.SIGNED_IN;
      this.notifyStateChange();
      console.info('[ProdAuth] Token刷新成功');
    } catch (error) {
      console.error(`[ProdAuth] Token刷新失败: ${JSON.stringify(error)}`);
      // Token刷新失败,需要重新登录
      this.authState = AuthState.SIGNED_OUT;
      this.notifyStateChange();
    }
  }

  /**
   * 转换用户对象
   */
  private convertUser(user: authentication.User): UserProfile {
    return {
      uid: user.getUid(),
      displayName: user.getDisplayName() || '',
      email: user.getEmail() || null,
      phoneNumber: user.getPhoneNumber() || null,
      photoUrl: user.getPhotoUrl() || null,
      providerIds: user.getProviderIds() || [],
      isAnonymous: user.isAnonymous()
    };
  }

  /**
   * 通知状态变化
   */
  private notifyStateChange(): void {
    this.stateListeners.forEach(listener => {
      try {
        listener(this.authState);
      } catch (error) {
        console.warn(`[ProdAuth] 监听器执行失败: ${error}`);
      }
    });
  }
}

踩坑与注意事项

坑1:验证码的VerifyId必须保存

requestVerifyCode返回的verifyId,在后续credential校验时必须传入。如果你没保存这个ID,验证码就白发了。

更坑的是,verifyId有时效性,通常5分钟。用户磨蹭太久没输入验证码,verifyId过期了,得重新发。

坑2:账号绑定的冲突处理

用户A用华为账号登录,用户B用手机号登录。现在用户A想绑定同一个手机号——冲突了。

Authentication Kit不会自动合并账号,而是返回LINK_ACCOUNT_CONFLICT错误。你需要自己处理:

  • 提示用户"该手机号已被其他账号使用"
  • 提供账号合并选项(需要用户验证两个账号的身份)

坑3:Token过期后自动刷新可能失败

addTokenListener可以监听Token过期事件,但自动刷新不是100%成功的。比如用户长时间没打开App,RefreshToken也过期了,这时候只能重新登录。

建议:在关键操作前主动检查Token状态,不要等到操作失败了才发现Token过期。

坑4:匿名登录的数据迁移

用户先用匿名登录,产生了一些数据。后来绑定了华为账号,匿名账号升级为正式账号。但如果你在绑定之前没处理好,匿名账号的数据可能丢失。

解决方案:绑定操作会自动把匿名账号的数据迁移到正式账号,前提是你用的是同一个authService实例。

坑5:SHA256指纹必须匹配

华为账号登录要求App的签名指纹和AGC控制台配置的指纹一致。开发阶段用调试签名,上线用发布签名,两个指纹都要在控制台配置。

忘了配调试签名?登录时直接报SIGN_IN_FAILED,而且错误信息不会告诉你是指纹不匹配——你得自己想到。

坑6:手机号格式必须是国际格式

手机号必须带国际区号,如+8613800138000。如果你只传了13800138000,验证码发送失败。PhoneAuthProvidercredential方法也一样。

HarmonyOS 6适配说明

HarmonyOS 6对Authentication Kit做了以下更新:

  1. 生物认证集成:新增指纹和面部识别登录支持。用户首次用华为账号登录后,后续可以用生物认证快速登录,不需要每次都输密码。
// HarmonyOS 6生物认证登录
const biometricResult = await authService.signInWithBiometric(
  authentication.BiometricType.FINGERPRINT
);
  1. 多设备登录管理:新增设备管理能力,用户可以查看当前账号在哪些设备上登录了,可以远程注销其他设备的登录状态。

  2. 账号安全等级:新增安全等级评估API,可以根据用户的认证方式(仅华为账号 vs 华为账号+手机号+邮箱)评估账号安全等级,引导用户绑定更多认证方式。

  3. 自定义认证UI:之前登录页面是系统提供的标准UI,HarmonyOS 6支持自定义登录页面样式,可以和App的品牌风格保持一致。

  4. Token安全增强:AccessToken的有效期从1小时缩短到30分钟,RefreshToken增加了设备绑定,不能跨设备使用。

总结

Authentication Kit让你不用自己搞登录系统,但"不用自己搞"不等于"不用理解"。Token管理、账号绑定冲突、验证码有效期、签名指纹——这些坑你都得知道,不然线上出问题你都不知道怎么查。

核心记住三点:

  • VerifyId必须保存,丢了验证码就白发了
  • 账号绑定冲突要处理,两个不同账号绑同一个手机号,不处理就是事故
  • SHA256指纹别忘了配,调试签名和发布签名都要在控制台配置
评估维度 说明
学习难度 ⭐⭐⭐ 华为账号登录简单,手机号/邮箱认证和账号绑定需要理解
使用频率 ⭐⭐⭐⭐⭐ 几乎所有应用都需要用户认证
重要程度 ⭐⭐⭐⭐⭐ 没有认证,用户数据就没有归属

Authentication Kit不是可选项。你自己搞登录系统,安全性过不了关,体验也跟不上——何必呢?

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。