LVL 클래스 및 인터페이스
표 1에는 Android SDK를 통해 사용 가능한 라이선스 확인 라이브러리(LVL)의 모든 소스 파일이 나열되어 있습니다. 모든 파일은 com.android.vending.licensing
패키지의 일부입니다.
카테고리 | 이름 | 설명 |
---|---|---|
라이선스 확인 및 결과 | LicenseChecker | 라이선스 확인을 시작하기 위해 인스턴스화하거나 서브클래스로 세분하는 클래스입니다. |
LicenseCheckerCallback | 라이선스 확인 결과를 처리하기 위해 구현하는 인터페이스입니다. | |
정책 | 정책 | 라이선스 응답에 기반하여 애플리케이션의 액세스 허용 여부를 판단하기 위해 구현하는 인터페이스입니다. |
ServerManagedPolicy | 기본 Policy 구현입니다. 라이선스 서버에서 제공한 설정을 사용하여 라이선스 데이터의 로컬 저장소, 라이선스 유효성, 재시도를 관리합니다. |
|
StrictPolicy | 대체 Policy 구현입니다. 서버의 직접적인 라이선스 응답에만 기반하여 라이선스를 시행합니다. 캐싱 또는 요청 재시도는 없습니다. |
|
데이터 난독화 (선택사항) |
Obfuscator | 영구 저장소에 라이선스 응답 데이터를 캐시하는 Policy (예: ServerManagedPolicy)를 사용하는 경우 구현하는 인터페이스입니다.
난독화 알고리즘을 적용하여 쓰거나 읽고 있는 데이터를 인코딩 및 디코딩합니다. |
AESObfuscator | AES 암호화 및 복호화 알고리즘을 사용하여 데이터를 난독화 및 난독화 해제하는 기본 Obfuscator 구현입니다. | |
기기 제한 (선택사항) |
DeviceLimiter | 애플리케이션의 사용을 특정 기기로 제한하려는 경우 구현하는 인터페이스입니다. LicenseValidator에서 호출됩니다. DeviceLimiter 구현은 대부분의 애플리케이션에 권장하지 않습니다. 백엔드 서버가 필요하고 신중하게 디자인하지 않으면 사용자가 라이선스가 부여된 애플리케이션의 액세스 권한을 잃을 수 있기 때문입니다. |
NullDeviceLimiter | 모든 기기에 액세스를 허용하는 노옵(no-op)인 기본 DeviceLimiter 구현입니다. | |
라이브러리 코어, 통합 필요 없음 | ResponseData | 라이선스 응답 필드를 보유하는 클래스입니다. |
LicenseValidator | 라이선스 서버에서 수신한 응답을 복호화하고 확인하는 클래스입니다. | |
ValidationException | Obfuscator에서 관리하는 데이터의 무결성을 검사할 때 발생하는 오류를 표시하는 클래스입니다. | |
PreferenceObfuscator | 시스템의 SharedPreferences 저장소에 난독화된 데이터를 쓰거나 읽는 유틸리티 클래스입니다. |
|
ILicensingService | 라이선스 확인 요청이 Google Play 클라이언트에 전달되는 단방향 IPC 인터페이스입니다. | |
ILicenseResultListener | 애플리케이션이 라이선스 서버에서 비동기 응답을 수신하는 단방향 IPC 콜백 구현입니다. |
서버 응답
표 2에는 라이선스 서버에서 반환하는 모든 라이선스 응답 필드가 나열되어 있습니다.
필드 | 설명 |
---|---|
responseCode |
라이선스 서버에서 반환한 응답 코드입니다. 응답 코드는 서버 응답 코드에 설명되어 있습니다. |
signedData |
다음과 같이 라이선스 서버에서 반환한 데이터를 보유하는 문자열 연결입니다.
responseCode|nonce|packageName|versionCode|userId|timestamp:extras
|
signature |
앱별 키를 사용하는 signedData 의 서명입니다.
|
서버 응답 코드
표 3에는 지원되는 모든 라이선스 응답 코드가 나열되어 있습니다. 일반적으로 애플리케이션은 이러한 응답 코드를 모두 처리해야 합니다. 기본적으로 LVL의 LicenseValidator 클래스는 이러한 응답 코드의 필요한 처리를 모두 제공합니다.
응답 코드 | 정수 값 표현 | 설명 | 서명 여부 | 추가 | 참고 |
---|---|---|---|---|---|
LICENSED |
0 |
사용자에게 애플리케이션의 라이선스가 부여되었습니다. 사용자가 애플리케이션을 구매했거나 사용자에게 애플리케이션의 알파 또는 베타 버전을 다운로드하고 설치할 권한이 부여되었습니다. | 예 | VT , GT , GR |
Policy 제약 조건에 따라 액세스를 허용합니다. |
LICENSED_OLD_KEY |
2 |
사용자에게 애플리케이션의 라이선스가 부여되었지만 다른 키로 서명되어 업데이트된 사용 가능한 애플리케이션 버전이 있습니다. | 예 | VT , GT , GR , UT |
Policy 제약 조건에 따라 선택적으로 액세스를 허용합니다.
설치된 애플리케이션 버전에서 사용하는 키 쌍이 무효하거나 손상되었다고 표시할 수 있습니다. 애플리케이션은 필요한 경우 액세스를 허용하거나 사용자에게 업그레이드를 할 수 있다고 알리고 업그레이드할 때까지 추가 사용을 제한할 수 있습니다. |
NOT_LICENSED |
1 |
사용자에게 애플리케이션의 라이선스가 부여되지 않았습니다. | 아니요 | 액세스를 허용하지 않습니다. | |
ERROR_CONTACTING_SERVER |
257 |
로컬 오류 - Google Play 애플리케이션이 라이선스 서버에 도달할 수 없었으며 네트워크 가용성 문제가 원인일 수 있습니다. | 아니요 | Policy 재시도 한도에 따라 라이선스 확인을 재시도합니다. |
|
ERROR_SERVER_FAILURE |
4 |
서버 오류 - 서버가 라이선스를 위한 애플리케이션의 키 쌍을 로드할 수 없었습니다. | 아니요 | Policy 재시도 한도에 따라 라이선스 확인을 재시도합니다.
|
|
ERROR_INVALID_PACKAGE_NAME |
258 |
로컬 오류 - 애플리케이션이 기기에 설치되지 않은 패키지의 라이선스 확인을 요청했습니다. | 아니요 | 라이선스 확인을 재시도하지 마세요.
일반적으로 개발 오류로 인해 발생합니다. |
|
ERROR_NON_MATCHING_UID |
259 |
로컬 오류 - UID(패키지, 사용자 ID 쌍)가 요청하는 애플리케이션의 UID와 일치하지 않는 패키지의 라이선스 확인을 애플리케이션이 요청했습니다. | 아니요 | 라이선스 확인을 재시도하지 마세요.
일반적으로 개발 오류로 인해 발생합니다. |
|
ERROR_NOT_MARKET_MANAGED |
3 |
서버 오류 - 애플리케이션(패키지 이름)을 Google Play에서 인식하지 못했습니다. | 아니요 | 라이선스 확인을 재시도하지 마세요.
애플리케이션이 Google Play를 통해 게시되지 않았거나 라이선스 구현에 개발 오류가 있다고 표시할 수 있습니다. |
참고: 테스트 환경 설정에 설명된 대로 응답 코드는 애플리케이션 개발자 및 등록된 테스트 사용자를 위해 Google Play Console을 통해 수동으로 재정의될 수 있습니다.
참고: 전에는 게시되지 않은 '초안' 버전을 업로드하여 앱을 테스트할 수 있었습니다. 이 기능은 더 이상 지원되지 않습니다. 대신 알파 또는 베타 배포 채널에 게시해야 합니다. 자세한 내용은 초안 앱이 더 이상 지원되지 않음을 참조하세요.
서버 응답 추가 정보
애플리케이션이 애플리케이션 환불 기간에 애플리케이션 액세스를 관리하는 것을 지원하고 기타 정보를 제공하기 위해 라이선스 서버에는 라이선스 응답의 여러 정보가 포함되어 있습니다. 특히 이 서비스는 애플리케이션의 라이선스 유효 기간, 재시도 유예 기간, 최대 허용 가능 재시도 횟수 및 기타 설정의 권장 값을 제공합니다. 애플리케이션이 APK 확장 파일을 사용한다면 응답에는 파일 이름, 크기 및 URL도 포함됩니다. 서버는 라이선스 응답 'extras' 필드에 설정을 키-값 쌍으로 추가합니다.
모든 Policy
구현은 라이선스 응답에서 extras 설정을 추출하여 필요에 따라 사용할 수 있습니다. LVL 기본 Policy
구현인 ServerManagedPolicy
는 실용적인 구현 역할을 하고, 설정을 가져오고 저장하며 사용하는 방법을 보여줍니다.
추가 | 설명 |
---|---|
VT |
라이선스 유효성 타임스탬프 현재 캐시된 라이선스 응답이 만료되어 라이선스 서버에서 다시 확인해야 하는 날짜와 시간을 지정합니다. 라이선스 유효 기간에 관한 아래 섹션을 참조하세요. |
GT |
유예 기간 타임스탬프 응답 상태가 RETRY 인 경우에도 정책이 애플리케이션 액세스를 허용할 수 있는 기간의 종료를 지정합니다. 값은 서버에서 관리하고 일반적인 값은 5일 이상입니다. 아래에서 재시도 기간 및 최대 재시도 횟수에 관한 섹션을 참조하세요. |
GR |
최대 재시도 횟수 사용자의 애플리케이션 액세스를 거부하기 전에 Policy 가 허용해야 하는 연속 RETRY 라이선스 확인 횟수를 지정합니다.
값은 서버에서 관리하고 일반적인 값은 '10' 이상일 수 있습니다. 아래에서 재시도 기간 및 최대 재시도 횟수에 관한 섹션을 참조하세요. |
UT |
업데이트 타임스탬프. 애플리케이션의 최신 업데이트가 업로드되고 게시된 날짜와 시간을 지정합니다. 서버는 이 추가를 |
FILE_URL1 또는 FILE_URL2 |
확장 파일의 URL입니다. 1은 기본 파일이고 2는 패치 파일입니다. HTTP를 통해 파일을 다운로드하는 데 사용합니다. |
FILE_NAME1 또는 FILE_NAME2 |
확장 파일의 이름입니다. 1은 기본 파일이고 2는 패치 파일입니다. 기기에 파일을 저장할 때 이 이름을 사용해야 합니다. |
FILE_SIZE1 또는 FILE_SIZE2 |
바이트 단위의 파일 크기입니다. 1은 기본 파일이고 2는 패치 파일입니다. 다운로드를 지원하고, 다운로드하기 전에 기기의 공유 저장공간 위치에 충분한 공간을 사용할 수 있는지 확인하는 데 사용합니다. |
라이선스 유효 기간
Google Play 라이선스 서버는 다운로드된 모든 애플리케이션의 라이선스 유효 기간을 설정합니다. 이 기간은 애플리케이션의 라이선스 Policy
에서 애플리케이션의 라이선스 상태가 변경되지 않고 캐시 가능한 것으로 간주되어야 하는 시간 간격을 표시합니다. 라이선스 서버는 모든 라이선스 확인의 응답에 유효 기간을 포함하고 VT
키에 따라 응답에 유효성 종료 타임스탬프를 추가로 제공합니다. Policy
는 VT 키 값을 추출하고 이것을 사용하여 유효 기간이 만료될 때까지 라이선스를 다시 확인하지 않고 애플리케이션 액세스를 조건부로 허용합니다.
라이선스 유효성은 라이선스 서버에서 라이선스 상태를 다시 확인해야 하는 경우 라이선스 Policy
에 알립니다. 애플리케이션이 실제로 사용을 위한 라이선스를 부여받았는지 암시하는 것은 아닙니다. 즉, 애플리케이션의 라이선스 유효 기간이 만료되면 애플리케이션을 사용할 수 있는 라이선스가 더 이상 없다는 의미가 아니라 Policy
가 서버에서 라이선스 상태를 다시 확인해야 한다는 것을 나타낼 뿐입니다. 또한 라이선스 유효 기간이 만료되지 않는 한 Policy
가 초기 라이선스 상태를 로컬에 캐시하고 서버에 새로운 라이선스 확인을 전송하는 대신 캐시된 라이선스 상태를 반환하는 것이 허용됩니다.
라이선스 서버는 Google Play에서 유료 애플리케이션에 제공하는 환불 기간에 애플리케이션이 라이선스를 올바르게 시행하도록 돕는 수단으로 유효 기간을 관리합니다. 애플리케이션을 구매했는지, 구매했다면 얼마나 오래됐는지에 기반하여 유효 기간을 설정합니다. 특히 서버는 다음과 같이 유효 기간을 설정합니다.
- 유료 애플리케이션의 경우 서버는 애플리케이션이 환불될 수 있는 한 라이선스 응답이 유효한 상태로 유지되도록 초기 라이선스 유효 기간을 설정합니다. 애플리케이션의 라이선스
Policy
는 초기 라이선스 확인 결과를 캐시할 수 있으며 유효 기간이 만료될 때까지 라이선스를 다시 확인할 필요가 없습니다. - 애플리케이션을 더 이상 환불할 수 없을 때 서버는 더 긴 유효 기간(일반적으로 며칠)을 설정합니다.
- 무료 애플리케이션의 경우 서버는 유효 기간을 매우 높은 값(
long.MAX_VALUE
)으로 설정합니다. 이렇게 설정하면Policy
가 유효성 타임스탬프를 로컬에 캐시한 경우 향후 애플리케이션의 라이선스 상태를 다시 확인할 필요가 없게 됩니다.
ServerManagedPolicy
구현은 추출된 타임스탬프(mValidityTimestamp
)를 기본 조건으로 사용하여 사용자의 애플리케이션 액세스를 허용하기 전에 서버에서 라이선스 상태를 다시 확인할지 판단합니다.
재시도 기간 및 최대 재시도 횟수
경우에 따라 시스템 또는 네트워크 상태로 인해 애플리케이션의 라이선스 확인이 라이선스 서버에 도달하지 못하거나 서버의 응답이 Google Play 클라이언트 애플리케이션에 도달하지 못할 수 있습니다. 예를 들어 비행기에 탔을 때와 같이 사용 가능한 셀 네트워크나 데이터 연결이 없을 때 또는 네트워크 연결이 불안정하거나 셀 신호가 약할 때 사용자가 애플리케이션을 실행할 수 있습니다.
네트워크 문제로 라이선스 확인이 방해되거나 중단되면 Google Play 클라이언트는 RETRY
응답 코드를 Policy
의 processServerResponse()
메서드에 반환하여 애플리케이션에 알립니다. 시스템 문제의 경우(예: 애플리케이션이 Google Play의 ILicensingService
구현과 바인딩될 수 없는 경우) LicenseChecker
라이브러리 자체에서 RETRY
응답 코드로 정책 processServerResponse()
메서드를 호출합니다.
일반적으로 RETRY
응답 코드는 라이선스 확인의 완료를 방해하는 오류가 발생했다고 애플리케이션에 알리는 신호입니다.
Google Play 서버로 애플리케이션은 재시도 '유예 기간'과 권장되는 최대 재시도 횟수를 설정하여 오류 상황에서도 라이선스를 관리할 수 있습니다. 서버는 이러한 값을 모든 라이선스 확인 응답에 포함하고 GT
및 GR
키 아래에 추가로 삽입합니다.
애플리케이션 Policy
는 GT
및 GR
추가를 추출하여 다음과 같이 조건부로 애플리케이션 액세스를 허용하는 데 사용할 수 있습니다.
RETRY
응답이 발생하는 라이선스 확인의 경우Policy
는RETRY
응답 코드를 캐시하고RETRY
응답 횟수를 늘려야 합니다.- 재시도 유예 기간이 아직 활성화되어 있거나 최대 재시도 횟수에 도달하지 않았다면
Policy
는 사용자의 애플리케이션 액세스를 허용해야 합니다.
ServerManagedPolicy
는 위에서 설명한 대로 서버에서 제공한 GT
및 GR
값을 사용합니다. 아래 예에서는 allow()
메서드에서 재시도 응답의 조건부 처리를 보여줍니다. RETRY
응답 횟수는 여기 표시되지 않은 processServerResponse()
메서드에서 유지됩니다.
Kotlin
fun allowAccess(): Boolean { val ts = System.currentTimeMillis() return when(lastResponse) { LICENSED -> { // Check if the LICENSED response occurred within the validity timeout. ts <= validityTimestamp // Cached LICENSED response is still valid. } RETRY -> { ts < lastResponseTime + MILLIS_PER_MINUTE && // Only allow access if we are within the retry period // or we haven't used up our max retries. (ts <= retryUntil || retryCount <= maxRetries) } else -> false } }
Java
public boolean allowAccess() { long ts = System.currentTimeMillis(); if (lastResponse == LicenseResponse.LICENSED) { // Check if the LICENSED response occurred within the validity timeout. if (ts <= validityTimestamp) { // Cached LICENSED response is still valid. return true; } } else if (lastResponse == LicenseResponse.RETRY && ts < lastResponseTime + MILLIS_PER_MINUTE) { // Only allow access if we are within the retry period // or we haven't used up our max retries. return (ts <= retryUntil || retryCount <= maxRetries); } return false; }