Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
닷넷 System.ComponentModel.LicenseManager를 이용한 라이선스 적용

찾아보니, 다음과 같은 글이 눈에 띄는군요.

Licensed Applications using the .NET Framework
; http://www.codeguru.com/columns/experts/article.php/c5469

구현 방식은 예상외로 무척 간단했습니다. 라이선스를 걸어 보호하려는 클래스가 있다면 아래와 같이 LicenseProvider 특성을 걸어주고,

[LicenseProvider(typeof(MyLicenseProvider))]
public class Class1
{
    public Class1()
    {
        LicenseManager.Validate(typeof(Class1), this);
    }
}

다음으로, LicenseProvider를 우리가 구현해 주면 됩니다.

public class MyLicenseProvider : System.ComponentModel.LicenseProvider
{
    public override License GetLicense(LicenseContext context, 
        Type type, object instance, bool allowExceptions) 
    {
        return null;
    }
}

GetLicense 내의 코드 구현은 그야말로 천차만별이라고 보면 되겠습니다. 중요한 것은, 라이선스가 올바르다고 판단이 되면 GetLicense 메서드에서 License 개체(추상 클래스이므로 그것을 상속받은 개체)를 반환해주면 됩니다.




위의 글에서 보면 닷넷 BCL에서 기본 제공되는 LicFileLicenseProvider를 설명해 주고 있는데요. 이건 매우 간단한 거라서, 해당 클래스가 라이선스가 걸린 것임을 나타내는 정도로만 쓰일 뿐 어떤 강제적인 수단을 동원하는 것은 아닙니다. 이 때문에, LicFileLicenseProvider로 보호된 클래스가 있다면 실행 파일과 같은 위치에 "{네임스페이스를 포함한 클래스 이름}.lic"라는 파일명을 두는 것으로 '라이선스 검사'에 통과할 수 있습니다.

제가 테스트 한 예제를 기준으로 한번 살펴볼까요?

우선, 다음과 같은 예제 클래스를 두고,

===== CommercialLibrary 라이브러리 프로젝트의 Class1.cs 파일 내용 =====

namespace CommercialLibrary
{
    [LicenseProvider(typeof(LicFileLicenseProvider))]
    public class LicFileLicensedClass1
    {
        public LicFileLicensedClass1()
        {
            License lic = LicenseManager.Validate(typeof(LicFileLicensedClass1), this);
            if (lic == null)
            {
                Console.WriteLine("lic == null");
            }
            else
            {
                Console.WriteLine(lic);
            }
        }

        public void Do()
        {
            Console.WriteLine("licensed library is activated!");
        }
    }
}

이런 경우, CommercialLibrary.dll을 사용하는 ConsoleApplication1.exe 측에서는 다음과 같은 이름의 lic 파일을 exe 파일과 같은 폴더에 두어야 합니다.

CommercialLibrary.LicFileLicensedClass1.lic

그리고, 그 파일의 내용에는 "{네임스페이스를 포함한 클래스 이름} is a licensed component." 같은 형식의 문자열이 포함되어 있어야 합니다. 예제에서는 다음과 같습니다.

CommercialLibrary.LicFileLicensedClass1 is a licensed component.

보시는 것처럼, 다소 허무하긴 하지만 LicFileLicenseProvider 파일은 상속이 가능하고 IsKeyValid와 GetKey 메서드를 재정의할 수 있기 때문에 약간의 부가적인 코드만 구현해 준다면 상용 라이브러리를 보호하기 위한 어느 정도의 기준은 만족시킬 수 있습니다.




상용 컴포넌트를 사용해 보신 분들은 lc.exe 컴파일러와 친숙하실 텐데요.

System.ComponentModel.LicenseException (MSBuild의 lc.exe 빌드 과정 생략)
; https://www.sysnet.pe.kr/2/0/518

64비트 OS에서의 ChartFX 라이선스 문제
; https://www.sysnet.pe.kr/2/0/537

기왕 여기까지 온 김에, 그동안 무심코 써왔던 lc.exe에 대해 "Licensed Applications using the .NET Framework" 글을 통해서 이해를 해보는 것도 좋겠지요.

사실, lc.exe를 굳이 사용하지 않아도 상관없습니다. 위에서 설명한 것처럼 lic 파일을 실행 파일과 같이 두는 것은 그리 어려운 일이 아니기 때문입니다. 문제는, lic 파일에 2개 이상의 구성요소 정의가 불가능하다는 데 있습니다. 즉, 라이선스를 걸어놓은 구성 요소 10개를 사용중이라면 10개의 lic 파일들이 exe 파일과 동일한 폴더에 배포되어야 한다는 약간의 불편함 정도가 있는 것입니다.

바로 이런 문제를 해결하기 위해 lc.exe를 사용합니다. lc.exe를 이용하면 라이선스 정보를 '하나의 파일에 여러개 정의하는 것'이 가능하고, 게다가 그렇게 정의된 파일을 어셈블리 내의 리소스로 포함하는 것이 가능합니다. 즉, 배포할 때 lic 파일들이 전혀 필요없고 오로지 exe 파일 하나만을 복사하는 것으로 끝낼 수 있다는 장점이 있습니다.

lc.exe 실행 방법을 알아보기 위해 MSDN 도움말을 보면,

License Compiler (Lc.exe)
; https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-2.0/ha0k3c9f(v=vs.80)

/complist 옵션에 주는 파일(ConsoleApplication1Lic.txt)의 내용을 다음과 같이 구성하도록 설명하고 있는데요. (바로 이 파일 내에 여러 개의 구성 요소를 지정해 주는 것이 가능합니다.)

CommercialLibrary.LicFileLicensedClass1, CommercialLibrary.dll

그리고, 위의 파일에 들어가는 구성요소에 대한 lic 파일(이 예제에서는 CommercialLibrary.LicFileLicensedClass1.lic)이 exe 파일과 같은 폴더에 있어야 합니다.

애석하게도 그렇게 모든 준비를 마치고, 실제로 실행해 보면 다음과 같은 식으로 LC0003 오류가 발생합니다.

D:\...[생략]...\bin\Debug>lc /target:ConsoleApplication1.exe /complist:ConsoleApplication1Lic.txt /i:CommercialLibrary.DLL
Microsoft (R) .NET License Compiler
[Microsoft .Net Framework, Version 4.0.30319.225]
Copyright (c) Microsoft Corporation.  All rights reserved.


Processing complist 'ConsoleApplication1Lic.txt'...
ConsoleApplication1Lic.txt (1) : error LC0003 : Unable to resolve type 'CommercialLibrary.LicFileLicensedClass1, Commerc
ialLibrary.DLL'

이거 원인 찾느라고 엄청난 시간을 소비했는데요. ^^; 아래의 예제에 소개된 것을 보고서야 오류를 바로 잡을 수 있었습니다.

.NET Licensing
; http://windowsforms.net/articles/Licensing.aspx

즉, ConsoleApplication1Lic.txt 파일의 내용에서 DLL 확장자를 지정해서는 안되는 것이었습니다.

CommercialLibrary.LicFileLicensedClass1, CommercialLibrary

아래는 실행 결과입니다.

D:\...[생략]...\bin\Debug>lc /target:ConsoleApplication1.exe /complist:ConsoleApplication1Lic.txt /i:CommercialLibrary.DLL
Microsoft (R) .NET License Compiler
[Microsoft .Net Framework, Version 4.0.30319.225]
Copyright (c) Microsoft Corporation.  All rights reserved.


Processing complist 'ConsoleApplication1Lic.txt'...
System.ComponentModel.LicFileLicenseProvider+LicFileLicense
Creating Licenses file consoleapplication1.exe.licenses...

이렇게 생성된 "consoleapplication1.exe.licenses" 파일은 닷넷 어셈블리에 '리소스' 자원으로 포함될 수 있습니다. 하지만, 이렇게 되면 빌드 절차가 복잡해지기 때문에 사실 이런 유형으로는 거의 사용되질 않고 있습니다.

MSbuild에서는 이런 작업을 간단하게 할 수 있도록 프로젝트에 licx라는 특별한 확장자를 가진 파일이 있다면 그것을 '/complist'에 지정되는 파일이라고 여기고 자동으로 lc.exe 컴파일러를 호출하여 .licenses 파일을 생성하고 어셈블리의 리소스로 포함까지 시켜줍니다.

실제로 LicFileLicenseProvider를 기준으로 테스트를 원한다면, 다음과 같은 식으로 솔루션을 구성해 주면 됩니다.

license_manager_compiler_1.png

즉, 사용하려는 응용 프로그램(ConsoleApplication1.exe)측에서 licx 파일을 추가하고 내용은 다음과 같이 구성합니다.

CommercialLibrary.LicFileLicensedClass1, CommercialLibrary

위에서 lc.exe 컴파일을 명령행으로 사용하는 방법을 설명하면서 언급했지만, lc.exe는 /complist 옵션에 지정될 파일 뿐만 아니라, 원래의 구성요소에 대한 lic 파일들이 필요하므로, 라이브러리(CommercialLibrary.dll) 측에서는 CommercialLibrary.LicFileLicensedClass1.lic 파일의 "Copy to output Directory" 속성을 "Copy if newer" 설정으로 해두시면 '편리합니다.' (물론, 배포시에는 lic 파일들은 없어도 됩니다.)

결국, 빌드된 ConsoleApplication1.exe 파일을 .NET Reflector로 확인해 보면 다음과 같이 명령행에서 lc.exe를 실행했을 때 처럼 "consoleapplication1.exe.licenses 리소스 파일이 들어있는 것을 확인할 수 있습니다.

license_manager_compiler_2.png

LicenseProvider와 lc.exe에 대해서 이 정도 알아봤으니, 이제 자신만의 라이선스 체계를 만들어서 LicFileLicenseProvider를 확장하고 싶어질 텐데요. 소스 코드를 구현하기 전에 아래의 글들 중에서 자신이 생각하고 있던 것이 있다면,... ^^ 그대로 가져다 쓰는 것도 좋겠지요.

C#: LicenseManager (and Other uses): Get a Mobile Devices Unique ID 
; http://skysigal.xact-solutions.com/Blog/tabid/427/entryid/1962/C-LicenseManager-and-Other-uses-Get-a-Mobile-Devices-Unique-ID.aspx

C#: Developing a LicenseProvider/License: Getting the Motherboard ID. 
; http://skysigal.xact-solutions.com/Blog/tabid/427/entryid/1963/C-Developing-a-LicenseProvider-License-Getting-the-Motherboard-ID.aspx

C#: Developing a LicenseProvider/License: Getting the Manufacturer’s UID.
; http://skysigal.xact-solutions.com/Blog/tabid/427/entryid/1964/C-Developing-a-LicenseProvider-License-Getting-the-Manufacturer-rsquo-s-UID.aspx

C#: Developing a LicenseProvider/License: Getting the CPU’s ID. 
; http://skysigal.xact-solutions.com/Blog/tabid/427/entryid/1965/C-Developing-a-LicenseProvider-License-Getting-the-CPU-rsquo-s-ID.aspx

참고로, 첨부한 파일은 제가 테스트한 프로젝트 소스입니다.



[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/9/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2012-08-08 02시26분
[전호진] 유용한 정보 도움 많이 되었습니다 ^^
[guest]

... 61  62  63  64  65  66  [67]  68  69  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
12261정성태7/9/202019388VS.NET IDE: 148. 윈도우 10에서 .NET Core 응용 프로그램을 리눅스 환경에서 실행하는 2가지 방법 - docker, WSL 2 [5]
12260정성태7/8/202017178.NET Framework: 926. C# - ETW를 이용한 ThreadPool 스레드 감시파일 다운로드1
12259정성태7/8/202016386오류 유형: 627. nvlddmkm.sys의 BAD_POOL_HEADER BSOD 문제 [1]
12258정성태7/8/202020250기타: 77. DataDog APM 간략 소개
12257정성태7/7/202016561.NET Framework: 925. C# - ETW를 이용한 Monitor Enter/Exit 감시파일 다운로드1
12256정성태7/7/202017652.NET Framework: 924. C# - Reflection으로 변경할 수 없는 readonly 정적 필드 [4]
12255정성태7/6/202018407.NET Framework: 923. C# - ETW(Event Tracing for Windows)를 이용한 Finalizer 실행 감시파일 다운로드1
12254정성태7/2/202016944오류 유형: 626. git - REMOTE HOST IDENTIFICATION HAS CHANGED!
12253정성태7/2/202019415.NET Framework: 922. C# - .NET ThreadPool의 Local/Global Queue파일 다운로드1
12252정성태7/2/202021234.NET Framework: 921. C# - I/O 스레드를 사용한 비동기 소켓 서버/클라이언트파일 다운로드2
12251정성태7/1/202019994.NET Framework: 920. C# - 파일의 비동기 처리 유무에 따른 스레드 상황 [1]파일 다운로드2
12250정성태6/30/202022140.NET Framework: 919. C# - 닷넷에서의 진정한 비동기 호출을 가능케 하는 I/O 스레드 사용법 [1]파일 다운로드1
12249정성태6/29/202017614오류 유형: 625. Microsoft SQL Server 2019 RC1 Setup - 설치 제거 시 Warning 26003 오류 발생
12248정성태6/29/202015408오류 유형: 624. SQL 서버 오류 - service-specific error code 17051
12247정성태6/29/202017354.NET Framework: 918. C# - 불린 형 상수를 반환값으로 포함하는 3항 연산자 사용 시 단축 표현 권장(IDE0075) [2]파일 다운로드1
12246정성태6/29/202018512.NET Framework: 917. C# - USB 관련 ETW(Event Tracing for Windows)를 이용한 키보드 입력을 감지하는 방법
12245정성태6/24/202018950.NET Framework: 916. C# - Task.Yield 사용법 (2) [2]파일 다운로드1
12244정성태6/24/202018961.NET Framework: 915. ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용 [1]파일 다운로드1
12243정성태6/23/202015384VS.NET IDE: 147. Visual C++ 프로젝트 - .NET Core EXE를 "Debugger Type"으로 지원하는 기능 추가
12242정성태6/23/202016965오류 유형: 623. AADSTS90072 - User account '...' from identity provider 'live.com' does not exist in tenant 'Microsoft Services'
12241정성태6/23/202020163.NET Framework: 914. C# - Task.Yield 사용법파일 다운로드1
12240정성태6/23/202021716오류 유형: 622. 소켓 바인딩 시 "System.Net.Sockets.SocketException: An attempt was made to access a socket in a way forbidden by its access permissions" 오류 발생
12239정성태6/21/202019730Linux: 30. (윈도우라면 DLL에 속하는) .so 파일이 텍스트로 구성된 사례 [1]
12238정성태6/21/202018111.NET Framework: 913. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 라이브러리
12237정성태6/20/202017634.NET Framework: 912. 리눅스 환경의 .NET Core에서 "test".IndexOf("\0")가 0을 반환
12236정성태6/19/202018359오류 유형: 621. .NET Standard 대상으로 빌드 시 dynamic 예약어에서 컴파일 오류 - error CS0656: Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'
... 61  62  63  64  65  66  [67]  68  69  70  71  72  73  74  75  ...