라이선스 참조

LVL 클래스 및 인터페이스

표 1에는 Android SDK를 통해 사용 가능한 라이선스 확인 라이브러리(LVL)의 모든 소스 파일이 나열되어 있습니다. 모든 파일은 com.android.vending.licensing 패키지의 일부입니다.

표 1. LVL 라이브러리 클래스 및 인터페이스 요약

카테고리 이름 설명
라이선스 확인 및 결과 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에는 지원되는 모든 라이선스 응답 코드가 나열되어 있습니다. 일반적으로 애플리케이션은 이러한 응답 코드를 모두 처리해야 합니다. 기본적으로 LVL의 LicenseValidator 클래스는 이러한 응답 코드의 필요한 처리를 모두 제공합니다.

표 2. 라이선스 응답에서 Google Play 서버가 반환하는 응답 코드의 요약

응답 코드 설명 서명 여부 Extras 참고
LICENSED 사용자에게 애플리케이션의 라이선스가 부여되었습니다. 사용자가 애플리케이션을 구매했거나 사용자에게 애플리케이션의 알파 또는 베타 버전을 다운로드하고 설치할 권한이 부여되었습니다. VTGT, GR Policy 제약 조건에 따라 액세스를 허용합니다.
LICENSED_OLD_KEY 사용자에게 애플리케이션의 라이선스가 부여되었지만 다른 키로 서명되어 업데이트된 사용 가능한 애플리케이션 버전이 있습니다. VT, GT, GR, UT Policy 제약 조건에 따라 선택적으로 액세스를 허용합니다.

설치된 애플리케이션 버전에서 사용하는 키 쌍이 무효하거나 손상되었다고 표시할 수 있습니다. 애플리케이션은 필요한 경우 액세스를 허용하거나 사용자에게 업그레이드를 할 수 있다고 알리고 업그레이드할 때까지 추가 사용을 제한할 수 있습니다.

NOT_LICENSED 사용자에게 애플리케이션의 라이선스가 부여되지 않았습니다. 아니요 액세스를 허용하지 않습니다.
ERROR_CONTACTING_SERVER 로컬 오류 - Google Play 애플리케이션이 아마 네트워크 가용성 문제로 인해 라이선스 서버에 도달할 수 없었습니다. 아니요 Policy 재시도 한도에 따라 라이선스 확인을 재시도합니다.
ERROR_SERVER_FAILURE 서버 오류 - 서버가 라이선스를 위한 애플리케이션의 키 쌍을 로드할 수 없었습니다. 아니요 Policy 재시도 한도에 따라 라이선스 확인을 재시도합니다.
ERROR_INVALID_PACKAGE_NAME 로컬 오류 - 애플리케이션이 기기에 설치되지 않은 패키지의 라이선스 확인을 요청했습니다. 아니요 라이선스 확인을 재시도하지 마세요.

일반적으로 개발 오류로 인해 발생합니다.

ERROR_NON_MATCHING_UID 로컬 오류 - UID(패키지, 사용자 ID 쌍)가 요청하는 애플리케이션의 UID와 일치하지 않는 패키지의 라이선스 확인을 애플리케이션이 요청했습니다. 아니요 라이선스 확인을 재시도하지 마세요.

일반적으로 개발 오류로 인해 발생합니다.

ERROR_NOT_MARKET_MANAGED 서버 오류 - 애플리케이션(패키지 이름)을 Google Play에서 인식하지 못했습니다. 아니요 라이선스 확인을 재시도하지 마세요.

애플리케이션이 Google Play를 통해 게시되지 않았거나 라이선스 구현에 개발 오류가 있다고 표시할 수 있습니다.

참고: 테스트 환경 설정에 설명된 대로 응답 코드는 애플리케이션 개발자 및 등록된 테스트 사용자를 위해 Google Play Console을 통해 수동으로 재정의될 수 있습니다.

참고: 전에는 게시되지 않은 '초안' 버전을 업로드하여 앱을 테스트할 수 있었습니다. 이 기능은 더 이상 지원되지 않습니다. 대신 알파 또는 베타 배포 채널에 게시해야 합니다. 자세한 내용은 초안 앱이 더 이상 지원되지 않음을 참조하세요.

서버 응답 Extras

애플리케이션이 애플리케이션 환불 기간에 애플리케이션 액세스를 관리하는 것을 지원하고 기타 정보를 제공하기 위해 라이선스 서버에는 라이선스 응답의 여러 정보가 포함되어 있습니다. 특히 이 서비스는 애플리케이션의 라이선스 유효 기간, 재시도 유예 기간, 최대 허용 가능 재시도 횟수 및 기타 설정의 권장 값을 제공합니다. 애플리케이션이 APK 확장 파일을 사용한다면 응답에는 파일 이름, 크기 및 URL도 포함됩니다. 서버는 라이선스 응답 'extras' 필드에 설정을 키-값 쌍으로 추가합니다.

모든 Policy 구현은 라이선스 응답에서 extras 설정을 추출하여 필요에 따라 사용할 수 있습니다. LVL 기본 Policy 구현인 ServerManagedPolicy는 실용적인 구현 역할을 하고, 설정을 가져오고 저장하며 사용하는 방법을 보여줍니다.

표 3. 라이선스 응답에서 Google Play 서버가 제공한 라이선스 관리 설정 요약

Extra설명
VT 라이선스 유효성 타임스탬프 현재 캐시된 라이선스 응답이 만료되어 라이선스 서버에서 다시 확인해야 하는 날짜와 시간을 지정합니다. 라이선스 유효 기간에 관한 아래 섹션을 참조하세요.
GT 유예 기간 타임스탬프 응답 상태가 RETRY인 경우에도 정책이 애플리케이션 액세스를 허용할 수 있는 기간의 종료를 지정합니다.

값은 서버에서 관리하고 일반적인 값은 5일 이상입니다. 아래에서 재시도 기간 및 최대 재시도 횟수에 관한 섹션을 참조하세요.

GR 최대 재시도 횟수 사용자의 애플리케이션 액세스를 거부하기 전에 Policy가 허용해야 하는 연속 RETRY 라이선스 확인 횟수를 지정합니다.

값은 서버에서 관리하고 일반적인 값은 '10' 이상일 수 있습니다. 아래에서 재시도 기간 및 최대 재시도 횟수에 관한 섹션을 참조하세요.

UT 업데이트 타임스탬프. 애플리케이션의 최신 업데이트가 업로드되고 게시된 날짜와 시간을 지정합니다.

서버는 이 extra를 LICENSED_OLD_KEYS 응답에만 반환하여 사용자의 애플리케이션 액세스를 거부하기 전에 새로운 라이선스 키로 업데이트가 게시된 후 경과한 시간을 Policy에서 확인할 수 있습니다.

FILE_URL1 또는 FILE_URL2 확장 파일의 URL입니다. 1은 기본 파일이고 2는 패치 파일입니다. HTTP를 통해 파일을 다운로드하는 데 사용합니다.
FILE_NAME1 또는 FILE_NAME2 확장 파일의 이름입니다. 1은 기본 파일이고 2는 패치 파일입니다. 기기에 파일을 저장할 때 이 이름을 사용해야 합니다.
FILE_SIZE1 또는 FILE_SIZE2 바이트 단위의 파일 크기입니다. 1은 기본 파일이고 2는 패치 파일입니다. 다운로드를 지원하고 다운로드하기 전에 기기의 공유 저장공간 위치에 충분한 공간을 사용할 수 있는지 확인하는 데 사용합니다.

라이선스 유효 기간

Google Play 라이선스 서버는 다운로드된 모든 애플리케이션의 라이선스 유효 기간을 설정합니다. 이 기간은 애플리케이션의 라이선스 Policy에서 애플리케이션의 라이선스 상태가 변경되지 않고 캐시 가능한 것으로 간주되어야 하는 시간 간격을 표시합니다. 라이선스 서버는 모든 라이선스 확인의 응답에 유효 기간을 포함하고 VT 키에 따라 응답에 유효성 종료 타임스탬프를 extra로 추가합니다. Policy는 VT 키 값을 추출하고 이것을 사용하여 유효 기간이 만료될 때까지 라이선스를 다시 확인하지 않고 애플리케이션 액세스를 조건부로 허용합니다.

라이선스 유효성은 라이선스 서버에서 라이선스 상태를 다시 확인해야 하는 경우 라이선스 Policy에 알립니다. 애플리케이션이 실제로 사용을 위한한 라이선스를 부여받았는지 암시하려는 것은 아닙니다. 즉, 애플리케이션의 라이선스 유효 기간이 만료되면 애플리케이션을 사용할 수 있는 라이선스가 더 이상 없다는 의미가 아니라 Policy가 서버에서 라이선스 상태를 다시 확인해야 한다는 것을 나타낼 뿐입니다. 또한 라이선스 유효 기간이 만료되지 않는 한 Policy가 초기 라이선스 상태를 로컬에 캐시하고 서버에 새로운 라이선스 확인을 전송하는 대신 캐시된 라이선스 상태를 반환하는 것이 허용됩니다.

라이선스 서버는 Google Play에서 유료 애플리케이션에 제공하는 환불 기간에 애플리케이션이 라이선스를 올바르게 시행하도록 돕는 수단으로 유효 기간을 관리합니다. 애플리케이션을 구매했는지, 구매했다면 얼마나 오래됐는지에 기반하여 유효 기간을 설정합니다. 특히 서버는 다음과 같이 유효 기간을 설정합니다.

  • 유료 애플리케이션의 경우 서버는 애플리케이션이 환불될 수 있는 한 라이선스 응답이 유효한 상태로 유지되도록 초기 라이선스 유효 기간을 설정합니다. 애플리케이션의 라이선스 Policy는 초기 라이선스 확인 결과를 캐시할 수 있으며 유효 기간이 만료될 때까지 라이선스를 다시 확인할 필요가 없습니다.
  • 애플리케이션을 더 이상 환불할 수 없을 때 서버는 더 긴 유효 기간(일반적으로 며칠)을 설정합니다.
  • 무료 애플리케이션의 경우 서버는 유효 기간을 매우 높은 값(long.MAX_VALUE)으로 설정합니다. 이렇게 설정하면 Policy가 유효성 타임스탬프를 로컬에 캐시한 경우 향후 애플리케이션의 라이선스 상태를 다시 확인할 필요가 없게 됩니다.

ServerManagedPolicy 구현은 추출된 타임스탬프(mValidityTimestamp)를 기본 조건으로 사용하여 사용자의 애플리케이션 액세스를 허용하기 전에 서버에서 라이선스 상태를 다시 확인할지 판단합니다.

재시도 기간 및 최대 재시도 횟수

경우에 따라 시스템 또는 네트워크 상태로 인해 애플리케이션의 라이선스 확인이 라이선스 서버에 도달하지 못하거나 서버의 응답이 Google Play 클라이언트 애플리케이션에 도달하지 못할 수 있습니다. 예를 들어 비행기에 탔을 때와 같이 사용 가능한 셀 네트워크나 데이터 연결이 없을 때 또는 네트워크 연결이 불안정하거나 셀 신호가 약할 때 사용자가 애플리케이션을 실행할 수 있습니다.

네트워크 문제로 라이선스 확인이 방해되거나 중단되면 Google Play 클라이언트는 RETRY 응답 코드를 PolicyprocessServerResponse()메서드에 반환하여 애플리케이션에 알립니다. 시스템 문제의 경우(예: 애플리케이션이 Google Play의 ILicensingService 구현과 바인딩될 수 없는 경우) LicenseChecker 라이브러리 자체에서 RETRY 응답 코드로 정책 processServerResonse() 메서드를 호출합니다.

일반적으로 RETRY 응답 코드는 라이선스 확인의 완료를 방해하는 오류가 발생했다고 애플리케이션에 알리는 신호입니다.

Google Play 서버로 애플리케이션은 재시도 '유예 기간'과 권장되는 최대 재시도 횟수를 설정하여 오류 상황에서도 라이선스를 관리할 수 있습니다. 서버는 이러한 값을 모든 라이선스 확인 응답에 포함하고 GTGR 키 아래에 extras로 추가합니다.

애플리케이션 PolicyGTGR extras를 추출하여 다음과 같이 조건부로 애플리케이션 액세스를 허용하는 데 사용할 수 있습니다.

  • RETRY 응답이 발생하는 라이선스 확인의 경우 PolicyRETRY 응답 코드를 캐시하고 RETRY 응답 횟수를 늘려야 합니다.
  • 재시도 유예 기간이 아직 활성화되어 있거나 최대 재시도 횟수에 도달하지 않았다면 Policy는 사용자의 애플리케이션 액세스를 허용해야 합니다.

ServerManagedPolicy는 위에서 설명한 대로 서버에서 제공한 GTGR 값을 사용합니다. 아래 예에서는 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
    }
}

자바

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;
}