透過 Android 應用程式要求簡訊驗證

如要自動驗證電話號碼,您必須同時實作驗證流程的用戶端和伺服器部分。本文件說明如何在 Android 應用程式中實作用戶端部分。

如要在 Android 應用程式中啟動電話號碼驗證流程,請將電話號碼傳送至驗證伺服器,並呼叫 SMS Retriever API 開始監聽含有應用程式的一次性驗證碼簡訊。收到簡訊後,請將一次性代碼傳回伺服器,完成驗證程序。

事前準備

如要讓應用程式做好準備,請完成下列各節的步驟。

應用程式必要條件

請確認應用程式的版本檔案使用下列的值:

  • 19 以上版本的 minSdkVersion
  • 28 以上版本的 compileSdkVersion

設定應用程式

在專案層級的 build.gradle 檔案中,同時在 buildscriptallprojects 區段中加入 Google Maven 存放區Maven 中央存放區

buildscript {
    repositories {
        google()
        mavenCentral()
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

將 SMS Retriever API 的 Google Play 服務依附元件,新增至模組的 Gradle 版本檔案,通常為 app/build.gradle

dependencies {
  implementation 'com.google.android.gms:play-services-auth:21.0.0'
  implementation 'com.google.android.gms:play-services-auth-api-phone:18.0.2'
}

1. 取得使用者的電話號碼

您可以根據應用程式需求,以任何方式取得使用者的電話號碼。通常,使用提示挑選器即可提示使用者選擇裝置上儲存的電話號碼,因此不必手動輸入電話號碼,是最佳的使用者體驗。如何使用提示挑選器:

// Construct a request for phone numbers and show the picker
private void requestHint() {
    HintRequest hintRequest = new HintRequest.Builder()
           .setPhoneNumberIdentifierSupported(true)
           .build();

    PendingIntent intent = Auth.CredentialsApi.getHintPickerIntent(
            apiClient, hintRequest);
    startIntentSenderForResult(intent.getIntentSender(),
            RESOLVE_HINT, null, 0, 0, 0);
}

// Obtain the phone number from the result
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (requestCode == RESOLVE_HINT) {
      if (resultCode == RESULT_OK) {
          Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
          // credential.getId();  <-- will need to process phone number string
      }
  }
}

2. 啟動簡訊擷取程式

準備好驗證使用者的電話號碼時,請取得 SmsRetrieverClient 物件的執行個體、呼叫 startSmsRetriever,並將成功和失敗事件監聽器附加到簡訊擷取工作:

// Get an instance of SmsRetrieverClient, used to start listening for a matching
// SMS message.
SmsRetrieverClient client = SmsRetriever.getClient(this /* context */);

// Starts SmsRetriever, which waits for ONE matching SMS message until timeout
// (5 minutes). The matching SMS message will be sent via a Broadcast Intent with
// action SmsRetriever#SMS_RETRIEVED_ACTION.
Task<Void> task = client.startSmsRetriever();

// Listen for success/failure of the start Task. If in a background thread, this
// can be made blocking using Tasks.await(task, [timeout]);
task.addOnSuccessListener(new OnSuccessListener<Void>() {
  @Override
  public void onSuccess(Void aVoid) {
    // Successfully started retriever, expect broadcast intent
    // ...
  }
});

task.addOnFailureListener(new OnFailureListener() {
  @Override
  public void onFailure(@NonNull Exception e) {
    // Failed to start retriever, inspect Exception for more details
    // ...
  }
});

簡訊擷取工作最多會監聽五分鐘的簡訊,其中一則包含應用程式的專屬字串。

3. 將電話號碼傳送至伺服器

取得使用者的電話號碼並開始監聽簡訊後,請使用任何方式 (通常具有 HTTPS POST 要求) 將使用者的電話號碼傳送至驗證伺服器。

您的伺服器會產生驗證訊息,並透過簡訊傳送至您指定的電話號碼。請參閱「在伺服器上執行簡訊驗證」。

4. 接收驗證訊息

在使用者的裝置上收到驗證訊息時,Play 服務會明確向應用程式廣播 SmsRetriever.SMS_RETRIEVED_ACTION 意圖,其中含有訊息文字。請使用 BroadcastReceiver 接收這則驗證訊息。

BroadcastReceiveronReceive 處理常式中,從意圖的額外項目取得驗證訊息的文字:

/**
 * BroadcastReceiver to wait for SMS messages. This can be registered either
 * in the AndroidManifest or at runtime.  Should filter Intents on
 * SmsRetriever.SMS_RETRIEVED_ACTION.
 */
public class MySMSBroadcastReceiver extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
    if (SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {
      Bundle extras = intent.getExtras();
      Status status = (Status) extras.get(SmsRetriever.EXTRA_STATUS);

      switch(status.getStatusCode()) {
        case CommonStatusCodes.SUCCESS:
          // Get SMS message contents
          String message = (String) extras.get(SmsRetriever.EXTRA_SMS_MESSAGE);
          // Extract one-time code from the message and complete verification
          // by sending the code back to your server.
          break;
        case CommonStatusCodes.TIMEOUT:
          // Waiting for SMS timed out (5 minutes)
          // Handle the error ...
          break;
      }
    }
  }
}

透過在應用程式的 AndroidManifest.xml 檔案中,使用意圖篩選器 com.google.android.gms.auth.api.phone.SMS_RETRIEVED (SmsRetriever.SMS_RETRIEVED_ACTION 常數的值) 和權限 com.google.android.gms.auth.api.phone.permission.SEND (SmsRetriever.SEND_PERMISSION 常數的值) 註冊此 BroadcastReceiver,如以下範例所示,或者動態使用 Context.registerReceiver

<receiver android:name=".MySMSBroadcastReceiver" android:exported="true"
          android:permission="com.google.android.gms.auth.api.phone.permission.SEND">
    <intent-filter>
        <action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>
    </intent-filter>
</receiver>

5. 將驗證訊息中的一次性驗證碼傳送至您的伺服器

您現在已擁有驗證訊息的文字,請使用規則運算式或其他邏輯,從訊息中取得一次性代碼。一次性程式碼的格式取決於您在伺服器中的實作方式。

最後,透過安全連線將一次性驗證碼傳送到您的伺服器。當您的伺服器收到動態代碼時,就會記錄電話號碼已通過驗證。

選用:使用密碼專用 Smart Lock 儲存電話號碼

或者,在使用者驗證電話號碼後,您也可以提示使用者使用密碼專用 Smart Lock 儲存此電話號碼帳戶,讓該帳戶自動用於其他應用程式和其他裝置,而無需再次輸入或選取電話號碼:

Credential credential = new Credential.Builder(phoneNumberString)
        .setAccountType("https://signin.example.com")  // a URL specific to the app
        .setName(displayName)  // optional: a display name if available
        .build();
Auth.CredentialsApi.save(apiClient, credential).setResultCallback(
            new ResultCallback() {
                public void onResult(Result result) {
                    Status status = result.getStatus();
                    if (status.isSuccess()) {
                        Log.d(TAG, "SAVE: OK");  // already saved
                    } else if (status.hasResolution()) {
                        // Prompt the user to save
                        status.startResolutionForResult(this, RC_SAVE);
                    }
                }
            });

然後,在使用者重新安裝應用程式或在新裝置上安裝應用程式時,您可以擷取已儲存的電話號碼,不必再次要求使用者提供電話號碼:

// On the next install, retrieve the phone number
mCredentialRequest = new CredentialRequest.Builder()
    .setAccountTypes("https://signin.example.com")  // the URL specific to the developer
    .build();
Auth.CredentialsApi.request(apiClient, mCredentialRequest).setResultCallback(
    new ResultCallback<CredentialRequestResult>() {
        public void onResult(CredentialRequestResult credentialRequestResult) {
            if (credentialRequestResult.getStatus().isSuccess()) {
                credentialRequestResult.getCredential().getId();  // this is the phone number
            }
        }
    });

// Then, initiate verification and sign the user in (same as original verification logic)