Weryfikacja SMS w aplikacji Android

Wyobraź sobie aplikację, w której potrzebujesz potwierdzić rejestrację lub numer telefonu za pomocą SMS-a. Wydaje się dość skomplikowane, ale to tylko pozory. Dziś na warsztat weżniemy właśnie ten temat. Weryfikacja za pomocą SMS-a jest popularna wśród różnych serwisów czy aplikacji mobilnych. Jest to również mechanizm bezpieczeństwa, na przykład w aplikacjach bankowych.

Jak to działa?

  1. Musisz pobrać nr telefonu od użytkownika.
  2. Zarejestrować usługę, która będzie nadsłuchiwała na przyjście SMS-a.
  3. Przesłać numer telefonu na serwer OTP, który wyśle SMS-a.
  4. Wysłać SMS-a z poufnym kodem.
  5. Odebrać SMS-a na telefonie.
  6. Wysłać kod z SMS-a + nr tel na serwer. w celu weryfikacji.
  7. Wykonać odpowiednią akcję w aplikacji po weryfikacji.
  8. Wyrejestrować usługę, która nadsłuchuje.

Wydaje się dość skomplikowane, w kodzie jest jeszcze prościej….

Weryfikacja sms w Androdzie

Jeżeli zdobyłeś numer telefonu to teraz musisz zarejestrować SMS retriever w następujący sposób:

SmsRetrieverClient client = SmsRetriever.getClient(this );
Task<Void> task = client.startSmsRetriever();
task.addOnSuccessListener(new OnSuccessListener<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
        Log.d(TAG, "SMS Retriever starts");
    }
});
task.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        Log.d(TAG, "SMS Retriever error" + e);
    }
});

Następnie musimy zarejestrować broadcast, który będzie nadsłuchiwał na przyjście wiadomości:

mySMSBroadcastReceiver = new MySMSBroadcastReceiver();
mySMSBroadcastReceiver.setOTPListener(this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(SmsRetriever.SMS_RETRIEVED_ACTION);
this.registerReceiver(mySMSBroadcastReceiver, intentFilter);

Przykładowa klasa MySMSBroadcastReceiver może wyglądać następująco:

public class MySMSBroadcastReceiver extends BroadcastReceiver {
    private OTPReceiveListener OTPReceiveListener;
    @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:
                    String message = (String) extras.get(SmsRetriever.EXTRA_SMS_MESSAGE);
                    if (OTPReceiveListener != null) {
                        String str = message.replaceAll("\\D+", "");
                        OTPReceiveListener.onOTPReceived(str);
                    }
                    break;
                case CommonStatusCodes.TIMEOUT:
                    if (OTPReceiveListener != null)
                        OTPReceiveListener.onOTPTimeOut("SMS retriever API Timeout");
                    break;
            }
        }
    }
    public void setOTPListener(OTPReceiveListener OTPReceiveListener) {
        this.OTPReceiveListener = OTPReceiveListener;
    }
    interface OTPReceiveListener {
        void onOTPReceived(String code);
        void onOTPTimeOut(String text);
    }
}

Jeszcze implementacja interfejsu:

@Override
public void onOTPReceived(String code) {
    if (mySMSBroadcastReceiver != null)
       this.unregisterReceiver(mySMSBroadcastReceiver);
    Log.e(TAG,"OTP Received code: "+ code);
}
@Override
public void onOTPTimeOut(String text) {
    Log.e(TAG, text);
}

Kod po stronie aplikacji można powiedzieć, że mamy gotowy. Teraz trzeba wysłać nr telefonu na serwer i przygotować odpowiedniego SMS-a. Treść wiadomości powinna spełniać następujące warunki:

    • Początek wiadomości musi zawierać <#> lub \u200b\u200b. Pierwszy to widoczny przedrostek, który pojawi się w treści wiadomości, a drugi jest niewidocznym łańcuchem Unicode, ale wymaga obsługi standardu Unicode.

 

    • Treść wiadomości nie może przekraczać 140 bajtów.

 

    • Zawiera kod jednorazowy.

 

  • I specjalny 11-znakowy skrót dla twojej aplikacji. Ten hash może być wygenerowany przez tę klasę lub w ten sposób.
  • Reszta treści jest opcjonalna.

Przykładowa treść może wyglądać następująco:

<#> Your code is: 58628 NcZLqx+vXTu

Android Oreo na ratunek

Powyższa metoda działa. Niestety nie jest do końca bezpieczna, ponieważ aplikację, które mają dostęp do czytania SMS-a mogą odczytać kod weryfikacyjny. W Androidzie O wprowadzono metodę createAppSpecificSmsToken, która generuje specjalny token, który działa do przyjścia SMS-a. Po odebraniu wiadomości tylko aplikacja zareaguje na tą wiadomość. Inne aplikacje nie dowiedzą się, że przyszedł SMS.

@TargetApi(Build.VERSION_CODES.O)
private void appSmsToken(){
    SmsManager smsManager = SmsManager.getDefault();
    Intent intent = new Intent(this, SmsTokenResultActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 1234, intent,0);
    String appSmsToken = smsManager.createAppSpecificSmsToken(pendingIntent);
    Log.i(TAG, "sms token " + appSmsToken);
}

Kod zadziała nawet po zamknięciu aplikacji. Gdy wygenerujesz specjalny token wysyłać go do serwera OTP, który następnie wysyła SMS-a z tyn tokenem. Gdy nadejdzie wiadomość, system Android wywoła klasę SmsTokenResultActivity. W niej możesz odebrać wiadomość w taki sposób:

for (SmsMessage pdu : Telephony.Sms.Intents.getMessagesFromIntent(getIntent())) {
    Log.d(TAG, pdu.getDisplayMessageBody());
}

Podsumowanie

Weryfikacja SMS w systemie Android jest prosta i skuteczna. Nie wymaga uprawnień SMS, a to jest duży plus. Jedynie co potrzebujesz to telefon z Androidem z usługą Google Play Services.

Co dalej?

  • Polub stronę MYENV na Facebooku oraz śledź mnie na Twitterze
  • Zachęcam do komentowania i pisania propozycji tematów, o których chcesz przeczytać
  • Poleć ten wpis za pomocą poniższych przycisków. Będę Ci za to bardzo wdzięczny 🙂
  • Życzę Ci miłego dnia i miłego kodowania 🙂

Nie zapomnij przeczytać: