はじめに
ある日、GooglePlayConsole上にこんな警告が出ていました。
このアプリには、android.permission.RECEIVE_SMS
の権限を付与していました。
android.permission.RECEIVE_SMS
の権限を使うなら、その権限をなんの目的で使うのかを明確に申請してくれというのが今回の警告の内容です。
このアプリでのandroid.permission.RECEIVE_SMS
の用途は、アカウントの認証時、SMS認証をしており、認証コードを自動入力させるために、受信したSMSの内容を取得するためでした。
この用途でandroid.permission.RECEIVE_SMS
を使ってしまうと、自分のアプリに必要なSMS以外のすべてのSMSをアプリから読むことができてしまいます。それはセキュリティー的にいかがなことなのかということで、Googleにお叱りをうけました。
そして、代替手段として、 SMS Retriever APIを使ってねと言われました。
SMS Retriever API とは
android.permission.RECEIVE_SMS
を使わずに、自分のアプリに必要なSMSを受信できる仕組みが、SMS Retriever APIです。
https://developers.google.com/identity/sms-retriever/overview
サーバーから送られてくるSMSのメッセージに、プレフィックスと、自分のアプリであることを識別するハッシュを記載します。
https://developers.google.com/identity/sms-retriever/verify
アプリは、SMS Retriever APIをつかって、自分のアプリのハッシュが含まれているSMSの内容だけ取得できます。
https://developers.google.com/identity/sms-retriever/request
今回は上の画像の③⑤の部分の実装について書いています。
事前準備
app配下のbuild.gradle
に com.google.android.gms:play-services-auth
を追加します。
バージョンはここを参考にしてください。
dependencies {
implementation "com.google.android.gms:play-services-auth:16.0.1"
}
実装方法
ほとんどリファレンス通りなのですが、サンプルを載せます
class MainActivity : AppCompatActivity(), GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
createSmsRetrieverClient()
//Hashの作成
// val helper = AppSignatureHelper(applicationContext)
// Log.d("hash = ", helper.appSignatures.toString())//このハッシュをSMSに含める
}
private fun createSmsRetrieverClient() {
val receiver = SMSBroadcastReceiver()
val intentFilter = IntentFilter()
intentFilter.addAction(SmsRetriever.SMS_RETRIEVED_ACTION)
registerReceiver(receiver, intentFilter)
val client = SmsRetriever.getClient(this)
val task = client.startSmsRetriever()
task.addOnSuccessListener {
//SMSRetriever追加成功時リスナー
Log.d("Listener", "SUCCESS")
}
task.addOnFailureListener {
//SMSRetriever追加失敗時リスナー
Log.d("Listener", "FAILURE")
}
}
inner class SMSBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val status = extras!!.get(SmsRetriever.EXTRA_STATUS) as Status
var message = ""
when (status.statusCode) {
CommonStatusCodes.SUCCESS -> {
//自分のアプリ用のハッシュ値が含まれているSMSを受信したときにここに入ってくる
//対象のSMSの内容のみ取得可能
message = extras.get(SmsRetriever.EXTRA_SMS_MESSAGE) as String
Log.d("SMSMessage = ", message)
}
CommonStatusCodes.TIMEOUT -> {
//レシーバーを開始して5分間SMSを受信しないとタイムアウトになる
Log.d("SMSMessage = ", "TIMEOUT")
}
}
}
}
}
override fun onConnectionFailed(p0: ConnectionResult) {
}
override fun onConnectionSuspended(p0: Int) {
}
override fun onConnected(p0: Bundle?) {
}
}
SMSを受信したいタイミングでSmsRetrieverClientのインスタンスを作成し、SMSを受信する準備をします。
サーバーから、
<#> あなたの認証コードは: 123456 です
ハッシュ
というような内容のSMSが送られてきて、ハッシュが自分のアプリのハッシュと一致していた場合、
extras.get(SmsRetriever.EXTRA_SMS_MESSAGE)
で、SMSの内容を取得することができます。
ハッシュは、アプリのパッケージ名、証明書の情報で生成されます。ハッシュ作成用のサンプルコード が提供されているので、これで自分の環境にあったハッシュを生成してください。
余談?ですが、リファレンスに書いてあるコマンドでは正しいハッシュを作れませんでした…(これで数時間ハマった…)コマンドに当てはめる項目を間違えたのだと思いますが、何を間違えたのか判明したら追記します。。。
受信確認
サーバーからSMSを送ってもらうのが実際の使用用途かと思いますが、確認だけなら、SMSBroadcastReceiverが準備されている検証端末に、他の端末から<#>
のプレフィックスをつけ、生成したハッシュを含んだSMSを送っても受信確認ができます
まとめ
上記の通りに実装すれば、android.permission.RECEIVE_SMS
の権限なしに、自分のアプリに関係するSMSの内容だけアプリ内で読み取る事ができます。
あとは煮るなり焼くなり好きにして、SMS認証コードの自動入力などに活用してください!
私が調べた時点では日本語の情報がとても少なかったので、記事を書いてみました。
参考にしていただけたら幸いです!
サンプルコード
サンプルコード全部はGitHubに載せてます