Я работаю над приложением для Android, в котором сервер отправляет OTP, и пользователю необходимо ввести этот OTP в приложении, чтобы зарегистрироваться для моего приложения. Я хочу, чтобы мое приложение могло автоматически читать OTP, отправленный сервером. Как я могу этого добиться? Любая помощь или руководство в этом отношении будут высоко оценены.
OTP (токен) должен автоматически считываться из сообщения
Ответы (9)
Я рекомендую вам не использовать какие-либо сторонние библиотеки для автоматической загрузки OTP из почтового ящика SMS. Это можно сделать легко, если у вас есть базовые знания о Broadcast Receiver и о том, как он работает. Просто попробуйте следующий подход:
Шаг 1) Создайте единый интерфейс, например SmsListner
package com.wnrcorp.reba;
public interface SmsListener{
public void messageReceived(String messageText);}
Шаг 2) Создайте один приемник широковещательной рассылки, т. е. SmsReceiver
package com.wnrcorp.reba;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
public class SmsReceiver extends BroadcastReceiver {
private static SmsListener mListener;
Boolean b;
String abcd,xyz;
@Override
public void onReceive(Context context, Intent intent) {
Bundle data = intent.getExtras();
Object[] pdus = (Object[]) data.get("pdus");
for(int i=0;i<pdus.length;i++){
SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pdus[i]);
String sender = smsMessage.getDisplayOriginatingAddress();
// b=sender.endsWith("WNRCRP"); //Just to fetch otp sent from WNRCRP
String messageBody = smsMessage.getMessageBody();
abcd=messageBody.replaceAll("[^0-9]",""); // here abcd contains otp
which is in number format
//Pass on the text to our listener.
if(b==true) {
mListener.messageReceived(abcd); // attach value to interface
object
}
else
{
}
}
}
public static void bindListener(SmsListener listener) {
mListener = listener;
}
}
Шаг 3) Добавьте прослушиватель, т. е. широковещательный приемник, в файл манифеста Android
<receiver android:name=".SmsReceiver">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
и добавить разрешение
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
Последний шаг 4) Действие, при котором вы собираетесь автоматически получать otp, когда он получен в папке «Входящие». В моем случае я получаю otp и устанавливаю его в поле edittext.
public class OtpVerificationActivity extends AppCompatActivity {
EditText ed;
TextView tv;
String otp_generated,contactNo,id1;
GlobalData gd = new GlobalData();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_otp_verification);
ed=(EditText)findViewById(R.id.otp);
tv=(TextView) findViewById(R.id.verify_otp);
/*This is important because this will be called every time you receive
any sms */
SmsReceiver.bindListener(new SmsListener() {
@Override
public void messageReceived(String messageText) {
ed.setText(messageText);
}
});
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try
{
InputMethodManager imm=
(InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),0);
}
catch(Exception e)
{}
if (ed.getText().toString().equals(otp_generated))
{
Toast.makeText(OtpVerificationActivity.this, "OTP Verified
Successfully !", Toast.LENGTH_SHORT).show();
}
});
}
}
Файл макета для OtpVerificationActivity
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_otp_verification"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.wnrcorp.reba.OtpVerificationActivity">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/firstcard"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
card_view:cardCornerRadius="10dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/white">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OTP Confirmation"
android:textSize="18sp"
android:textStyle="bold"
android:id="@+id/dialogTitle"
android:layout_margin="5dp"
android:layout_gravity="center"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/otp"
android:layout_margin="5dp"
android:hint="OTP Here"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Verify"
android:textSize="18sp"
android:id="@+id/verify_otp"
android:gravity="center"
android:padding="10dp"
android:layout_gravity="center"
android:visibility="visible"
android:layout_margin="5dp"
android:background="@color/colorPrimary"
android:textColor="#ffffff"
/>
</LinearLayout>
</android.support.v7.widget.CardView>
</RelativeLayout>
Скриншоты действия по проверке OTP, когда вы получаете OTP, как только получаете сообщения
Вы можете попробовать использовать простую библиотеку, например
После установки через gradle и добавления разрешений инициируйте SmsVerifyCatcher в методе, подобном действию onCreate:
smsVerifyCatcher = new SmsVerifyCatcher(this, new OnSmsCatchListener<String>() {
@Override
public void onSmsCatch(String message) {
String code = parseCode(message);//Parse verification code
etCode.setText(code);//set code in edit text
//then you can send verification code to server
}
});
Кроме того, переопределите методы жизненного цикла активности:
@Override
protected void onStart() {
super.onStart();
smsVerifyCatcher.onStart();
}
@Override
protected void onStop() {
super.onStop();
smsVerifyCatcher.onStop();
}
/**
* need for Android 6 real time permissions
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
smsVerifyCatcher.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
public String parseCode(String message) {
Pattern p = Pattern.compile("\\b\\d{4}\\b");
Matcher m = p.matcher(message);
String code = "";
while (m.find()) {
code = m.group(0);
}
return code;
}
Поскольку Google ограничил использование разрешения READ_SMS, здесь есть решение без разрешения READ_SMS.
Основная функция заключается в том, чтобы избежать использования критического разрешения Android READ_SMS и выполнить задачу с помощью этого метода. Удар - это шаги, которые вам нужны.
Опубликуйте отправку OTP на номер пользователя, проверьте, может ли SMS Retriever API получить сообщение или нет
SmsRetrieverClient client = SmsRetriever.getClient(SignupSetResetPasswordActivity.this);
Task<Void> task = client.startSmsRetriever();
task.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
// Android will provide message once receive. Start your broadcast receiver.
IntentFilter filter = new IntentFilter();
filter.addAction(SmsRetriever.SMS_RETRIEVED_ACTION);
registerReceiver(new SmsReceiver(), filter);
}
});
task.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Failed to start retriever, inspect Exception for more details
}
});
Код широковещательного приемника
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import com.google.android.gms.auth.api.phone.SmsRetriever;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.Status;
public class SmsReceiver 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 otp;
String msgs = (String) extras.get(SmsRetriever.EXTRA_SMS_MESSAGE);
// Extract one-time code from the message and complete verification
break;
case CommonStatusCodes.TIMEOUT:
// Waiting for SMS timed out (5 minutes)
// Handle the error ...
break;
}
}
}
}
Последний шаг. Зарегистрируйте этот приемник в своем манифесте
<receiver android:name=".service.SmsReceiver" android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>
</intent-filter>
</receiver>
Ваше SMS должно быть таким, как показано ниже.
<#> Your OTP code is: 6789
QWsa8754qw2
Здесь QWsa8754qw2 — это 11-символьный хэш-код вашего собственного приложения. Перейдите по этой ссылке.
- Быть не длиннее 140 байт
- Начните с префикса ‹#>
- Завершите 11-символьной хэш-строкой, которая идентифицирует ваше приложение.
Чтобы импортировать com.google.android.gms.auth.api.phone.SmsRetriever
, не забудьте добавить эту строку в build.gradle вашего приложения:
implementation "com.google.android.gms:play-services-auth-api-phone:16.0.0"
Я реализовал что-то из того такого. Но вот что я сделал, когда приходит сообщение: я получаю только шестизначный код, связываю его с намерением и отправляю в действие или фрагмент, который в нем нуждается, и проверяю код. В примере показано, как уже получить смс. Посмотрите на приведенный ниже код, чтобы проиллюстрировать, как отправлять сообщения с помощью LocalBrodcastManager. Если ваше сообщение содержит больше текста, например приветствия, стандартизируйте его, чтобы вам было удобнее. Например, «Ваш код подтверждения: 84HG73», вы можете создать шаблон regex, подобный этому ([0-9]){2}([A-Z]){2}([0-9]){2}
, что означает два целых числа, две [заглавные] буквы и два целых числа. Удачи!
После удаления всей ненужной информации из сообщения
Intent intent = new Intent("AddedItem");
intent.putExtra("items", code);
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
И Фрагмент/Действие, получающее его
@Override
public void onResume() {
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(receiver, new IntentFilter("AddedItem"));
super.onResume();
}
@Override
public void onPause() {
super.onDestroy();
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(receiver);
}
И код, предназначенный для обработки собранной вами полезной нагрузки
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction()) {
final String message = intent.getStringExtra("message");
//Do whatever you want with the code here
}
}
};
Это немного помогает. Я сделал это лучше, используя обратные вызовы.
onPause()
вместо onDestroy()
?
- person galdin; 06.03.2016
Fragment
или Activity
всегда активны, оставьте их в onPause
, но в моем случае, когда мне это нужно только один раз, я могу поместить их в onPause
или onDestroy
, так как оба будут вызываться в любом случае
- person Tonespy; 06.03.2016
Извините за поздний ответ, но все же решил опубликовать свой ответ, если он поможет. Он работает для 6-значного одноразового пароля.
@Override
public void onOTPReceived(String messageBody)
{
Pattern pattern = Pattern.compile(SMSReceiver.OTP_REGEX);
Matcher matcher = pattern.matcher(messageBody);
String otp = HkpConstants.EMPTY;
while (matcher.find())
{
otp = matcher.group();
}
checkAndSetOTP(otp);
}
Adding constants here
public static final String OTP_REGEX = "[0-9]{1,6}";
Для прослушивателя SMS можно следовать классу ниже
public class SMSReceiver extends BroadcastReceiver
{
public static final String SMS_BUNDLE = "pdus";
public static final String OTP_REGEX = "[0-9]{1,6}";
private static final String FORMAT = "format";
private OnOTPSMSReceivedListener otpSMSListener;
public SMSReceiver(OnOTPSMSReceivedListener listener)
{
otpSMSListener = listener;
}
@Override
public void onReceive(Context context, Intent intent)
{
Bundle intentExtras = intent.getExtras();
if (intentExtras != null)
{
Object[] sms_bundle = (Object[]) intentExtras.get(SMS_BUNDLE);
String format = intent.getStringExtra(FORMAT);
if (sms_bundle != null)
{
otpSMSListener.onOTPSMSReceived(format, sms_bundle);
}
else {
// do nothing
}
}
}
@FunctionalInterface
public interface OnOTPSMSReceivedListener
{
void onOTPSMSReceived(@Nullable String format, Object... smsBundle);
}
}
@Override
public void onOTPSMSReceived(@Nullable String format, Object... smsBundle)
{
for (Object aSmsBundle : smsBundle)
{
SmsMessage smsMessage = getIncomingMessage(format, aSmsBundle);
String sender = smsMessage.getDisplayOriginatingAddress();
if (sender.toLowerCase().contains(ONEMG))
{
getIncomingMessage(smsMessage.getMessageBody());
} else
{
// do nothing
}
}
}
private SmsMessage getIncomingMessage(@Nullable String format, Object aObject)
{
SmsMessage currentSMS;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && format != null)
{
currentSMS = SmsMessage.createFromPdu((byte[]) aObject, format);
} else
{
currentSMS = SmsMessage.createFromPdu((byte[]) aObject);
}
return currentSMS;
}
С помощью SMS Retriever API можно прочитать OTP, не объявляя android.permission.READ_SMS
.
- Запустите поиск SMS
private fun startSMSRetriever() {
// Get an instance of SmsRetrieverClient, used to start listening for a matching SMS message.
val 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.
val task: Task<Void> = 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 {
Log.d("SmsRetriever", "SmsRetriever Start Success")
}
task.addOnFailureListener {
Log.d("SmsRetriever", "SmsRetriever Start Failed")
}
}
- Получать сообщения через рассылку
public class MySMSBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent?.action && intent.extras!=null) {
val extras = intent.extras
val status = extras.get(SmsRetriever.EXTRA_STATUS) as Status
when (status.statusCode) {
CommonStatusCodes.SUCCESS -> {
// Get SMS message contents
val message = extras.get(SmsRetriever.EXTRA_SMS_MESSAGE) as String
Log.e("Message", message);
// Extract one-time code from the message and complete verification
// by sending the code back to your server.
}
CommonStatusCodes.TIMEOUT -> {
// Waiting for SMS timed out (5 minutes)
// Handle the error ...
}
}
}
}
}
/**Don't forgot to define BroadcastReceiver in AndroidManifest.xml.*/
<receiver android:name=".MySMSBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>
</intent-filter>
</receiver>
- Отправьте одноразовый код из проверочного сообщения на свой сервер
Убедитесь, что формат вашего SMS соответствует указанному ниже:
<#> Your ExampleApp code is: 123ABC78
fBzOyyp9h6L
- Быть не длиннее 140 байт
- Начните с префикса ‹#>
Завершите 11-символьной хэш-строкой, которая идентифицирует ваше приложение.
Вы можете вычислить хэш приложения с помощью следующего кода:
import android.content.Context import android.content.ContextWrapper import android.content.pm.PackageManager import android.util.Base64 import android.util.Log import java.nio.charset.StandardCharsets import java.security.MessageDigest import java.security.NoSuchAlgorithmException import java.util.* /** * This is a helper class to generate your message hash to be included in your SMS message. * * Without the correct hash, your app won't recieve the message callback. This only needs to be * generated once per app and stored. Then you can remove this helper class from your code. * * For More Detail: https://developers.google.com/identity/sms-retriever/verify#computing_your_apps_hash_string * */ public class AppSignatureHelper(private val context: Context) : ContextWrapper(context) { companion object { val TAG = AppSignatureHelper::class.java.simpleName; private const val HASH_TYPE = "SHA-256"; const val NUM_HASHED_BYTES = 9; const val NUM_BASE64_CHAR = 11; } /** * Get all the app signatures for the current package * @return */ public fun getAppSignatures(): ArrayList<String> { val appCodes = ArrayList<String>(); try { // Get all package signatures for the current package val signatures = packageManager.getPackageInfo( packageName, PackageManager.GET_SIGNATURES ).signatures; // For each signature create a compatible hash for (signature in signatures) { val hash = hash(packageName, signature.toCharsString()); if (hash != null) { appCodes.add(String.format("%s", hash)); } } } catch (e: PackageManager.NameNotFoundException) { Log.e(TAG, "Unable to find package to obtain hash.", e); } return appCodes; } private fun hash(packageName: String, signature: String): String? { val appInfo = "$packageName $signature"; try { val messageDigest = MessageDigest.getInstance(HASH_TYPE); messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8)); var hashSignature = messageDigest.digest(); // truncated into NUM_HASHED_BYTES hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES); // encode into Base64 var base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP); base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR); Log.e(TAG, String.format("pkg: %s -- hash: %s", packageName, base64Hash)); return base64Hash; } catch (e: NoSuchAlgorithmException) { Log.e(TAG, "hash:NoSuchAlgorithm", e); } return null; } }
Требуемый Gradle:
implementation "com.google.android.gms:play-services-auth-api-phone:16.0.0"
Ссылки:
https://developers.google.com/identity/sms-retriever/overview
https://developers.google.com/identity/sms-retriever/request
https://developers.google.com/identity/sms-retriever/verify
Да, теперь это возможно и в браузерах. Chrome выпускает эту функцию в версии 84 и выше. С помощью WEBOTP API мы можем обнаруживать OTP в Интернете для мобильных устройств.
Вот интегрированный код Web-OTP с приложениями Angular PWA: https://github.com/Rohit3230/webOtpAutoReadByAngular а>
Перейдите к рабочему URL-адресу углового приложения PWA. https://rohit3230.github.io/webOtpAutoReadByAngular/
Я знаю, что уже поздно, но чтобы это было проще для других, вот решение, которое я написал ранее. Воспользуйтесь ссылкой на эту библиотеку. Вам не придется ни с чем возиться. После добавления зависимости просто используйте этот метод.
OtpFetcher.getInstance().verifyOtpByMatchingString(this, "OTP", 21000, object : OtpListener {
override fun onReceived(messageItem: MessageItem) {
Toast.makeText(this@MainActivity, "" + messageItem, Toast.LENGTH_SHORT).show()
}
override fun onTimeOut() {
Toast.makeText(this@MainActivity, "TimeOut", Toast.LENGTH_SHORT).show()
}
})
Вам нужно будет передать контекст, строку поиска вашего сообщения, например
Вы ожидаете OTP в своем сообщении. Пропустите OTP и Time Out for Как долго вы хотите слушать OTP и все. Вы получите свое сообщение в простом формате в OnRecieved CallBack.