OTP (token) should be automatically read from the message

前端 未结 7 822
栀梦
栀梦 2020-12-04 10:50

I am working on an Android App, in which the server sends an OTP and the user needs to enter this OTP in the App, to SignUp for my App. What I want is, that my App should be

相关标签:
7条回答
  • 2020-12-04 11:22

    As Google has restricted use of READ_SMS permission here is solution without READ_SMS permission.

    SMS Retriever API

    Basic function is to avoid using Android critical permission READ_SMS and accomplish task using this method. Blow are steps you needed.

    Post Sending OTP to user's number, check SMS Retriever API able to get message or not

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

    Broadcast Receiver Code

    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;
                }
            }
        }
    }
    

    Final Step. Register this receiver into your Manifest

    <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>
    

    Your SMS must as below.

    <#> Your OTP code is: 6789
    QWsa8754qw2 
    

    Here QWsa8754qw2 is your own application 11 character hash code. Follow this link

    • Be no longer than 140 bytes
    • Begin with the prefix <#>
    • End with an 11-character hash string that identifies your app

    To import com.google.android.gms.auth.api.phone.SmsRetriever, Dont forget to add this line to your app build.gradle:

    implementation "com.google.android.gms:play-services-auth-api-phone:16.0.0"
    
    0 讨论(0)
  • 2020-12-04 11:33

    You can try using a simple library like

    After installing via gradle and adding permissions initiate SmsVerifyCatcher in method like onCreate activity:

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

    Also, override activity lifecicle methods:

      @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;
    }
    
    0 讨论(0)
  • 2020-12-04 11:34

    With the SMS Retriever API, one can Read OTP without declaring android.permission.READ_SMS.

    1. Start the SMS retriever
        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")
            }
        }
    
    1. Receive messages via Broadcast
        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>
    
    1. Send the one-time code from the verification message to your server

    Make sure your SMS format is exactly as below:

    <#> Your ExampleApp code is: 123ABC78
    fBzOyyp9h6L
    
    1. Be no longer than 140 bytes
    2. Begin with the prefix <#>
    3. End with an 11-character hash string that identifies your app

      You can compute app hash with following code:

      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;
          }
      }       
      

    Required Gradle :

    implementation "com.google.android.gms:play-services-auth-api-phone:16.0.0"
    

    References:
    https://developers.google.com/identity/sms-retriever/overview
    https://developers.google.com/identity/sms-retriever/request
    https://developers.google.com/identity/sms-retriever/verify

    0 讨论(0)
  • 2020-12-04 11:39

    I will recommend you not to use any third party libraries for auto fetch OTP from SMS Inbox. This can be done easily if you have basic understanding of Broadcast Receiver and how it works. Just Try following approach :

    Step 1) Create single interface i.e SmsListner

    package com.wnrcorp.reba;
    public interface SmsListener{
    public void messageReceived(String messageText);}
    

    Step 2) Create single Broadcast Receiver i.e 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;
    }
    }
    

    Step 3) Add Listener i.e broadcast receiver in android manifest file

    <receiver android:name=".SmsReceiver">    
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
            </intent-filter>
    </receiver>
    

    and add permission

    <uses-permission android:name="android.permission.RECEIVE_SMS"/>

    Final Step 4) The activity where you going to auto fetch otp when it is received in inbox. In my case I'm fetching otp and setting on edittext field.

    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();           
                 }
        });
       }
    }
    

    Layout File for 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>
    

    Screenshots for OTP Verification Activity where you fetch OTP as soons as messages received

    0 讨论(0)
  • 2020-12-04 11:40

    I implemented something of that such. But, here is what I did when the message comes in, I retrieve only the six digit code, bundle it in an intent and send it to the activity or fragment needing it and verifies the code. The example shows you the way to get the sms already. Look at the code below for illustration on how to send using LocalBrodcastManager and if your message contains more texts E.g Greetings, standardize it to help you better. E.g "Your verification code is: 84HG73" you can create a regex pattern like this ([0-9]){2}([A-Z]){2}([0-9]){2} which means two ints, two [capital] letters and two ints. Good Luck!

    After discarding all un needed info from the message

     Intent intent = new Intent("AddedItem");
     intent.putExtra("items", code);
     LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent); 
    

    And the Fragment/Activity receiving it

    @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);
    }
    

    And the code meant to handle the payload you collected

     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
            }
        }
    };
    

    Does that help a little bit. I did it better by using Callbacks

    0 讨论(0)
  • 2020-12-04 11:43

    Sorry for late reply but still felt like posting my answer if it helps.It works for 6 digits OTP.

        @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}";
    

    For SMS listener one can follow the below class

    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;
        }
    
    0 讨论(0)
提交回复
热议问题