【HarmonyOS NEXT】华为一键登录

517 阅读7分钟

华为账号登录

开发前提

  1. 在进行代码开发前,请先确认已完成开发准备工作。

  2. 应用使用华为账号一键登录功能之前,需要完成quickLoginMobilePhone(华为账号一键登录)的scope权限申请,详情参见配置scope权限。scope权限申请审批未完成或未通过,将报错1001502014 应用未申请scopes或permissions权限

    细分场景对应scope权限名称权限描述权限是否需要申请
    华为账号一键登录quickLoginAnonymousPhonequickLoginMobilePhone华为账号一键登录,包含获取完整手机号

客户端开发

IDE辅助开发(推荐)

  1. 打开需要提供一键登录功能的页面,在页面的build()中创建一个容器(如Column)。

  2. 在DevEco Studio菜单栏点击View > Tool Windows > Kit Assistant,或使用快捷键Alt + K,进入Kit Assistant页面。

  3. 在左侧目录中点击选中AccountKit > QuickLoginButton,并拖拽至新创建的容器中。即可在当前位置插入相应的代码片段。

    若代码片段插入失败,可查询快速插入场景化代码片段的说明排查原因。

  4. 在自动生成的代码段的getQuickLoginAnonymousPhone函数中,执行executeRequest函数可获取响应结果。

    根据获取的响应结果判断,可能存在以下场景:

    • 已正确获取到用户身份标识UnionID、OpenID,应用可通过用户身份标识查询该用户是否已关联。

      1)如已关联,结合风控、安全因素及自身业务场景判断,可展示已关联的账号,由用户选择是否使用华为账号登录应用,或免用户操作,静默登录应用,客户端开发结束。

      2)如未关联,再判断是否存在下面的异常场景,如无,则参考下面步骤5继续开发。

    • 存在如下返回ArkTS错误码的异常场景:

      1)返回1001502001 用户未登录华为账号错误码,说明华为账号未登录。

      2)返回1001500003 不支持该scopes或permissions错误码,说明华为账号用户注册地非中国境内(不包含中国香港、中国澳门、中国台湾)。

      3)获取到的匿名手机号为空,说明华为账号没有绑定手机号、权限未申请或未生效。

      上述异常场景应用需要展示其他登录方式。

  5. 根据上述代码实现应用的登录页面,并展示华为账号一键登录按钮和华为账号用户认证协议(Account Kit提供跳转链接,应用需实现协议跳转,参见约束与限制第2点),用户同意协议并点击一键登录按钮后,可获取到Authorization Code,将该值传给应用服务端用于获取用户信息(完整手机号、UnionID、OpenID)。

  6. 源代码 有小伙伴反馈无法拖动 可以复制下面的源代码

    import { util } from '@kit.ArkTS';
    import { authentication, loginComponentManager, LoginWithHuaweiIDButton } from '@kit.AccountKit';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import { BusinessError } from '@kit.BasicServicesKit';
    import { promptAction, router } from '@kit.ArkUI';
    
    @Component
    @Entry
    export struct HuaweiLoginPage {
      build() {
        Column() {
          QuickLoginButtonComponent()
        }.width('100%')
        .height('100%')
      }
    }
    
    @Component
    struct QuickLoginButtonComponent {
      logTag: string = 'QuickLoginButtonComponent';
      domainId: number = 0x0000;
      // 匿名化手机号
      @State quickLoginAnonymousPhone: string = '';
      // 是否勾选协议
      @State isSelected: boolean = false;
      // 华为账号用户认证协议链接,此处仅为示例,实际开发过程中,出于可维护性、安全性等方面考虑,域名不建议硬编码在本地
      private static USER_AUTHENTICATION_PROTOCOL: string =
        'https://privacy.consumer.huawei.com/legal/id/authentication-terms.htm?code=CN&language=zh-CN';
      private static USER_SERVICE_TAG = '用户服务协议';
      private static PRIVACY_TAG = '隐私协议';
      private static USER_AUTHENTICATION_TAG = '华为账号用户认证协议';
      // 定义LoginWithHuaweiIDButton展示的隐私文本,展示应用的用户服务协议、隐私协议和华为账号用户认证协议
      privacyText: loginComponentManager.PrivacyText[] = [{
        text: '已阅读并同意',
        type: loginComponentManager.TextType.PLAIN_TEXT
      }, {
        text: '《用户服务协议》',
        tag: QuickLoginButtonComponent.USER_SERVICE_TAG,
        type: loginComponentManager.TextType.RICH_TEXT
      }, {
        text: '《隐私协议》',
        tag: QuickLoginButtonComponent.PRIVACY_TAG,
        type: loginComponentManager.TextType.RICH_TEXT
      }, {
        text: '和',
        type: loginComponentManager.TextType.PLAIN_TEXT
      }, {
        text: '《华为账号用户认证协议》',
        tag: QuickLoginButtonComponent.USER_AUTHENTICATION_TAG,
        type: loginComponentManager.TextType.RICH_TEXT
      }, {
        text: '。',
        type: loginComponentManager.TextType.PLAIN_TEXT
      }];
      // 构造LoginWithHuaweiIDButton组件的控制器
      controller: loginComponentManager.LoginWithHuaweiIDButtonController =
        new loginComponentManager.LoginWithHuaweiIDButtonController()
          /**
           * 当应用使用自定义的登录页时,如果用户未同意协议,需要设置协议状态为NOT_ACCEPTED,当用户同意协议后再设置
           * 协议状态为ACCEPTED,才可以使用华为账号一键登录功能
           */
          .setAgreementStatus(loginComponentManager.AgreementStatus.NOT_ACCEPTED)
          .onClickLoginWithHuaweiIDButton((error: BusinessError | undefined,
            response: loginComponentManager.HuaweiIDCredential) => {
            this.handleLoginWithHuaweiIDButton(error, response);
          })
          .onClickEvent((error: BusinessError, clickEvent: loginComponentManager.ClickEvent) => {
            if (error) {
              this.dealAllError(error);
              return;
            }
            hilog.info(this.domainId, this.logTag, `onClickEvent clickEvent: ${clickEvent}`);
          });
      agreementDialog: CustomDialogController = new CustomDialogController({
        builder: AgreementDialog({
          privacyText: this.privacyText,
          cancel: () => {
            this.agreementDialog.close();
            this.controller.setAgreementStatus(loginComponentManager.AgreementStatus.NOT_ACCEPTED);
          },
          confirm: () => {
            this.agreementDialog.close();
            this.isSelected = true;
            this.controller.setAgreementStatus(loginComponentManager.AgreementStatus.ACCEPTED);
            // 调用此方法,同意协议与登录一并完成,无需再次点击登录按钮
            this.controller.continueLogin((error: BusinessError) => {
              if (error) {
                hilog.error(this.domainId, this.logTag,
                  `Failed to login with agreementDialog. errCode is ${error.code}, errMessage is ${error.message}`);
              } else {
                hilog.info(this.domainId, this.logTag,
                  'Succeed in clicking agreementDialog continueLogin.');
              }
            });
          },
          clickHyperlinkText: () => {
            this.agreementDialog.close();
            this.jumpToPrivacyWebView();
          }
        }),
        autoCancel: false,
        alignment: DialogAlignment.Center,
      });
    
      aboutToAppear(): void {
        this.getQuickLoginAnonymousPhone();
      }
    
      // Toast提示
      showToast(resource: string) {
        try {
          promptAction.showToast({
            message: resource,
            duration: 2000
          });
        } catch (error) {
          const message = (error as BusinessError).message
          const code = (error as BusinessError).code
          hilog.error(this.domainId, this.logTag, `showToast args  errCode is ${code}, errMessage is ${message}`);
        }
      }
    
      // 跳转华为账号用户认证协议页,该页面需在工程main_pages.json文件配置
      jumpToPrivacyWebView() {
        router.pushUrl({
          // 在工程main_pages.json文件配置跳转页,具体可参考AccountKit开发指南使用华为账号一键登录WebPage示例代码
          url: 'pages/WebPage',
          params: {
            isFromDialog: true,
            url: QuickLoginButtonComponent.USER_AUTHENTICATION_PROTOCOL,
          }
        }, (err) => {
          if (err) {
            hilog.error(this.domainId, this.logTag,
              `Failed to jumpToPrivacyWebView, errCode is ${err.code}, errMessage is ${err.message}`);
          }
        });
      }
    
      handleLoginWithHuaweiIDButton(error: BusinessError | undefined,
        response: loginComponentManager.HuaweiIDCredential) {
        if (error) {
          hilog.error(this.domainId, this.logTag,
            `Failed to login with LoginWithHuaweiIDButton. errCode is ${error.code}, errMessage is ${error.message}`);
          if (error.code === ErrorCode.ERROR_CODE_NETWORK_ERROR) {
            AlertDialog.show(
              {
                message: "网络未连接,请检查网络设置。",
                offset: { dx: 0, dy: -12 },
                alignment: DialogAlignment.Bottom,
                autoCancel: false,
                confirm: {
                  value: "知道了",
                  action: () => {
                  }
                }
              }
            );
          } else if (error.code === ErrorCode.ERROR_CODE_AGREEMENT_STATUS_NOT_ACCEPTED) {
            // 未同意协议,弹出协议弹框,推荐使用该回调方式
            this.agreementDialog.open();
          } else if (error.code === ErrorCode.ERROR_CODE_LOGIN_OUT) {
            // 华为账号未登录提示
            this.showToast("华为账号未登录,请重试");
          } else if (error.code === ErrorCode.ERROR_CODE_NOT_SUPPORTED) {
            // 不支持该scopes或permissions提示
            this.showToast("该scopes或permissions不支持");
          } else {
            // 其他提示系统或服务异常
            this.showToast('服务或网络异常,请稍后重试');
            // TODO: 其他错误码处理,请参考API中的错误码查看详细错误原因
          }
          return;
        }
        try {
          if (this.isSelected) {
            if (response) {
              hilog.info(this.domainId, this.logTag, 'Succeed in clicking LoginWithHuaweiIDButton.');
              // 开发者根据实际业务情况使用以下信息
              const authCode = response.authorizationCode;
              const openID = response.openID;
              const unionID = response.unionID;
              const idToken = response.idToken;
            }
          } else {
            this.agreementDialog.open();
          }
        } catch (err) {
          hilog.error(this.domainId, this.logTag,
            `Failed to login with LoginWithHuaweiIDButton, errCode: ${err.code}, errMessage: ${err.message}`);
          AlertDialog.show(
            {
              message: '服务或网络异常,请稍后重试',
              offset: { dx: 0, dy: -12 },
              alignment: DialogAlignment.Bottom,
              autoCancel: false,
              confirm: {
                value: '知道了',
                action: () => {
                }
              }
            }
          );
        }
      }
    
      // 错误处理
      dealAllError(error: BusinessError): void {
        hilog.error(this.domainId, this.logTag,
          `Failed to login, errorCode is ${error.code}, errorMessage is ${error.message}`);
        // TODO: 错误码处理,请参考API中的错误码根据实际情况处理
      }
    
      getQuickLoginAnonymousPhone() {
        // 创建授权请求,并设置参数
        const authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
        // 获取匿名手机号需传quickLoginAnonymousPhone这个scope,传参之前需要先申请“华为账号一键登录”权限
        //(权限名称为:quickLoginMobilePhone),后续才能获取匿名手机号数据
        authRequest.scopes = ['quickLoginAnonymousPhone'];
        // 用于防跨站点请求伪造
        authRequest.state = util.generateRandomUUID();
        // 一键登录场景该参数只能设置为false
        authRequest.forceAuthorization = false;
        const controller = new authentication.AuthenticationController();
        try {
          controller.executeRequest(authRequest).then((response: authentication.AuthorizationWithHuaweiIDResponse) => {
            // 获取到UnionID、OpenID、匿名手机号
            const unionID = response.data?.unionID;
            const openID = response.data?.openID;
            const anonymousPhone = response.data?.extraInfo?.quickLoginAnonymousPhone as string;
            if (anonymousPhone) {
              hilog.info(this.domainId, this.logTag, 'Succeeded in authentication.');
              this.quickLoginAnonymousPhone = anonymousPhone;
              return;
            }
            hilog.info(this.domainId, this.logTag, 'Succeeded in authentication. AnonymousPhone is empty.');
            // 未获取到匿名手机号需要跳转到应用自定义的登录页面
          }).catch((error: BusinessError) => {
            this.dealAllError(error);
          })
        } catch (error) {
          this.dealAllError(error);
        }
      }
    
      build() {
        Scroll() {
          Column() {
            Column() {
              Column() {
                Image($r('app.media.app_icon'))
                  .width(48)
                  .height(48)
                  .draggable(false)
                  .copyOption(CopyOptions.None)
                  .onComplete(() => {
                    hilog.info(this.domainId, this.logTag, 'appIcon loading success.');
                  })
                  .onError(() => {
                    hilog.error(this.domainId, this.logTag, 'appIcon loading fail.');
                  })
    
                Text($r('app.string.app_name'))
                  .fontFamily($r('sys.string.ohos_id_text_font_family_medium'))
                  .fontWeight(FontWeight.Medium)
                  .fontWeight(FontWeight.Bold)
                  .maxFontSize($r('sys.float.ohos_id_text_size_headline8'))
                  .minFontSize($r('sys.float.ohos_id_text_size_body1'))
                  .maxLines(1)
                  .fontColor($r('sys.color.ohos_id_color_text_primary'))
                  .constraintSize({ maxWidth: '100%' })
                  .margin({
                    top: 12,
                  })
    
                Text('应用描述')
                  .fontSize($r('sys.float.ohos_id_text_size_body2'))
                  .fontColor($r('sys.color.ohos_id_color_text_secondary'))
                  .fontFamily($r('sys.string.ohos_id_text_font_family_regular'))
                  .fontWeight(FontWeight.Regular)
                  .constraintSize({ maxWidth: '100%' })
                  .margin({
                    top: 8,
                  })
              }.margin({
                top: 100
              })
    
              Column() {
                Text(this.quickLoginAnonymousPhone)
                  .fontSize(36)
                  .fontColor($r('sys.color.ohos_id_color_text_primary'))
                  .fontFamily($r('sys.string.ohos_id_text_font_family_medium'))
                  .fontWeight(FontWeight.Bold)
                  .lineHeight(48)
                  .textAlign(TextAlign.Center)
                  .maxLines(1)
                  .constraintSize({ maxWidth: '100%', minHeight: 48 })
    
                Text('华为账号绑定号码')
                  .fontSize($r('sys.float.ohos_id_text_size_body2'))
                  .fontColor($r('sys.color.ohos_id_color_text_secondary'))
                  .fontFamily($r('sys.string.ohos_id_text_font_family_regular'))
                  .fontWeight(FontWeight.Regular)
                  .lineHeight(19)
                  .textAlign(TextAlign.Center)
                  .maxLines(1)
                  .constraintSize({ maxWidth: '100%' })
                  .margin({
                    top: 8
                  })
              }.margin({
                top: 64
              })
    
              Column() {
                LoginWithHuaweiIDButton({
                  params: {
                    // LoginWithHuaweiIDButton支持的样式
                    style: loginComponentManager.Style.BUTTON_RED,
                    // 账号登录按钮在登录过程中展示加载态
                    extraStyle: {
                      buttonStyle: new loginComponentManager.ButtonStyle().loadingStyle({
                        show: true
                      })
                    },
                    // LoginWithHuaweiIDButton的边框圆角半径
                    borderRadius: 24,
                    // LoginWithHuaweiIDButton支持的登录类型
                    loginType: loginComponentManager.LoginType.QUICK_LOGIN,
                    // LoginWithHuaweiIDButton支持按钮的样式跟随系统深浅色模式切换
                    supportDarkMode: true,
                    // verifyPhoneNumber:如果华为账号用户在过去90天内未进行短信验证,是否拉起Account Kit提供的短信验证码页面
                    verifyPhoneNumber: true
                  },
                  controller: this.controller
                })
              }
              .height(40)
              .margin({
                top: 56
              })
    
              Column() {
                Button({
                  type: ButtonType.Capsule,
                  stateEffect: true
                }) {
                  Text('其他方式登录')
                    .fontColor($r('sys.color.ohos_id_color_text_primary_activated'))
                    .fontFamily($r('sys.string.ohos_id_text_font_family_medium'))
                    .fontWeight(FontWeight.Medium)
                    .fontSize($r('sys.float.ohos_id_text_size_button1'))
                    .focusable(true)
                    .focusOnTouch(true)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                    .maxLines(1)
                    .padding({ left: 8, right: 8 })
                }
                .fontColor($r('sys.color.ohos_id_color_text_primary_activated'))
                .fontFamily($r('sys.string.ohos_id_text_font_family_medium'))
                .fontWeight(FontWeight.Medium)
                .backgroundColor($r('sys.color.ohos_id_color_button_normal'))
                .focusable(true)
                .focusOnTouch(true)
                .constraintSize({ minHeight: 40 })
                .width('100%')
                .onClick(() => {
                  hilog.info(this.domainId, this.logTag, 'click optionalLoginButton.');
                })
              }.margin({ top: 16 })
            }.width('100%')
    
            Row() {
              Row() {
                Checkbox({ name: 'privacyCheckbox', group: 'privacyCheckboxGroup' })
                  .width(24)
                  .height(24)
                  .focusable(true)
                  .focusOnTouch(true)
                  .margin({ top: 0 })
                  .select(this.isSelected)
                  .onChange((value: boolean) => {
                    if (value) {
                      this.isSelected = true;
                      this.controller.setAgreementStatus(loginComponentManager.AgreementStatus.ACCEPTED);
                    } else {
                      this.isSelected = false;
                      this.controller.setAgreementStatus(loginComponentManager.AgreementStatus.NOT_ACCEPTED);
                    }
                    hilog.info(this.domainId, this.logTag, `agreementChecked: ${value}`);
                  })
              }
    
              Row() {
                Text() {
                  ForEach(this.privacyText, (item: loginComponentManager.PrivacyText) => {
                    if (item?.type == loginComponentManager.TextType.PLAIN_TEXT && item?.text) {
                      Span(item?.text)
                        .fontColor($r('sys.color.ohos_id_color_text_secondary'))
                        .fontFamily($r('sys.string.ohos_id_text_font_family_regular'))
                        .fontWeight(FontWeight.Regular)
                        .fontSize($r('sys.float.ohos_id_text_size_body3'))
                    } else if (item?.type == loginComponentManager.TextType.RICH_TEXT && item?.text) {
                      Span(item?.text)
                        .fontColor($r('sys.color.ohos_id_color_text_primary_activated'))
                        .fontFamily($r('sys.string.ohos_id_text_font_family_medium'))
                        .fontWeight(FontWeight.Medium)
                        .fontSize($r('sys.float.ohos_id_text_size_body3'))
                        .focusable(true)
                        .focusOnTouch(true)
                        .onClick(() => {
                          // 应用需要根据item.tag实现协议页面的跳转逻辑
                          hilog.info(this.domainId, this.logTag, `click privacy text tag: ${item.tag}`);
                          // 华为账号用户认证协议
                          if (item.tag === QuickLoginButtonComponent.USER_AUTHENTICATION_TAG) {
                            this.jumpToPrivacyWebView();
                          }
                        })
                    }
                  }, (item: loginComponentManager.PrivacyText, index: number) => { return item?.tag + index.toString(); })
                }
                .width('100%')
              }
              .margin({ left: 12 })
              .layoutWeight(1)
              .constraintSize({ minHeight: 24 })
            }
            .alignItems(VerticalAlign.Top)
            .margin({
              bottom: 16,
              top: 16
            })
          }
          .justifyContent(FlexAlign.SpaceBetween)
          .constraintSize({ minHeight: '100%' })
          .margin({
            left: 16,
            right: 16
          })
        }
        .width('100%')
        .height('100%')
      }
    }
    @CustomDialog
    export struct AgreementDialog {
      logTag: string = 'AgreementDialog';
      domainId: number = 0x0000;
      dialogController?: CustomDialogController;
      cancel: () => void = () => {
      };
      confirm: () => void = () => {
      };
      clickHyperlinkText: () => void = () => {
      };
      privacyText: loginComponentManager.PrivacyText[] = [];
      private static USER_AUTHENTICATION_TAG = '华为账号用户认证协议';
    
      build() {
        Column() {
          Row() {
            Text('用户协议与隐私条款')
              .id('loginPanel_agreement_dialog_privacy_title')
              .maxFontSize($r('sys.float.ohos_id_text_size_headline8'))
              .minFontSize($r('sys.float.ohos_id_text_size_body1'))
              .fontColor($r('sys.color.ohos_id_color_text_primary'))
              .fontFamily($r('sys.string.ohos_id_text_font_family_medium'))
              .fontWeight(FontWeight.Bold)
              .textAlign(TextAlign.Center)
              .textOverflow({ overflow: TextOverflow.Ellipsis })
              .maxLines(2)
          }
          .alignItems(VerticalAlign.Center)
          .constraintSize({ minHeight: 56, maxWidth: 400 })
          .margin({
            left: $r('sys.float.ohos_id_max_padding_start'),
            right: $r('sys.float.ohos_id_max_padding_start')
          })
    
          Row() {
            Text() {
              ForEach(this.privacyText, (item: loginComponentManager.PrivacyText) => {
                if (item?.type == loginComponentManager.TextType.PLAIN_TEXT && item?.text) {
                  Span(item?.text)
                    .fontSize($r('sys.float.ohos_id_text_size_body1'))
                    .fontColor($r('sys.color.ohos_id_color_text_primary'))
                    .fontFamily($r('sys.string.ohos_id_text_font_family_regular'))
                    .fontWeight(FontWeight.Regular)
                } else if (item?.type == loginComponentManager.TextType.RICH_TEXT && item?.text) {
                  Span(item?.text)
                    .fontSize($r('sys.float.ohos_id_text_size_body1'))
                    .fontColor('#CE0E2D')
                    .fontFamily($r('sys.string.ohos_id_text_font_family_medium'))
                    .fontWeight(FontWeight.Medium)
                    .focusable(true)
                    .focusOnTouch(true)
                    .onClick(() => {
                      // 应用需要根据item.tag实现协议页面的跳转逻辑
                      hilog.info(this.domainId, this.logTag, `click privacy text tag: ${item.tag}`);
                      // 华为账号用户认证协议
                      if (item.tag === AgreementDialog.USER_AUTHENTICATION_TAG) {
                        hilog.info(this.domainId, this.logTag, 'AgreementDialog click.');
                        this.clickHyperlinkText();
                      }
                    })
                }
              }, (item: loginComponentManager.PrivacyText, index: number) => { return item?.tag + index.toString(); })
            }
            .width('100%')
            .textOverflow({ overflow: TextOverflow.Ellipsis })
            .maxLines(10)
            .textAlign(TextAlign.Start)
            .focusable(true)
            .focusOnTouch(true)
            .padding({ left: 24, right: 24 })
          }.width('100%')
    
          Flex({
            direction: FlexDirection.Row
          }) {
            Button('取消',
              { type: ButtonType.Capsule, stateEffect: true })
              .id('loginPanel_agreement_cancel_btn')
              .fontColor($r('sys.color.ohos_id_color_text_primary'))
              .fontSize($r('sys.float.ohos_id_text_size_button1'))
              .fontFamily($r('sys.string.ohos_id_text_font_family_medium'))
              .backgroundColor(Color.Transparent)
              .fontWeight(FontWeight.Medium)
              .focusable(true)
              .focusOnTouch(true)
              .constraintSize({ minHeight: 40, maxWidth: 400 })
              .width('50%')
              .onClick(() => {
                hilog.info(this.domainId, this.logTag, 'AgreementDialog cancel.');
                this.cancel();
              })
    
            Button('同意并登录',
              { type: ButtonType.Capsule, stateEffect: true })
              .id('loginPanel_agreement_dialog_huawei_id_login_btn')
              .fontColor(Color.White)
              .backgroundColor('#CE0E2D')
              .fontSize($r('sys.float.ohos_id_text_size_button1'))
              .fontFamily($r('sys.string.ohos_id_text_font_family_medium'))
              .fontWeight(FontWeight.Medium)
              .focusable(true)
              .focusOnTouch(true)
              .constraintSize({ minHeight: 40, maxWidth: 400 })
              .width('50%')
              .onClick(() => {
                hilog.info(this.domainId, this.logTag, 'AgreementDialog confirm.');
                this.confirm();
              })
          }
          .margin({
            top: 8,
            left: $r('sys.float.ohos_id_elements_margin_horizontal_l'),
            right: $r('sys.float.ohos_id_elements_margin_horizontal_l'),
            bottom: 16
          })
        }.backgroundColor($r('sys.color.ohos_id_color_dialog_default_bg'))
        .padding({
          left: 16,
          right: 16
        })
      }
    }
    export enum ErrorCode {
      // 账号未登录
      ERROR_CODE_LOGIN_OUT = 1001502001,
      // 该账号不支持一键登录,如儿童账号、海外账号
      ERROR_CODE_NOT_SUPPORTED = 1001500003,
      // 网络错误
      ERROR_CODE_NETWORK_ERROR = 1001502005,
      // 用户未同意用户协议
      ERROR_CODE_AGREEMENT_STATUS_NOT_ACCEPTED = 1005300001
    }
    

自行开发

  1. 导入Account Kit的authentication模块及相关公共模块。
 import { util } from '@kit.ArkTS';
 import { hilog } from '@kit.PerformanceAnalysisKit';
 import { BusinessError } from '@kit.BasicServicesKit';
  1. 调用authentication模块的AuthorizationWithHuaweiIDRequest请求获取华为账号用户的UnionID、OpenID、匿名手机号。匿名手机号用于登录页面展示。

    注意

    该场景下forceAuthorization参数需设置为false。

    根据获取的响应结果判断,可能存在以下场景:

    • 已正确获取到用户身份标识UnionID、OpenID,应用可通过用户身份标识查询该用户是否已关联。

      1)如已关联,结合风控、安全因素及自身业务场景判断,可展示已关联的账号,由用户选择是否使用华为账号登录应用,或免用户操作,静默登录应用,客户端开发结束。

      2)如未关联,再判断是否存在下面的异常场景,如无,则参考下面步骤3继续开发。

    • 存在如下返回ArkTS错误码的异常场景:

      1)返回1001502001 用户未登录华为账号错误码,说明华为账号未登录。

      2)返回1001500003 不支持该scopes或permissions错误码,说明华为账号用户注册地非中国境内(不包含中国香港、中国澳门、中国台湾)。

      3)获取到的匿名手机号为空,说明华为账号没有绑定手机号、权限未申请或未生效或者开发者开启了代码混淆,quickLoginAnonymousPhone(匿名手机号)属性需要配置混淆白名单防止被混淆。

      上述异常场景应用需要展示其他登录方式。

  2. 将获取到的匿名手机号设置给下面QuickLoginButtonComponent组件示例代码中的quickLoginAnonymousPhone变量,调用LoginWithHuaweiIDButton组件,实现应用自己的登录页面,并展示华为账号一键登录按钮和华为账号用户认证协议(Account Kit提供跳转链接,应用需实现协议跳转,参见约束与限制第2点),用户同意协议并点击一键登录按钮后,可获取到Authorization Code,将该值传给应用服务端用于获取用户信息(完整手机号、UnionID、OpenID)。 代码地址链接