You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

上架Google Play后SMS Retriever API无法自动读取OTP问题排查

SMS Retriever API上架Google Play后失效问题排查与解决

问题描述

我在实现OTP自动读取功能时踩了个坑:本地Debug和Release测试时,SMS Retriever API能正常自动抓取验证码,但应用上架Google Play后,这个功能直接失效,用户必须手动输入OTP

我的实现逻辑是:

  • 通过AppSignatureHashHelper生成11位哈希码传给服务器
  • 服务器下发包含该哈希码的短信
  • 用Broadcast Receiver监听短信、解析OTP后回传服务器验证

相关代码和配置如下:

向服务器提交手机号与哈希码

phoneNo = phoneText.getText().toString();
System.out.println( phoneNo );
params.put( "mob_no", phoneNo );
//String debugHashKey = "Yq%2BZIxNoG%2BK";
//String releaseHashKey = "h9mVMD4POjg";
String appSigningKey = "b4epgGrrtyp";
params.put( "uniqueKey", releaseHashKey );
asyncHttpClient.post( mob_validate, params, new JsonHttpResponseHandler() {
    @Override
    public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
        try {
            System.out.println( "onSuccess" );
            status = response.getString( "status" );
            msg = response.getString( "msg" );
            Toast.makeText( getApplicationContext(), msg, Toast.LENGTH_SHORT ).show();
            if (status.equals( "success" )) {
                Intent intent = new Intent( MainActivity.this, OTPConfirmationActivity.class );
                intent.putExtra( "phone_no", phoneNo );
                startActivity( intent );
                finish();
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {
        //Toast.makeText( getApplicationContext(), R.string.onFailure, Toast.LENGTH_SHORT ).show();
    }
});

广播接收器读取OTP并验证

params.put( "otp", otp );
params.put( "mob_no", phoneNo );
asyncHttpClient.post( verify_otp, params, new JsonHttpResponseHandler() {
    @Override
    public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
        try {
            status = response.getString( "status" );
            msg = response.getString( "msg" );
            Toast.makeText( OTPConfirmationActivity.this, msg, Toast.LENGTH_SHORT ).show();
            if (status.equals( "success" )) {
                endTimer();
                sharedPreferences.edit().putBoolean( "logged", true ).apply();
                Intent intent = new Intent( getBaseContext(), HomeActivity.class );
                startActivity( intent );
                finish();
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {
        //Toast.makeText( getApplicationContext(), R.string.onFailure, Toast.LENGTH_SHORT ).show();
    }
});

Build.gradle签名配置

android {
    signingConfigs {
        release {
            storeFile file('/home/eazysoft/Documents/finalKey/releaseKey.jks')
            storePassword 'password'
            keyAlias = 'upload'
            keyPassword 'password'
        }
    }
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.eazysoft.lookAround"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 3
        versionName "1.0.2"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
        signingConfig signingConfigs.release
    }
    buildTypes {
        release {
            minifyEnabled true
            useProguard true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
            debuggable = true
        }
        debug {
            debuggable true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:support-v4:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha4'
    testImplementation 'junit:junit:4.13-beta-2'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support:multidex:1.0.3'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
    implementation 'com.android.support:cardview-v7:28.0.0'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support:exifinterface:28.0.0'
    implementation 'com.google.android.gms:play-services-maps:16.1.0'
    implementation 'com.google.android.gms:play-services-location:16.0.0'
    implementation 'com.google.android.gms:play-services-base:16.1.0'
    implementation 'com.google.android.gms:play-services-identity:16.0.0'
    implementation 'com.google.android.gms:play-services-auth:16.0.1'
    implementation 'com.google.android.gms:play-services-auth-api-phone:16.0.0'
    implementation 'com.loopj.android:android-async-http:1.4.9'
}

问题根源与解决步骤

经过排查,问题出在Google Play的应用签名机制:你本地打包用的是自己生成的Upload Key,但用户从Play商店下载的应用,是用Google自动生成的App Signing Key重新签名的。SMS Retriever API的哈希码依赖应用签名,所以本地生成的哈希码在Play版本上完全不匹配。

下面是具体修复步骤:

步骤1:下载Google生成的应用签名证书

登录Google Play Console,找到你的应用,进入Release > Setup > App signing页面,下载「部署证书(Deployment certificate)」,保存为deployment_cert.der文件。

步骤2:将DER证书转换为JKS格式

在Android Studio终端执行以下命令(仅修改证书文件路径,别名和密码可保持默认):

keytool -importcert -alias myalias -file /home/Downloads/deployment_cert.der -keystore certificate.jks -storepass mypassword

执行后会提示Trust this certificate? [no]: ,输入y确认,成功后项目目录会生成certificate.jks文件。

步骤3:生成适配Play版本的11位哈希码

执行以下命令生成新的哈希码(注意替换JKS文件路径,别名要和步骤2一致,com.eazysoft.lookAround替换为你的应用applicationId):

keytool -exportcert -alias myalias -keystore /home/user/AndroidStudioProjects/LookAround/certificate.jks | xxd -p | tr -d "[:space:]" | echo -n com.eazysoft.lookAround `cat` | sha256sum | tr -d "[:space:]-" | xxd -r -p | base64 | cut -c1-11

输入密码时用步骤2设置的mypassword,最终会得到新的11位哈希码。

步骤4:更新服务器哈希码

把新生成的哈希码替换代码中传给服务器的uniqueKey值,确保服务器下发的短信末尾包含@<新哈希码>的格式。

完成以上步骤后重新打包上传,Play商店的应用就能正常自动读取OTP了!


内容的提问来源于stack exchange,提问作者Rohan

火山引擎 最新活动