개발 Tip
SMS Retriever API 사용
삐질
2019. 3. 12. 21:22
개요
- 2019 / 01 / 09 부터 구글 플레이 스토어상의 SMS 권한 정책이 변경
- 자동 인증을 위한 SMS Read, Write 권한 제거
- 자동 인증 기능 대안 API → SMS Retriever API
Reference : https://developers.google.com/identity/sms-retriever/verify
사용
- 사용자 전화번호 획득 → HintRequest & PendingIntent
- SMS 검색기 시작 → SmsRetrieverClient & start()
- 서버에 인증 요청 → Client to Server
- 인증 번호 수신 → 지정된 Form 문자
<#> 내용 + HashCode(전체 HashCode의 앞 11글자)
EX) <#> Authorization Code : 3356 INRiCZwJsdf
- 인증 및 서버 반환
이때 HashCode 구성하는 방법
- 앱의 공개 키 인증서를 소문자의 16 진수 문자열로 가져옵니다
- 패키지 명과, 공백 하나, 얻은 공개키 문자열을 하나의 스트링으로 합친 후
- SHA-256으로 암호화
- 인증 후 서버 회신
HashCode를 얻어오기 위한 Helper 클래스
(단, 헬퍼 클래스는 상용 앱 내에 호출되지 않아야 하고, HashCode를 얻은 후 클래스를 제거하는 것을 권함)
public class AppSignatureHelper extends ContextWrapper {
public static final String TAG = AppSignatureHelper.class.getSimpleName();
private static final String HASH_TYPE = "SHA-256";
public static final int NUM_HASHED_BYTES = 9;
public static final int NUM_BASE64_CHAR = 11;
public AppSignatureHelper(Context context) {
super(context);
}
/**
* Get all the app signatures for the current package
* @return
*/
public ArrayList<String> getAppSignatures() {
ArrayList<String> appCodes = new ArrayList<>();
try {
// Get all package signatures for the current package
String packageName = getPackageName();
PackageManager packageManager = getPackageManager();
Signature[] signatures = packageManager.getPackageInfo(packageName,
PackageManager.GET_SIGNATURES).signatures;
// For each signature create a compatible hash
for (Signature signature : signatures) {
String hash = hash(packageName, signature.toCharsString());
if (hash != null) {
appCodes.add(String.format("%s", hash));
}
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Unable to find package to obtain hash.", e);
}
return appCodes;
}
private static String hash(String packageName, String signature) {
String appInfo = packageName + " " + signature;
try {
MessageDigest messageDigest = MessageDigest.getInstance(HASH_TYPE);
messageDigest.update(appInfo.getBytes(StandardCharsets.UTF_8));
byte[] hashSignature = messageDigest.digest();
// truncated into NUM_HASHED_BYTES
hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES);
// encode into Base64
String base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING | Base64.NO_WRAP);
base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR);
Log.d(TAG, String.format("pkg: %s -- hash: %s", packageName, base64Hash));
return base64Hash;
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "hash:NoSuchAlgorithm", e);
}
return null;
}
} |
GoogleClientServie 객체 init
private GoogleApiClient mCredentialsApiClient;
public void setGoogleServieClient() {
mCredentialsApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.enableAutoManage(this, this)
.addApi(Auth.CREDENTIALS_API)
.build();
}
|
각 전화번호 얻기
private static final int RESOLVE_HINT = 1000;
// Construct a request for phone numbers and show the picker
private void requestHint() {
HintRequest hintRequest = new HintRequest.Builder()
.setPhoneNumberIdentifierSupported(true)
.build();
PendingIntent intent = Auth.CredentialsApi.getHintPickerIntent(
mCredentialsApiClient, hintRequest);
startIntentSenderForResult(intent.getIntentSender(),
RESOLVE_HINT, null, 0, 0, 0);
}
// Obtain the phone number from the result
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RESOLVE_HINT) {
if (resultCode == RESULT_OK) {
Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
// credential.getId(); <-- will need to process phone number string
}
}
} |
SMSClient 작성
//Set Start SmsRetrieverClient
public void setSmsClient() {
// Get an instance of SmsRetrieverClient, used to start listening for a matching
// SMS message.
SmsRetrieverClient 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.
Task<Void> task = 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(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
// Successfully started retriever, expect broadcast intent
// ...
}
});
task.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Failed to start retriever, inspect Exception for more details
// ...
}
});
}
|
브로드 캐스트 리시버
class SmsBrReceiver extends BroadcastReceiver {
/* public void setTimeout() {
h.postDelayed(r, MAX_TIMEOUT);
}*/
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
String action = intent.getAction();
if (SmsRetriever.SMS_RETRIEVED_ACTION.equals(action)) {
Bundle extras = intent.getExtras();
Status status = (Status) extras.get(SmsRetriever.EXTRA_STATUS);
switch (status.getStatusCode()) {
case CommonStatusCodes.SUCCESS:
String smsMessage = (String) extras.get(SmsRetriever.EXTRA_SMS_MESSAGE);
break;
case CommonStatusCodes.TIMEOUT:
break;
default:
break;
}
}
}
} |