簡化登入流程,助力應用建立使用者體系

HarmonyOS_SDK發表於2024-08-28

隨著智慧手機和移動應用的普及,使用者需要在不同的應用中註冊和登入賬號,傳統的賬號註冊和登入流程需要使用者輸入使用者名稱和密碼,這不僅繁瑣而且容易造成使用者流失。

華為賬號服務(Account Kit)提供簡單、快速、安全的登入功能,讓使用者快捷地使用華為賬號登入應用。使用者授權後,華為賬號可提供頭像、暱稱、手機號碼等資訊,幫助應用更瞭解使用者。其中一鍵登入功能是基於OAuth 2.0協議標準OpenID Connect協議標準構建的OAuth2.0 授權登入系統,應用可以透過華為賬號一鍵登入能力方便地獲取華為賬號使用者的身份標識和手機號,快速建立應用內的使用者體系。

image

一鍵登入技術透過簡化登入流程,使用者無需記住額外的使用者名稱和密碼,只需點選一下按鈕即可快速登入,省去了填寫登錄檔單和登入表單的繁瑣步驟,提升了使用者體驗,降低了使用者因忘記密碼而不能訪問應用的機率,減少了使用者的流失率。

對於開發者和運營者來說,一鍵登入技術不僅能夠簡化使用者管理和支援流程,還能減少因賬號管理帶來的運營成本和風險。透過整合一鍵登入,開發者可以專注於應用的核心功能開發,提升開發效率和使用者體驗。

能力優勢

應用可以透過華為賬號一鍵登入功能獲取手機號授權並完成登入,幫助應用建立使用者體系或者打通原有的使用者體系。

便捷性:一鍵完成登入和手機號授權,為使用者提供更加便捷易用的登入體驗。

效率高:無需單獨整合SDK,減少開發者開發和運營成本。

登入元件

賬號服務提供了登入按鈕、登入皮膚兩種一鍵登入元件,可滿足應用不同的介面風格。

華為賬號一鍵登入按鈕:應用可以將華為賬號一鍵登入按鈕嵌入自有的登入頁,滿足應用對介面風格一致性和靈活性的要求。

華為賬號一鍵登入皮膚:應用可以直接呼叫華為賬號一鍵登入皮膚,無需自行開發登入頁,簡化開發步驟。

【華為賬號一鍵登入】按鈕

使用者體驗設計

image

登入頁面UX規範設計

image

【華為賬號一鍵登入】皮膚

使用者體驗設計

image

登入頁面UX規範設計

image

開發步驟

客戶端開發

1.匯入authentication模組及相關公共模組。

import { authentication } from '@kit.AccountKit';
import { util } from '@kit.ArkTS';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

2.呼叫authentication模組的AuthorizationWithHuaweiIDRequest請求獲取華為賬號使用者的UnionID、OpenID、匿名手機號。匿名手機號用於登入頁面展示。

getQuickLoginAnonymousPhone() {
  // 建立授權請求,並設定引數。
  let authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
  // 獲取手機號需傳quickLoginAnonymousPhone這個scope,傳參之前需要先申請"華為賬號一鍵登入"許可權,後續才能獲取手機號資料;
  // 獲取UnionID、OpenID需傳openid這個scope,這個scope不需要申請許可權。
  authRequest.scopes = ['quickLoginAnonymousPhone','openid'];
  // 用於防跨站點請求偽造。
  authRequest.state = util.generateRandomUUID();
  // 一鍵登入場景該引數只能設定為false。
  authRequest.forceAuthorization = false;
  let controller = new authentication.AuthenticationController();
  try {
    controller.executeRequest(authRequest).then((response: authentication.AuthorizationWithHuaweiIDResponse) => {
      // 獲取到UnionID、OpenID、匿名手機號
      let unionID = response.data?.unionID;
      let openID = response.data?.openID;
      let anonymousPhone = response.data?.extraInfo?.quickLoginAnonymousPhone;
      if (anonymousPhone) {
        hilog.info(0x0000, 'testTag', 'Succeeded in authentication');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in authentication. AnonymousPhone is empty');
      // 未獲取到匿名手機號需要跳轉到應用自定義的登入頁面。
    }).catch((error: BusinessError) => {
      this.dealAllError(error);
    })
  } catch (error) {
    this.dealAllError(error);
  }
}

// 錯誤處理
dealAllError(error: BusinessError): void {
  hilog.error(0x0000, 'testTag', 'Failed to auth, errorCode=%{public}d, errorMsg=%{public}s', error.code, error.message);
  // 應用需要展示其他登入方式。
}

3.將獲取到的匿名手機號設定給下面示例程式碼中的quickLoginAnonymousPhone變數,呼叫LoginWithHuaweiIDButton元件,實現應用自己的登入頁面,並展示華為賬號一鍵登入按鈕和華為賬號使用者認證協議(Account Kit提供跳轉連結,應用需實現協議跳轉,參見使用與約束第2點),使用者同意協議、點選一鍵登入按鈕後,可獲取到Authorization Code,將該值傳給應用伺服器用於獲取使用者資訊(完整手機號、UnionID)。

import { loginComponentManager, LoginWithHuaweiIDButton } from '@kit.AccountKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

@Entry
@Component
struct PreviewLoginButtonPage {
  // 設定步驟二中獲取到的匿名手機號。
  quickLoginAnonymousPhone: string = '';
  // 展示應用的使用者服務協議、隱私協議和華為賬號使用者認證協議。
  // 華為賬號使用者認證協議跳轉連結:https://privacy.consumer.huawei.com/legal/id/authentication-terms.htm?code=CN&language=zh-CN
  privacyText: loginComponentManager.PrivacyText[] = [{
    text: '已閱讀並同意',
    type: loginComponentManager.TextType.PLAIN_TEXT
  }, {
    text: '《使用者服務協議》 ',
    tag: '使用者服務協議',
    type: loginComponentManager.TextType.RICH_TEXT
  }, {
    text: '《隱私協議》',
    tag: '隱私協議',
    type: loginComponentManager.TextType.RICH_TEXT
  }, {
    text: '和',
    type: loginComponentManager.TextType.PLAIN_TEXT
  }, {
    text: '《華為賬號使用者認證協議》',
    tag: '華為賬號使用者認證協議',
    type: loginComponentManager.TextType.RICH_TEXT
  }];
  // 構造LoginWithHuaweiIDButton元件的控制器。
  controller: loginComponentManager.LoginWithHuaweiIDButtonController =
    new loginComponentManager.LoginWithHuaweiIDButtonController()
      /**
       * 當應用使用自定義的登入頁時,如果使用者未同意協議,需要設定協議狀態為NOT_ACCEPTED,當使用者同意協議後再設定
       * 協議狀態為ACCEPTED,才可以使用華為賬號一鍵登入功能。
       */
      .setAgreementStatus(loginComponentManager.AgreementStatus.NOT_ACCEPTED)
      .onClickLoginWithHuaweiIDButton((error: BusinessError, response: loginComponentManager.HuaweiIDCredential) => {
        if (error) {
          this.dealAllError(error);
          return;
        }
        if (response) {
          // 獲取到Authorization Code
          let authorizationCode = response.authorizationCode;
          hilog.info(0x0000, 'testTag', 'response: %{public}s', JSON.stringify(response));
          return;
        }
      });

  // 錯誤處理
  dealAllError(error: BusinessError): void {
    hilog.error(0x0000, 'testTag', 'Failed to login, errorCode=%{public}d, errorMsg=%{public}s', error.code, error.message);
  }

  build() {
    Scroll() {
      Column() {
        Column() {
          Column() {
            Image($r('app.media.app_icon'))
              .width(48)
              .height(48)
              .draggable(false)
              .copyOption(CopyOptions.None)
              .onComplete(() => {
                hilog.info(0x0000, 'testTag', 'appIcon loading success');
              })
              .onError(() => {
                hilog.error(0x0000, 'testTag', '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,
                // LoginWithHuaweiIDButton的邊框圓角半徑。
                borderRadius: 24,
                // LoginWithHuaweiIDButton支援的登入型別。
                loginType: loginComponentManager.LoginType.QUICK_LOGIN,
                // LoginWithHuaweiIDButton支援按鈕的樣式跟隨系統深淺色模式切換。
                supportDarkMode: true
              },
              controller: this.controller
            })
          }
          .height(40)
          .width('100%')
          .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(0x0000, 'testTag', 'click optionalLoginButton');
            })
          }.margin({ top: 16 })
        }

        Row() {
          Row() {
            Checkbox({ name: 'privacyCheckbox', group: 'privacyCheckboxGroup' })
              .width(24)
              .height(24)
              .focusable(true)
              .focusOnTouch(true)
              .margin({ top: 0 })
              .onChange((value: boolean) => {
                if (value) {
                  this.controller.setAgreementStatus(loginComponentManager.AgreementStatus.ACCEPTED);
                } else {
                  this.controller.setAgreementStatus(loginComponentManager.AgreementStatus.NOT_ACCEPTED);
                }
                hilog.info(0x0000, 'testTag', "agreementChecked " + value);
              })
          }

          Row() {
            Text() {
              ForEach(this.privacyText, (item: loginComponentManager.PrivacyText, index: number) => {
                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(0x0000, 'testTag', 'click privacy text tag:' + item.tag);
                    })
                }
              })
            }
            .width('100%')
          }
          .margin({ left: 12 })
          .layoutWeight(1)
          .constraintSize({ minHeight: 24 })
        }
        .alignItems(VerticalAlign.Top)
        .margin({
          bottom: 16
        })
      }
      .justifyContent(FlexAlign.SpaceBetween)
      .constraintSize({ minHeight: '100%' })
      .margin({
        left: 16,
        right: 16
      })
    }
    .width('100%')
    .height('100%')
  }
}

服務端開發

1.應用伺服器使用Client ID、Client Secret、Authorization Code呼叫獲取憑證Access Token的介面向華為賬號伺服器請求獲取Access Token、Refresh Token。

2.使用Access Token呼叫獲取使用者資訊介面獲取使用者資訊,從使用者資訊中獲取使用者繫結的完整手機號和華為賬號使用者標識UnionID。

3.應用透過獲取到的完整手機號或UnionID查詢該使用者是否已登入應用。如已登入,則繫結獲取的UnionID或手機號到已有使用者上(如已繫結,則可忽略),完成使用者登入;如未登入,則建立新使用者並繫結手機號與UnionID到該使用者上。

瞭解更多詳情>>

訪問華為賬號服務聯盟官網

獲取華為賬號一鍵登入服務開發指導文件

相關文章