본문 바로가기
개발/Unity

Unity) Android) 불법복제방지 - 라이선스 적용(Google Play Application License Verification )

by 테샤르 2022. 4. 7.

불법복제방지 - 라이선스 적용(Google Play Application License Verification )

LVL을 테스트하기 위해서는 어플리케이션이 안드로이드 마켓에 '유료' 로 업로드가되어야하고
내부 앱 공유로는 유료앱은 노출
이 되지 않는다.

 

공개 또는 비공개 테스트 트랙에 게시해야 확인이 된다.

반응형

Android LVL : [ 링크 ]

 

라이선스 설정  |  Android 개발자  |  Android Developers

애플리케이션에 라이선스 확인을 추가하기 전에 Google Play 게시 계정, 개발 환경 및 구현 확인에 필요한 테스트 계정을 설정해야 합니다. 아직 게시자가 없는 경우

developer.android.com

UnityTechonologies에서 제공하는 LVL를 확인해봤는데

여러 삽질을 했었다.

Unity-Technologies - GooglePlayLicenseVerification

 

GitHub - Unity-Technologies/GooglePlayLicenseVerification: Unity Android plugin example on how to interface the Google Play Lice

Unity Android plugin example on how to interface the Google Play License Verification (LVL) servers - GitHub - Unity-Technologies/GooglePlayLicenseVerification: Unity Android plugin example on how ...

github.com

 결론은..

RSA를 사용하는 과정에서 Momo로 사용해야 사용이 가능하다. (레거시 버전인듯하다.)

(Mono.Security.ASN1 : http://docs.go-mono.com/index.aspx?link=T%3aMono.Security.ASN1)

지금 본인이 필요한 환경은 IL2CPP의 Script Backend 상황이라서 사용하지 못했다.

 

 

Android Google Play Licensing Library를  Native Code로 처리하기(.aar)로 결정했다.

 

Android Studio 에서 Android SDK ->Google Play Licensing Library를 설치한다.

 

C:\Users\seo\AppData\Local\Android\Sdk\extras\google\market_licensing\library\AndroidManifest.xml

 

 

신규 프로젝트에서 Import Module로 해당 경로를 임포트한다.

Google Licensing Validator 참조 : [링크]

 

GitHub - google/play-licensing: Google Play licensing service client library

Google Play licensing service client library. Contribute to google/play-licensing development by creating an account on GitHub.

github.com

<응답 코드>

응답 코드 및 설명은 다음과 같다.

LICENSED = Hex: 0x0100, Decimal: 256
NOT_LICENSED = Hex: 0x0231, Decimal: 561
RETRY = Hex: 0x0123, Decimal: 291
LICENSED_OLD_KEY = Hex: 0x2, Decimal: 2
ERROR_NOT_MARKET_MANAGED = Hex: 0x3, Decimal: 3
ERROR_SERVER_FAILURE = Hex: 0x4, Decimal: 4
ERROR_OVER_QUOTA = Hex: 0x5, Decimal: 5
ERROR_CONTACTING_SERVER = Hex: 0x101, Decimal: 257
ERROR_INVALID_PACKAGE_NAME = Hex: 0x102, Decimal: 258 
ERROR_NON_MATCHING_UID = Hex: 0x103, Decimal: 259

 

응답코드 설명
LICENSED 사용자에게 애플리케이션의 라이선스가 부여되었습니다.
사용자가 애플리케이션을 구매했거나 사용자에게 애플리케이션의 알파 또는 베타 버전을 다운로드하고 설치할 권한이 부여되었습니다.
LICENSED_OLD_KEY 사용자에게 애플리케이션의 라이선스가 부여되었지만 다른 키로 서명되어 업데이트된 사용 가능한 애플리케이션 버전이 있습니다.
(설치된 애플리케이션 버전에서 사용하는 키 쌍이 무효하거나 손상되었다고 표시할 수 있습니다. 애플리케이션은 필요한 경우 액세스를 허용하거나 사용자에게 업그레이드를 할 수 있다고 알리고 업그레이드할 때까지 추가 사용을 제한할 수 있습니다.)
NOT_LICENSED 사용자에게 애플리케이션의 라이선스가 부여되지 않았습니다.
ERROR_CONTACTING_SERVER 로컬 오류 - Google Play 애플리케이션이 라이선스 서버에 도달할 수 없었으며 네트워크 가용성 문제가 원인일 수 있습니다.
ERROR_SERVER_FAILURE 서버 오류 - 서버가 라이선스를 위한 애플리케이션의 키 쌍을 로드할 수 없었습니다.
ERROR_INVALID_PACKAGE_NAME 로컬 오류 - 애플리케이션이 기기에 설치되지 않은 패키지의 라이선스 확인을 요청했습니다.
(일반적으로 개발 오류로 인해 발생합니다.)
ERROR_NON_MATCHING_UID 로컬 오류 - UID(패키지, 사용자 ID 쌍)가 요청하는 애플리케이션의 UID와 일치하지 않는 패키지의 라이선스 확인을 애플리케이션이 요청했습니다.
(일반적으로 개발 오류로 인해 발생합니다.)
ERROR_NOT_MARKET_MANAGED 서버 오류 - 애플리케이션(패키지 이름)을 Google Play에서 인식하지 못했습니다.
(애플리케이션이 Google Play를 통해 게시되지 않았거나 라이선스 구현에 개발 오류가 있다고 표시할 수 있습니다.)
ERROR_OVER_QUOTA
라이선스 서버가 할당량을 초과하여 이 장치와 통신하는 것을 거부합니다.
반응형

Java 코드를 작성한다.

package com.shc.unity.plugin;

import android.app.Activity;
import android.os.Handler;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;

import com.unity3d.player.UnityPlayer;

import com.google.android.vending.licensing.AESObfuscator;
import com.google.android.vending.licensing.LicenseChecker;
import com.google.android.vending.licensing.LicenseCheckerCallback;
import com.google.android.vending.licensing.ServerManagedPolicy;



public class UnityPlugin
{

    private static UnityPlugin _instance;
    private static Activity _context;


    private static final String BASE64_PUBLIC_KEY = ""; // TODO replace with your own key
    private LicenseCheckerCallback mLicenseCheckerCallback;
    private LicenseChecker mChecker;
    private static final byte[] SALT = new byte[] {
            -46, 65, 30, -128, -103, -57, 74, -64, 51, 88, -95, -45, 77, -117, -36, -113, -11, 32, -64,
            89
    };
    private Handler mHandler;
    private boolean bInitalize =false;
    private PluginCallback callback = null;
    public static UnityPlugin instance()
    {
        if(_instance == null)
        {
            _instance = new UnityPlugin();
            _context = UnityPlayer.currentActivity;
        }

        Log.i("[LVL]", "UnityPlugin instance");
        return _instance;
    }

    public void showToast(String _messag)
    {
        _context.runOnUiThread(new Runnable(){
            @Override
            public void run(){
                Toast.makeText(_context, _messag, Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void Initalize(String _packageName, PluginCallback _callback)
    {
        Log.i("[LVL]", "Initalize Start");
        String deviceId = Settings.Secure.getString(_context.getContentResolver(), Settings.Secure.ANDROID_ID);
        mLicenseCheckerCallback = new MyLicenseCheckerCallback();
        // Construct the LicenseChecker with a policy.
        mChecker = new LicenseChecker(
                _context, new ServerManagedPolicy(_context,
                //new AESObfuscator(SALT, _context.getPackageName(), deviceId)),
                new AESObfuscator(SALT, _packageName, deviceId)),
                BASE64_PUBLIC_KEY);

        bInitalize = true;
        callback =_callback;
        Log.i("[LVL]", "Initalize End");
    }

    private void doCheck() {
        mChecker.checkAccess(mLicenseCheckerCallback);
    }

    public void CheckLicense(String _pacakageName, PluginCallback callback)
    {
        Log.i("[LVL]", "CheckLicense :: "+ _pacakageName);
        if(false == bInitalize)
            Initalize(_pacakageName, callback);

        doCheck();
    }

    public void unitySendMessage(String _objectName, String _methodName, String _param)
    {
        UnityPlayer.UnitySendMessage(_objectName, _methodName, _param);
    }


    private class MyLicenseCheckerCallback implements LicenseCheckerCallback {
        public void allow(int policyReason) {

            Log.i("[LVL]", "allow :: "+policyReason);
            showToast("allow :: "+policyReason);
            callback.Allow(policyReason);
            if (_context.isFinishing()) {
                // Don't update UI if Activity is finishing.
                return;
            }

        }

        public void dontAllow(int policyReason) {

            Log.i("[LVL]", "dontAllow :: "+ policyReason);
            showToast("dontAllow :: "+ policyReason);
            callback.DontAllow(policyReason);
            if (_context.isFinishing()) {
                // Don't update UI if Activity is finishing.
                return;
            }

            // Should not allow access. In most cases, the app should assume
            // the user has access unless it encounters this. If it does,
            // the app should inform the user of their unlicensed ways
            // and then either shut down the app or limit the user to a
            // restricted set of features.
            // In this example, we show a dialog that takes the user to a deep
            // link returned by the license checker.
            // If the reason for the lack of license is that the service is
            // unavailable or there is another problem, we display a
            // retry button on the dialog and a different message.
            //_context.displayDialog(policyReason == Policy.RETRY);
        }

        public void applicationError(int errorCode) {

            Log.i("[LVL]", "applicationError :: "+ errorCode);
            showToast("applicationError :: "+ errorCode);
            callback.Error(errorCode);
            if (_context.isFinishing()) {
                // Don't update UI if Activity is finishing.
                return;
            }
            // This is a polite way of saying the developer made a mistake
            // while setting up or calling the license checker library.
            // Please examine the error code and fix the error.
        }
    }
}
반응형

실제 라이선스에 대한 테스트를 해보면 다음과 같다.

<로그로 확인해본 결과>

정상적으로 응답코드가 오는것을 확인했다.

2022-04-27 23:44:48.266 24447-24712/? I/Unity: [AndroidPluginCallback] DontAllow : 561
    AndroidPluginCallback:DontAllow(Int32)
    System.Reflection.MonoMethod:Invoke(Object, BindingFlags, Binder, Object[], CultureInfo)
    UnityEngine.AndroidJavaProxy:Invoke(String, Object[])
    UnityEngine._AndroidJNIHelper:InvokeJavaProxyMethod(AndroidJavaProxy, IntPtr, IntPtr)
 
★정상적인 계정 로그인할경우(라이선스 테스트에 등록된 계정)
2022-04-27 23:46:25.697 26870-27266/? I/Unity: [AndroidPluginCallback] Allow : 256
    AndroidPluginCallback:Allow(Int32)
    System.Reflection.MonoMethod:Invoke(Object, BindingFlags, Binder, Object[], CultureInfo)
    UnityEngine.AndroidJavaProxy:Invoke(String, Object[])
    UnityEngine._AndroidJNIHelper:InvokeJavaProxyMethod(AndroidJavaProxy, IntPtr, IntPtr)
 
★다른 계정(아내님)
 
2022-04-27 23:48:59.064 30025-30497/? I/Unity: [AndroidPluginCallback] Error : 3
    AndroidPluginCallback:Error(Int32)
    System.Reflection.MonoMethod:Invoke(Object, BindingFlags, Binder, Object[], CultureInfo)
    UnityEngine.AndroidJavaProxy:Invoke(String, Object[])
    UnityEngine._AndroidJNIHelper:InvokeJavaProxyMethod(AndroidJavaProxy, IntPtr, IntPtr)

 

Android 라이선스 개요(LVL) : [링크]

 

라이선스 개요  |  Android 개발자  |  Android Developers

Google Play 라이선스는 애플리케이션이 신뢰할 수 있는 Google Play 라이선스 서버에 쿼리하여 현재 기기 사용자에게 애플리케이션의 라이선스가 부여되었는지 확인하는 네트워크 기반 서비스입니

developer.android.com

 

★ Android Google Licencing 적용 방법 : 

https://stackoverflow.com/questions/62852617/google-play-licencing-for-an-android-app-in-android-studio

 

Google Play Licencing for an Android app in Android Studio

I am trying to set up Google Play Licencing for an app in Android studio for an app written in Kotlin. My goal is to avoid users sharing APK files without purchasing my app through the store. What ...

stackoverflow.com

 

★ Lorg/apache/http/client/utils/URLEncodedUtils; 이슈 해결방법

https://stackoverflow.com/questions/50461881/java-lang-noclassdeffounderrorfailed-resolution-of-lorg-apache-http-protocolve

 

java.lang.NoClassDefFoundError:failed resolution of :Lorg/apache/http/ProtocolVersion

I'm encountering this error when I use Android Studio to build my app. The APK is compiled, but when I attempt to run the app on Android P emulator, it will crash and throw the following error. Ple...

stackoverflow.com

    java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/client/utils/URLEncodedUtils;
        at com.google.android.vending.licensing.ServerManagedPolicy.decodeExtras(ServerManagedPolicy.java:266)
        at com.google.android.vending.licensing.ServerManagedPolicy.processServerResponse(ServerManagedPolicy.java:113)

 

★ 구글 계정 없을때 LVL에서 에러터지는것 해결방법

https://stackoverflow.com/questions/17453194/android-licensechecker-crashes-with-null-pointer-exception

 

Android LicenseChecker crashes with null pointer exception

It seems that if an Android phone is not logged in to Google Play, a checkAccess-call will throw a NullPointerException and eventually crash the application: // user not logged in to Google Play

stackoverflow.com

  java.lang.NullPointerException: Attempt to invoke virtual method 'byte[] java.lang.String.getBytes()' on a null object reference
        at com.google.android.vending.licensing.LicenseValidator.verify(LicenseValidator.java:99)
        at com.google.android.vending.licensing.LicenseChecker$ResultListener$2.run(LicenseChecker.java:236

 

 

 

★★★☆

 

반응형

댓글