카테고리 없음

[Android] 기기를 바꿔도 로그인이 유지된다 — Block Store API 정리

devyong 2026. 9. 21. 20:03

앱을 새 폰으로 옮길 때 가장 귀찮은 순간은 결국 "다시 로그인"입니다. 사용자는 비밀번호를 기억하지 못하고, 그 자리에서 이탈합니다. Google Play services가 제공하는 Block Store API는 바로 이 구간을 없애기 위한 API입니다. 이번 글에서는 Block Store가 무엇인지, 어떤 원리로 동작하는지, 그리고 실제 코드로 어떻게 붙이는지 정리해 보겠습니다.

Block Store가 해결하는 문제

Block Store는 앱이 최대 16개의 바이트 배열(각 4KB 이하) 을 Google Play services의 보호된 영역에 저장해 두고, 사용자가 새 기기로 이전할 때 그대로 복원해 주는 API입니다.

핵심은 "비밀번호를 저장하는 것이 아니라 인증 토큰을 저장한다"는 점입니다. 로그인에 성공한 시점에 세션 토큰(또는 리프레시 토큰)을 Block Store에 넣어두면, 새 기기 초기 설정에서 앱이 복원될 때 그 토큰도 함께 넘어옵니다. 앱은 첫 실행에서 토큰을 꺼내 바로 로그인 상태를 만들 수 있습니다.

기존 방식과의 차이를 정리하면 이렇습니다.

저장소 앱 삭제 후 유지 기기 이전 용도

SharedPreferences ✕ ✕ 일반 설정값
EncryptedSharedPrefs / Keystore ✕ ✕ (키가 기기 종속) 로컬 민감정보
Auto Backup △ △ (앱 전체 데이터) 범용 백업
Block Store ○ ○ 인증 토큰

Android Keystore는 키가 하드웨어에 묶여 있어 기기를 넘어가지 못합니다. Block Store는 Play services가 소유권(패키지명 + 서명)을 검증해 저장하므로, 다른 앱은 접근할 수 없으면서도 기기 이전은 가능합니다.

의존성 추가

dependencies {
    implementation 'com.google.android.gms:play-services-auth-blockstore:16.4.0'
}

토큰 저장하기

val client = Blockstore.getClient(this)

val storeRequest = StoreBytesData.Builder()
    .setBytes(sessionToken.toByteArray())
    .setKeys(listOf("com.example.app.session_token"))
    .build()

client.storeBytes(storeRequest)
    .addOnSuccessListener { bytesStored ->
        Log.d(TAG, "$bytesStored bytes 저장 완료")
    }
    .addOnFailureListener { e ->
        Log.e(TAG, "저장 실패", e)
    }

setKeys()를 생략하면 BlockstoreClient.DEFAULT_BYTES_DATA_KEY라는 기본 키에 저장됩니다. 다만 나중에 항목이 늘어날 것을 감안하면 처음부터 패키지명을 접두어로 한 명시적 키를 쓰는 편이 안전합니다.

토큰 꺼내오기

앱의 스플래시나 초기 진입 시점에서 호출합니다.

val retrieveRequest = RetrieveBytesRequest.Builder()
    .setKeys(listOf("com.example.app.session_token"))
    .build()

client.retrieveBytes(retrieveRequest)
    .addOnSuccessListener { response ->
        val data = response.blockstoreDataMap["com.example.app.session_token"]
        if (data != null) {
            val token = String(data.bytes)
            // 서버에 토큰 유효성 검증 후 자동 로그인
        } else {
            // 저장된 토큰 없음 → 로그인 화면
        }
    }

저장된 모든 항목이 필요하다면 setRetrieveAll(true) 를 사용하면 됩니다. 참고로 파라미터가 없는 구버전 retrieveBytes() 는 deprecated 되었으니 RetrieveBytesRequest 방식을 쓰는 것이 좋습니다.

로그아웃 시 삭제

val deleteRequest = DeleteBytesRequest.Builder()
    .setKeys(listOf("com.example.app.session_token"))
    .build()

client.deleteBytes(deleteRequest)

전체 삭제는 setDeleteAll(true) 입니다. 로그아웃, 토큰 폐기, 계정 탈퇴 시점에 반드시 삭제 처리를 넣어야 합니다. 이걸 빼먹으면 로그아웃한 사용자의 토큰이 새 기기까지 따라가는 상황이 생깁니다.

클라우드 백업과 E2EE

Block Store는 기본적으로 기기 간 직접 이전(D2D) 에서만 데이터를 넘깁니다. 구형 기기를 잃어버린 경우처럼 소스 기기가 없는 상황까지 커버하려면 클라우드 백업을 켜야 합니다.

단, 클라우드에 올라가는 만큼 종단간 암호화(E2EE) 가능 여부를 먼저 확인하는 것이 권장됩니다.

val builder = StoreBytesData.Builder()
    .setBytes(sessionToken.toByteArray())
    .setKeys(listOf("com.example.app.session_token"))

client.isEndToEndEncryptionAvailable()
    .addOnSuccessListener { isE2eeAvailable ->
        if (isE2eeAvailable) {
            builder.setShouldBackupToCloud(true)
        }
        client.storeBytes(builder.build())
    }

E2EE는 Android 9 이상 + 화면 잠금(PIN/패턴/비밀번호) 설정 이 조건입니다. 화면 잠금이 없는 기기에서는 false가 반환되므로, 이 경우에는 클라우드 백업 없이 D2D 복원만 지원하는 것이 안전합니다.

주의할 점이 하나 더 있습니다. shouldBackupToCloud 를 설정하지 않거나 false 로 storeBytes() 를 호출하면, 다음 주기적 동기화 때 기존에 백업된 클라우드 데이터가 삭제됩니다. 저장 로직이 여러 군데 흩어져 있으면 한쪽에서 무심코 덮어쓰기 쉬우니, 저장 호출을 한 곳으로 모아두는 편이 좋습니다.

테스트 방법

가장 간단한 검증은 같은 기기에서 삭제 후 재설치 입니다.

  1. 설정 > Google > 백업이 켜져 있는지 확인
  2. 앱에서 storeBytes() 호출
  3. 앱 삭제
  4. 재설치 후 retrieveBytes() 로 값이 그대로 나오는지 확인

클라우드 복원까지 확인하려면 Play Store에 테스트 앱을 올린 뒤 대상 기기를 초기화해야 해서 난이도가 확 올라갑니다. 백업 성공 여부는 로그캣에서 CloudSyncBpTkSvc 태그로 확인할 수 있고, 백업 시도 간에는 5분의 쿨다운이 있습니다.

도입 전에 고려할 것

  • 저장 용량이 작습니다. 항목당 4KB, 최대 16개입니다. 사용자 프로필이나 캐시를 넣는 용도가 아니라 토큰 전용이라고 봐야 합니다.
  • 토큰 검증은 서버 책임입니다. 복원된 토큰이 유효하다는 보장은 없습니다. 반드시 서버 검증을 거친 뒤 로그인 상태를 만들어야 합니다.
  • Google은 현재 Credential Manager(패스키)를 권장합니다. 신규 인증 설계라면 패스키를 우선 검토하고, Block Store는 이미 운영 중인 자체 토큰 기반 인증에 "기기 이전 경험"만 얹는 보완재로 보는 편이 현실적입니다.

로그인 화면 이탈률이 신경 쓰이는 앱이라면, 붙이는 비용 대비 체감 개선이 꽤 큰 API입니다. 저장·조회·삭제 세 개의 호출이 전부라 하루면 충분히 붙일 수 있습니다.


참고 자료