在 Android 應用中請求短信驗證

要自動驗證電話號碼,您必須同時實現驗證流程的客戶端和服務器部分。本文檔描述瞭如何在 Android 應用中實現客戶端部分。

要在 Android 應用程序中啟動電話號碼驗證流程,請將電話號碼發送到驗證服務器並調用 SMS Retriever API 以開始偵聽包含應用程序一次性代碼的 SMS 消息。收到消息後,您將一次性代碼發送回您的服務器以完成驗證過程。

在你開始之前

要準備您的應用程序,請完成以下部分中的步驟。

應用先決條件

確保您的應用的構建文件使用以下值:

  • minSdkVersion 為 19 或更高
  • 一個 compileSdkVersion 為 28 或更高

配置您的應用

在項目級的build.gradle文件,包括谷歌的Maven倉庫Maven的中央存儲庫在這兩個你buildscriptallprojects部分:

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

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

加入谷歌Play服務的依賴性為SMS獵犬API到你的模塊的搖籃構建文件,這是常用app/build.gradle

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

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
    // ...
  }
});

SMS 檢索任務將偵聽最多五分鐘的 SMS 消息,該消息包含用於標識您的應用程序的唯一字符串。

3. 將電話號碼發送到您的服務器

在您獲得用戶的電話號碼並開始偵聽 SMS 消息後,使用任何方法(通常使用 HTTPS POST 請求)將用戶的電話號碼發送到您的驗證服務器。

您的服務器會生成一條驗證消息,並通過 SMS 將其發送到您指定的電話號碼。請參見在服務器上執行短信驗證

4. 接收驗證消息

當用戶的設備上收到確認郵件,播放服務明確廣播對您的應用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;
      }
    }
  }
}

註冊此BroadcastReceiver與意圖過濾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在應用的常數) AndroidManifest.xml文件,如在以下示例中,或者動態使用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 for Passwords 保存電話號碼

或者,在用戶驗證其電話號碼後,您可以提示用戶使用 Smart Lock for Passwords 保存此電話號碼帳戶,以便它可以在其他應用程序和其他設備上自動使用,而無需再次鍵入或選擇電話號碼:

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)