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]

... 46  47  [48]  49  50  51  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12738정성태7/28/202115938오류 유형: 745. Azure Active Directory - Client credential flows must have a scope value with /.default suffixed to the resource identifier (application ID URI).
12737정성태7/28/202115034오류 유형: 744. Azure Active Directory - The resource principal named api://...[client_id]... was not found in the tenant
12736정성태7/28/202115298오류 유형: 743. Active Azure Directory에서 "API permissions"의 권한 설정이 "Not granted for ..."로 나오는 문제
12735정성태7/27/202115175.NET Framework: 1081. C# - Azure AD 인증을 지원하는 데스크톱 애플리케이션 예제(Windows Forms) [2]파일 다운로드1
12734정성태7/26/202131998스크립트: 20. 특정 단어로 시작하거나/끝나는 문자열을 포함/제외하는 정규 표현식 - Look-around
12733정성태7/23/202119901.NET Framework: 1081. Self-Contained/SingleFile 유형의 .NET Core/5+ 실행 파일을 임베딩한다면? [1]파일 다운로드2
12732정성태7/23/202113335오류 유형: 742. SharePoint - The super user account utilized by the cache is not configured.
12731정성태7/23/202115372개발 환경 구성: 584. Add Internal URLs 화면에서 "Save" 버튼이 비활성화 된 경우
12730정성태7/23/202116800개발 환경 구성: 583. Visual Studio Code - Go 코드에서 입력을 받는 경우
12729정성태7/22/202115285.NET Framework: 1080. xUnit 단위 테스트에 메서드/클래스 수준의 문맥 제공 - Fixture
12728정성태7/22/202115307.NET Framework: 1079. MSTestv2 단위 테스트에 메서드/클래스/어셈블리 수준의 문맥 제공
12727정성태7/21/202116492.NET Framework: 1078. C# 단위 테스트 - MSTestv2/NUnit의 Assert.Inconclusive 사용법(?) [1]
12726정성태7/21/202116266VS.NET IDE: 169. 비주얼 스튜디오 - 단위 테스트 선택 시 MSTestv2 외의 xUnit, NUnit 사용법 [1]
12725정성태7/21/202114731오류 유형: 741. Failed to find the "go" binary in either GOROOT() or PATH
12724정성태7/21/202117640개발 환경 구성: 582. 윈도우 환경에서 Visual Studio Code + Go (Zip) 개발 환경 [1]
12723정성태7/21/202114530오류 유형: 740. SharePoint - Alternate access mappings have not been configured 경고
12722정성태7/20/202114303오류 유형: 739. MSVCR110.dll이 없어 exe 실행이 안 되는 경우
12721정성태7/20/202115447오류 유형: 738. The trust relationship between this workstation and the primary domain failed. - 세 번째 이야기
12720정성태7/19/202114722Linux: 43. .NET Core/5+ 응용 프로그램의 Ubuntu (Debian) 패키지 준비
12719정성태7/19/202113912오류 유형: 737. SharePoint 설치 시 "0x800710D8 The object identifier does not represent a valid object." 오류 발생
12718정성태7/19/202113917개발 환경 구성: 581. Windows에서 WSL로 파일 복사 시 root 소유권으로 적용되는 문제파일 다운로드1
12717정성태7/18/202114112Windows: 195. robocopy에서 파일의 ADS(Alternate Data Stream) 정보 복사를 제외하는 방법
12716정성태7/17/202115245개발 환경 구성: 580. msbuild의 Exec Task에 robocopy를 사용하는 방법파일 다운로드1
12715정성태7/17/202122439오류 유형: 736. Windows - MySQL zip 파일 버전의 "mysqld --skip-grant-tables" 실행 시 비정상 종료 [1]
12714정성태7/16/202115798오류 유형: 735. VCRUNTIME140.dll, MSVCP140.dll, VCRUNTIME140.dll, VCRUNTIME140_1.dll이 없어 exe 실행이 안 되는 경우
12713정성태7/16/202117206.NET Framework: 1077. C# - 동기 방식이면서 비동기 규약을 따르게 만드는 Task.FromResult파일 다운로드1
... 46  47  [48]  49  50  51  52  53  54  55  56  57  58  59  60  ...