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?
- Musisz pobrać nr telefonu od użytkownika.
- Zarejestrować usługę, która będzie nadsłuchiwała na przyjście SMS-a.
- Przesłać numer telefonu na serwer OTP, który wyśle SMS-a.
- Wysłać SMS-a z poufnym kodem.
- Odebrać SMS-a na telefonie.
- Wysłać kod z SMS-a + nr tel na serwer. w celu weryfikacji.
- Wykonać odpowiednią akcję w aplikacji po weryfikacji.
- 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.