ASP.NET 服务器控制许可

 

Nikhil Kothari
Microsoft ASP.NET 团队

范达纳·达特耶
自由撰稿人

2003 年 7 月

适用于:
    Microsoft® ASP.NET

总结:了解 ASP.NET 服务器控件的许可要求,并了解可与 .NET Framework 1.0 和 1.1 版一起使用的 ASP.NET 控制许可实现。 可以扩展实现以创建自定义服务器端许可方案。 ) (23 页打印页

下载 ASPNETControlLicensing.msi

背景

本文假定熟悉 Microsoft® ASP.NET 编程和 ASP.NET 服务器控件创作。

本文是 Nikhil Kothari 和 Vandana Datye (Microsoft Press (ISBN 0-7356-1582-9) 的《 开发 Microsoft ASP.NET 服务器控件和组件 》©一书中许可内容的更详细阐述。保留所有权利) 。 内容在 Microsoft Press 的许可下使用。 有关该书的详细信息,请参阅 https://www.microsoft.com/mspress/books/5728.asp

目录

简介
ASP.NET 服务器控制许可要求
许可的控制演练
.NET Framework许可体系结构
ASP.NET 服务器控制许可基础结构
扩展默认许可方案
即将过期的许可证方案
加密许可证方案
许可实施清单
结论

简介

Microsoft .NET Framework具有内置的可扩展许可体系结构,支持所有托管组件(包括业务对象、Windows 窗体控件和 ASP.NET 服务器控件)的设计时和运行时许可。 本文基于该体系结构提供一个许可实现,该实现专门针对 ASP.NET 控件进行了优化,可以扩展以创建自定义许可方案,例如:

  • 一个简单的许可方案,仅检查是否存在有效的许可证数据,以确定是否启用控件。
  • 按使用情况许可方案,其中许可证在特定的使用计数后过期。 此方案可用于控件的演示版本。 许可证过期后,应用程序开发人员可以注册 (并购买) 控件,并接收未过期的许可证。
  • 一种许可方案,仅当请求来自特定客户端计算机(例如本地计算机)时才在页面中启用 ASP.NET 服务器控件。 此方案可用于实现控件的试用版。
  • 一种许可方案,它依赖于加密来防止应用程序开发人员欺骗许可证数据。

ASP.NET 服务器控制许可要求

ASP.NET 服务器控制许可方案必须满足以下要求:

  • 支持非编译方案。 ASP.NET Web 应用程序通常使用动态编译模型,因此没有与应用程序关联的预编译程序集。 许可机制不应依赖于在应用程序程序集中查找嵌入为程序集资源的许可证。
  • 支持运行时许可。 页面开发人员使用可视化设计时工具和简单的文本编辑器来开发其页面。 许可机制不能依赖于设计时检查,必须提供运行时验证。 此外,运行时许可实现不应依赖于任何 (可选) 设计时许可实现。
  • 支持许可证缓存机制。 理想情况下,每个应用程序只能检索一次许可证数据,而不是每个页面请求,因为检索逻辑可能涉及昂贵的操作,例如打开文件和解密信息。 应在第一次需要许可证时创建许可证,并缓存许可证以供在服务器上后续重复使用。 每次使用缓存许可证实现基于使用情况的许可方案时,仍可以验证这些许可证。
  • 支持 XCOPY 部署。 ASP.NET 允许页面开发人员只需在其网络上跨计算机复制文件即可部署其 Web 应用程序。 许可方案不应依赖于注册表或阻止简单 XCOPY 部署的其他计算机特定资源。

为简单起见,我们在前面的列表中使用了术语“服务器控件”。 但是,许可要求适用于所有 ASP.NET 服务器组件。 同样,本文所述的 ASP.NET 控制许可方案也适用于其他 ASP.NET 服务器组件。

许可的控制演练

控制许可涉及三个关键元素:

  • 控件中的代码以支持许可
  • 许可证数据
  • 一个类,用于检查许可证数据、颁发许可证,并在后续使用控件时验证许可证

许可的服务器控件

LicensedLabel以下列表中的服务器控件派生自 ASP.NET System.Web.UI.WebControls.Label 控件,并向其添加许可支持。 以粗体显示的代码提供许可功能。

// LicensedLabel.cs
//
using System;
using System.ComponentModel;
using System.Web.UI.WebControls;

namespace LicensedControls {

    [
    LicenseProvider(typeof(ServerLicenseProvider))
    ]
    public class LicensedLabel : Label {

        public LicensedLabel() {
            LicenseManager.Validate(typeof(LicensedLabel));
        }
    }
}

此示例演示了必须对任何服务器组件代码进行以下添加才能支持许可:

  • 在控件的构造函数中,调用 System.ComponentModel.LicenseManager 类的静态 Validate 方法,并将组件的类型作为 参数传递。 如果控件没有有效的许可证,LicenseManagerValidate 方法将引发 System.ComponentModel.LicenseException。 或者,在构造函数中,可以调用 LicenseManager 类的静态 IsValid 方法,该方法不会引发异常。 如果要启用控件, (在没有有效许可证的情况下将控件称为剥离版本) ,请调用 IsValid 方法。
  • System.ComponentModel.LicenseProviderAttribute 元数据属性应用于组件,并将许可证提供程序的类型 (派生自对组件强制许可的 System.ComponentModel.LicenseProvider) 。 本文的 ASP.NET 服务器控制许可基础结构部分介绍了控件 ServerLicenseProvider的许可证提供程序LicensedLabel的实现。

如图 1 所示,为支持许可而必须对控件进行的更改很少。 真正的许可功能驻留在许可证提供程序类中,稍后将进行介绍。

如果在Windows 窗体控件中实现了许可,你可能会惊讶于LicensedLabel没有释放其许可证。 这是因为LicensedLabel使用在服务器上缓存许可证的许可证提供程序。

许可证数据

许可证数据提供的信息由许可体系结构验证并合并到许可证中。 可以通过多种不同方式提供许可证数据 (,例如到期日期、使用情况计数或唯一密钥) 。 许可证数据的类型和位置由特定的许可方案决定。 通常,在扩展名为 .lic 的文件中提供许可证数据。 图 1 中控件的LicensedLabel许可证数据位于名为 LicensedControls.LicensedLabel.lic 的文件中,该文件仅包含文本“LicensedControls.LicensedLabel 已获得许可”。

在页面上使用许可控件

随本文示例代码一起提供的自述文件文档介绍了如何生成示例。

在页面中使用 LicensedLabel 控件

  1. 将包含LicensedLabel控件) 的 LicensedControls 程序集 (复制到应用程序的 \Bin 目录。 如果使用 Microsoft Visual Studio® .NET 并在 Web 应用程序项目中添加了对 LicensedControls 项目的引用,则不需要执行此步骤。
  2. 将 LicensedControls.LicensedLabel.lic 文件复制到应用程序的 Licenses\LicensedControls\1.0.0.0.0 目录。

现在应该能够从应用程序中的任何页面使用 控件。

以下代码显示了使用 LicensedLabel 控件的页面。

<%@ Page language="c#" %>
<%@ Register TagPrefix="lc" Assembly="LicensedControls" 
  Namespace="LicensedControls" %>
<html>
  <head>
    <title>LicensedLabel Sample</title>
  </head>
  <body>
    <form method="post" runat="server" ID="Form1">
      <p>
        <lc:LicensedLabel runat="server" id="LicensedLabel1" Text="Hello 
          World!" />
      </p>
    </form>
  </body>
</html>

若要查看许可操作,请删除 LicensedControls.LicensedLabel.lic 文件或将其移动到其他位置。 重新生成应用程序或进行一些会导致应用程序重启的更改。 需要此步骤才能清除由 ServerLicenseProvider 管理的许可证缓存,该许可证提供程序是在控件的元数据中指定的LicensedLabel。 在浏览器中请求 LicensedLabelTest.aspx 页。 该页面将生成如下图所示的错误。

Aa479017.aspnetcontrollicensing01 (en-us,MSDN.10) .gif

图 1. LicensedLabelTest.aspx 页面尝试在没有有效许可证的情况下使用 LicensedLabel 时生成的错误

.NET Framework许可体系结构

下图 (图 2) 演示了.NET Framework的许可体系结构。 它显示了页面尝试实例化LicensedLabel上一部分中所述的控件时发生的main步骤。 尽管实际步骤位于服务器控件的上下文中,但该图显示了构成.NET Framework许可体系结构的类,以及任何运行时许可方案通用的关键步骤。 许可证提供商执行的确切步骤特定于提供商实现的特定许可方案。 例如,如图中显示的许可证缓存功能是特定于 ServerLicenseProvider的,如本文 ASP.NET 服务器控制许可基础结构部分所述。 以粗体显示的类是.NET Framework类,以斜体显示的类是我们实现的派生类。

Aa479017.aspnetcontrollicensing02 (en-us,MSDN.10) .gif

图 2. .NET Framework的许可体系结构

强制执行控件许可main步骤如下:

  1. 许可控件在其构造函数中调用静态 System.ComponentModel.LicenseManager.Validate 方法。 (或者,控件可以在其构造函数中调用静态 LicenseManager.IsValid 方法。在这种情况下,返回类型将与图中所示的类型不同,并且不会在) 引发异常。
  2. LicenseManager.Validate 方法检查组件的元数据,并从应用于组件的 LicenseProviderAttribute 属性获取许可证提供程序的类型。 许可证提供程序类必须派生自 System.ComponentModel.LicenseProvider 类。
  3. LicenseManager 实例化在 System.ComponentModel.LicenseProviderAttribute 元数据属性中指定的类型的许可证提供程序类,将组件的类型传递给许可证提供程序,并指示是在设计时还是在运行时使用该组件。
  4. 许可证提供程序在许可证缓存中查找组件的许可证。 如果找到许可证,许可证提供程序将验证许可证。 请注意,许可证缓存查找和许可证存储不是通用要求,而是特定于 ServerLicenseProvider我们实现的许可证提供程序。
    1. (首次仅) 许可证提供程序提取并验证许可证数据。 如果数据无效,许可证提供程序将引发 System.ComponentModel.LicenseException 异常。
    2. (首次仅) 如果许可证数据有效,许可证提供程序 (派生自 System.ComponentModel.License) 的类创建许可证。 此外,许可证提供程序会验证许可证,如果许可证有效,则将其存储在许可证缓存中。
  5. 许可证提供程序将有效的许可证返回给许可证管理器,或引发许可证异常。
  6. LicenseManager.Validate 方法返回有效的许可证,或将许可证异常传递给调用代码。
  7. 如果 LicenseManager 返回有效的许可证,则构造函数将初始化 类并实例化控件。 否则,构造函数会将 LicenseException 异常传递给尝试实例化控件的代码。 本文许可的控制演练部分的图中显示的错误消息由 ASP.NET 运行时生成,当页面使用没有有效许可证的许可控件时,该运行时处理控件的构造函数传递的许可证异常。

首次创建是指在 Web 应用程序中首次实例化组件。 如果在应用程序 (同一请求或后续请求) 的同一页或不同页面上创建组件的另一个实例,则不会发生步骤 4a 和 4b。 出于性能原因,ServerLicenseProvider缓存每个应用程序的许可证 (而不是每页或每个会话) 。

.NET Framework中的许可体系结构旨在使非法使用组件) 难以 (但并非不可能。 如果用户尝试在没有许可证的情况下使用许可组件,许可会向用户表明该组件被非法使用。 许可不会使组件防篡改。

.NET Framework中的许可体系结构由 System.ComponentModel 命名空间中的以下四个类提供:

  • LicenseManager:此类负责实例化组件的元数据中指定的许可证提供程序。 许可证管理器还会将组件的类型和许可上下文传递给许可证提供程序,这指示组件是在设计时还是在运行时使用。 除了在组件的构造函数中调用 LicenseManager 类的 ValidateIsValid 方法外,无需了解有关 LicenseManager 的其他详细信息。
  • LicenseProviderAttribute:此属性指定负责创建和验证组件的许可证的许可证提供程序的类型。 必须将此属性应用于支持许可的组件。
  • LicenseProvider:此类包含任何许可方案的核心功能,即颁发和验证许可证的任务。 若要实现许可支持,必须通过派生自 LicenseProvider 类创建自定义许可证提供程序,并实现基类的抽象 GetLicense 方法以提供许可逻辑。 本文的下一部分介绍了许可证提供程序 ServerLicenseProvider的实现。
  • 许可证:此类是许可证数据的软件抽象, (如 包含一个 .lic 文件) 。 若要实现许可证类,必须从 License 类派生并实现基类的抽象 LicenseKey 属性。 我们将实现一个许可证类,该类适用于ServerLicenseProvider本文的下一部分。

.NET Framework在 System.ComponentModel.LicFileLicenseProvider 类中提供许可证提供程序的默认实现。 此许可证提供程序依赖于可视化设计器 ((如 Visual Studio .NET) )在设计时提取许可数据,并在编译期间将许可证数据作为资源嵌入使用许可组件的应用程序的程序集中。 LicFileLicenseProvider 类可由Windows 窗体控件使用,但它不满足本文 ASP.NET 服务器控件许可要求部分中所述 ASP.NET 服务器控件许可的要求。

ASP.NET 服务器控制许可基础结构

本部分介绍我们的核心许可实现,它为 ASP.NET 服务器控制许可方案提供管道。 该实现包含在两个类中,ServerLicenseProvider ServerLicense,它们分别派生自 LicenseProviderLicense 类。 我们希望在将来的 ASP.NET 版本中,在类似的一组基类中提供对许可的内置支持。 可以使用和扩展ServerLicenseProviderServerLicense类,而无需检查其源代码,就像在.NET Framework中使用类一样。 但是,为了完整性,本部分包含这些类的代码。

ServerLicenseProvider 类

The ServerLicenseProvider 类派生自 LicenseProvider 并重写 GetLicense 方法以实现核心服务器控制许可要求。ServerLicenseProvider满足本文前面所述的服务器许可要求-无编译模型、运行时许可支持、许可证缓存和 XCOPY 部署。ServerLicenseProvider实现默认许可方案,该方案从 .lic 文本文件加载许可证数据,这些文件存储在 Web 应用程序的根目录中名为“许可证”的目录中。 此目录下的结构基于程序集的名称和版本,如本文的许可控制演练部分所示。 默认方案依赖于在 .lic 文件中查找以下文本:“<组件的> 全类型名称已获得许可。

// ServerLicenseProvider.cs
//
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Diagnostics;
using System.Globalization;
using System.Web;

namespace LicensedControls {

    public class ServerLicenseProvider : LicenseProvider {

        private static readonly ServerLicenseCollector LicenseCollector = 
          new ServerLicenseCollector();

        protected virtual ServerLicense CreateLicense(Type type, string 
          key) {
            return new ServerLicense(type, key);
        }

        protected virtual ServerLicense CreateEmptyLicense(Type type) {
            return new ServerLicense(type, String.Empty);

        }

        public override License GetLicense(LicenseContext context, Type 
          type, object instance, bool allowExceptions) {
            ServerLicense license = null;
            string errorMessage = null;

            if (context.UsageMode == LicenseUsageMode.Designtime) {
                license = CreateEmptyLicense(type);
            }
            else {
                license = LicenseCollector.GetLicense(type);

                if (license == null) {
                    string licenseData = GetLicenseData(type);
                    if ((licenseData != null) && (licenseData.Length != 
                      0)) {

                        if (ValidateLicenseData(type, licenseData)) {
                            ServerLicense newLicense = CreateLicense(type, 
                              licenseData);

                            if (ValidateLicense(newLicense, out 
                              errorMessage)) {
                                license = newLicense;
                                LicenseCollector.AddLicense(type, 
                                  license);
                            }
                        }
                    }
                }
                else {
                    if (ValidateLicense(license, out errorMessage) == 
                      false) {
                        license = null;
                    }
                }
            }

            if (allowExceptions && (license == null)) {
                if (errorMessage == null) {
                    throw new LicenseException(type);
                }
                else {
                    throw new LicenseException(type, instance, 
                      errorMessage);
                }
            }

            return license;
        }

        protected virtual string GetLicenseData(Type type) {
            string licenseData = null;
            Stream licenseStream = null;

            try {
                licenseStream = GetLicenseDataStream(type);

                if (licenseStream != null) {
                    StreamReader sr = new StreamReader(licenseStream);

                    licenseData = sr.ReadLine();
                }
            }
            finally {
                if (licenseStream != null) {
                    licenseStream.Close();
                    licenseStream = null;
                }
            }

            return licenseData;
        }

        protected virtual Stream GetLicenseDataStream(Type type) {
            string assemblyPart = type.Assembly.GetName().Name;
            string versionPart = 
              type.Assembly.GetName().Version.ToString();
            string relativePath = "~/licenses/" + assemblyPart + "/" + 
              versionPart + "/" + type.FullName + ".lic";

            string licensesFile = null;
            try {
                licensesFile = 
                  HttpContext.Current.Server.MapPath(relativePath);
                if (File.Exists(licensesFile) == false) {
                    licensesFile = null;
                }
            }
            catch {
            }

            if (licensesFile != null) {
                return new FileStream(licensesFile, FileMode.Open, 
                  FileAccess.Read, FileShare.Read);
            }
            return null;
        }

        protected virtual bool ValidateLicense(ServerLicense license, out 
          string errorMessage) {
            errorMessage = null;
            return true;
        }

        protected virtual bool ValidateLicenseData(Type type, string 
          licenseData) {
            string licenseKey = type.FullName + " is licensed.";
            return String.Compare(licenseKey, licenseData, true, 
              CultureInfo.InvariantCulture) == 0;
        }


        private sealed class ServerLicenseCollector {

            private IDictionary _collectedLicenses;

            public ServerLicenseCollector() {
                _collectedLicenses = new HybridDictionary();
            }

            public void AddLicense(Type objectType, ServerLicense license) 
              {
                if (objectType == null) {
                    throw new ArgumentNullException("objectType");
                }
                if (license == null) {
                    throw new ArgumentNullException("objectType");
                }

                _collectedLicenses[objectType] = license;
            }

            public ServerLicense GetLicense(Type objectType) {
                if (objectType == null) {
                    throw new ArgumentNullException("objectType");
                }

                if (_collectedLicenses.Count == 0) {
                    return null;
                }

                return (ServerLicense)_collectedLicenses[objectType];
            }

            public void RemoveLicense(Type objectType) {
                if (objectType == null) {
                    throw new ArgumentNullException("objectType");
                }

                _collectedLicenses.Remove(objectType);
            }
        }
    }
}

ServerLicense 类

ServerLicense 是与之 ServerLicenseProvider兼容的基许可证类。ServerLicense派生自 License 类,实现基类的抽象 LicenseKey 属性。

// ServerLicense.cs
//
using System;
using System.ComponentModel;
using System.Diagnostics;

namespace LicensedControls {

    public class ServerLicense : License {

        private Type _type;
        private string _key;

        public ServerLicense(Type type, string key) {
            _type = type;
            _key = key;
        }

        public override string LicenseKey {
            get {
                return _key;
            }
        }

        public Type LicensedType {
            get {
                return _type;
            }
        }

        public override void Dispose() {
        }
    }
}

扩展默认许可方案

ServerLicenseProviderServerLicense类实现简单的默认许可方案。 可以扩展这些类,以使用更复杂的验证逻辑实现自己的自定义许可方案。 若要实现自定义许可方案,请从ServerLicenseProvider派生自己的许可证提供程序,并重写其一个或多个虚拟方法,如下表所述。 若要完成许可方案的实现,可能还需要实现一ServerLicense个从许可证提供商派生并与之合作的许可证类。

下表描述了 类中ServerLicenseProvider定义的可重写方法。

可重写的 ServerLicenseProvider 方法 说明
protected virtual ServerLicense CreateLicense(Type type, string key) 创建并返回 ServerLicense 指定许可类型的实例。 字符串参数是与类型关联的已验证许可证数据。 派生许可证提供程序可以重写此方法以返回派生许可证。 有关示例,请参阅 ExpiringLicenseProvider 本文的即将过期的许可证方案部分中所述。
protected virtual ServerLicense CreateEmptyLicense(Type type) 创建并返回一个与实际许可证数据不关联的空 ServerLicense 实例。 ServerLicenseProvider 使用空许可证来启用设计时使用。 派生许可证提供程序可以重写此方法以返回空的派生许可证。
protected virtual string GetLicenseData(Type type)

通过读取流中的第一行数据,从许可证流中检索许可证数据。 派生的许可证提供程序可以重写此方法,以便从不基于流的其他许可证存储中读取数据。
protected virtual Stream GetLicenseDataStream(Type type)

打开用于读取许可证数据的流。 此方法还包含构成相应 .lic 文件的虚拟路径的逻辑。 派生的许可证提供程序可以重写此方法以返回自定义流实现,如本文加密许可证方案部分的示例所示 EncryptedLicenseProvider
protected virtual bool ValidateLicense(ServerLicense license, out string errorMessage)

验证缓存的许可证。 每次请求许可证时都会进行此验证。 派生的许可证提供程序可以重写此方法,以实现其自己的验证逻辑。 ExpiringLicenseProvider 本文的“即将到期的许可证方案”部分中所述的示例实现了基于使用情况的许可方案。
protected virtual bool ValidateLicenseData(Type type, string licenseData)

在创建许可证之前验证许可证数据,如果数据有效,则返回 true。 派生的许可证提供程序可以通过重写此方法来实现自定义验证规则。

即将过期的许可证方案

本部分和以下部分演示如何扩展我们提供的默认许可实现,以创建自定义许可方案。 本部分方案中显示的即将过期的许可证方案通过在控件使用指定次数后禁用控件来扩展默认方案。 此方案可用于控件的演示版本。

即将过期的许可证方案在 类中ExpiringLicenseProvider实现。

// ExpiringLicenseProvider.cs
//
using System;
using System.Diagnostics;
using System.Globalization;

namespace LicensedControls {

    public class ExpiringLicenseProvider : ServerLicenseProvider {

        protected override ServerLicense CreateLicense(Type type, string 
          key) {
            string[] parts = key.Split(';');
            Debug.Assert(parts.Length == 2);

            return new ExpiringLicense(type, key, Int32.Parse(parts[1], 
              CultureInfo.InvariantCulture));
        }

        protected override bool ValidateLicense(ServerLicense license, out 
          string errorMessage) {
            errorMessage = null;

            ExpiringLicense testLicense = (ExpiringLicense)license;

            testLicense.IncrementUsageCounter();
            if (testLicense.IsExpired) {
                errorMessage = "The License for " + 
                  testLicense.LicensedType.Name + " has expired.";
                return false;
            }
            return true;
        }

        protected override bool ValidateLicenseData(Type type, string 
          licenseData) {
            string[] parts = licenseData.Split(';');

            if (parts.Length == 2) {
                return base.ValidateLicenseData(type, parts[0]);
            }
            else {
                return false;
            }
        }
    }
}

The ExpiringLicenseProvider 类派生自ServerLicenseProvider并重写基类的以下方法:

  • CreateEmptyLicense 创建与实际许可证数据不关联的许可证。 空许可证适合在设计时使用。
  • CreateLicense 使用 .lic 文件中指定的使用计数创建许可证。
  • ValidateLicense 以检查许可证是否已过期。ValidateLicense在进行检查之前递增使用情况计数。
  • ValidateLicenseData 检查 .lic 文件中的文本字符串包含两个部分,用分号分隔:“<全类型名称>已获得许可。;<使用情况计数>”。

The ExpiringLicenseProvider 类使用ExpiringLicense类作为其许可证类型。

// ExpiringLicense.cs
//
using System;

namespace LicensedControls {

    public class ExpiringLicense : ServerLicense {

        private int _usageLimit;
        private int _usageCount;

        public ExpiringLicense(Type type, string key, int usageLimit) : 
          base(type, key) {
            _usageLimit = usageLimit;
        }

        public bool IsExpired {
            get {
                return _usageCount > _usageLimit;
            }
        }

        public void IncrementUsageCounter() {
            _usageCount++;
        }
    }
}

The ExpiringLicense 类实现以下逻辑:

  • 定义两个用于存储使用限制和使用情况计数的专用字段。
  • IncrementUsageCounter公开 方法,该方法在每次访问对象时ExpiringLicense递增使用计数。
  • IsExpired公开 属性,该属性通过将使用情况计数与使用限制进行比较来确定许可证是否已过期。

请注意,由于缓存了服务器许可证,因此可以自行保存ExpiringLicense使用情况计数。ServerLicenseProvider仅为给定组件类型创建许可证的一个实例,并将许可证保存在其内部许可证缓存中。 但是,将数据存储在缓存的许可证对象中并非万无一失。 例如,如果重新启动应用程序,则使用量计数将重置为零,从而允许比许可证数据中指定的总使用限制更大。 同样,如果应用程序部署在服务器场上,则所有服务器上的总使用计数可能会超过预期使用限制。 但是,中ExpiringLicense实现的逻辑是可接受的,因为许可的目标是防止未经授权使用组件,而不会对应用程序的性能产生重大影响。

以下代码显示了一个控件,ExpiringLicenseProvider该控件将 类用于其许可方案。

// ExpiringLicensedLabel.cs
//
using System;
using System.ComponentModel;
using System.Web.UI.WebControls;

namespace LicensedControls {

    [
    LicenseProvider(typeof(ExpiringLicenseProvider))
    ]
    public class ExpiringLicensedLabel : Label {

        public ExpiringLicensedLabel() {
            LicenseManager.Validate(typeof(ExpiringLicensedLabel));
        }
    }
}

的 .lic 文件中ExpiringLicensedLabel的文本是“LicensedControls.ExpiringLicensedLabel is licensed.;5".

在明文文件中指定许可证时,如本示例所示,应用程序开发人员可以轻松地欺骗文件中的数据。 可以通过加密许可证数据来提高许可方案的安全性,如下一部分所示。

加密许可证方案

本部分扩展默认方案,以实现加密的许可证提供程序,该提供程序在读取时解密加密的许可证数据。 以下代码显示了EncryptedLicenseProvider类。

// EncryptedLicenseProvider.cs
//
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;

namespace LicensedControls {

    public class EncryptedLicenseProvider : ServerLicenseProvider {

        // This is a 64-bit key generated from the string
        // "5FB281F6".
        //
        private static readonly byte[] encryptionKeyBytes =
            new byte[] { 0x35, 0x46, 0x42, 0x32, 0x38, 0x31, 0x46, 0x36 };

        protected override Stream GetLicenseDataStream(Type type) {
            Stream baseStream = base.GetLicenseDataStream(type);

            if (baseStream == null) {
                return null;
            }

            DESCryptoServiceProvider des = new DESCryptoServiceProvider();
            des.Key = encryptionKeyBytes;
            des.IV = encryptionKeyBytes;

            ICryptoTransform desDecryptor = des.CreateDecryptor();
            return new CryptoStream(baseStream, desDecryptor, 
              CryptoStreamMode.Read);
        }
    }
}

The EncryptedLicenseProvider 类派生自ServerLicenseProvider并重写GetLicenseDataStream方法,该方法创建 一个 System.IO.Stream 对象来读取许可证数据。EncryptedLicenseProvider使用 System.Security.Cryptography.CryptoStream 包装此流,以在读取许可证数据时解密许可证数据。 此示例中使用的 CryptoStream 采用数据加密标准 (DES) 加密算法,该算法具有 64 位加密密钥,该密钥作为专用encryptionKeyBytes字段嵌入在EncryptedLicenseProvider类本身中。 允许以这种方式嵌入密钥,因为许可体系结构旨在使破坏许可证更加困难(并非不可能)。 Win32 安全 API 提供更复杂的机制来存储加密密钥;但是,这些技术通常不适合 XCOPY 部署。

ServerLicense 作为 的许可证类 EncryptedLicenseProvider就足够了;因此没有为此许可证提供程序实现派生许可证类。

EncryptedLicensedLabel以下代码中显示的控件使用 中 EncryptedLicenseProvider class实现的许可方案。

// EncryptedLicensedLabel.cs
//

using System;
using System.ComponentModel;
using System.Web.UI.WebControls;

namespace LicensedControls {

    [
    LicenseProvider(typeof(EncryptedLicenseProvider))
    ]
    public class EncryptedLicensedLabel : Label {

        public EncryptedLicensedLabel() {
            LicenseManager.Validate(typeof(EncryptedLicensedLabel));
        }
    }
}

下图显示了与 EncryptedLicensedLabel 关联的 .lic 文件。

Aa479017.aspnetcontrollicensing03 (en-us,MSDN.10) .gif

图 3. LicensedControls.EncryptedLicensedLabel 文件的内容

此文件的内容已使用嵌入 EncryptedLicenseProvider的密钥加密。 文件中的数据是字符串“LicensedControls.EncryptedLicensedLabel is licensed”。但是,由于加密,它不可人工读取。 本文的示例文件中提供了加密工具EncLicGen.exe及其源代码 EncryptedLicenseGenerator.cs。

加密使许可方案更加可靠。 例如,可以在即将到期的许可证方案中使用加密来提高其安全性。 可以加密自己的数据和用户注册信息的组合,而不是像此示例那样加密固定字符串。

许可实施清单

以下列表描述了在为服务器控件实现许可时需要执行的任务:

  • 实现派生自ServerLicenseProvider类的许可证提供程序,并重写基类的一个或多个虚拟方法,以便为许可方案提供逻辑。
  • 实现派生自ServerLicense类的许可证类,并与实现的许可证提供程序一起使用 (请参阅前面的项目符号) 。 如果ServerLicense适用于许可方案 (,则不需要此步骤,如本文上一节) 中的示例所示EncryptedLicenseProvider
  • 通过应用 LicenseProviderAttribute 元数据属性并将实现的许可证提供程序的类型传入组件, (看到第一个项目符号) ,为组件添加许可支持。 此外,从组件的构造函数调用 LicenseManager.Validate
  • 为组件创建许可数据。 可以将此数据保存在 .lic 文件中,或者以许可方案所需的任何其他形式保存。
  • (可选) 创建用于生成许可证数据的工具。 例如,加密工具EncLicGen.exe,随本文的示例文件一起提供。 如果要创建用于商业分发的组件,你会发现拥有一个可自动创建许可证数据的工具很有用。
  • 如果组件的许可证数据包含在 (文件(如 .lic 文件) )中,请向应用程序开发人员说明如何创建许可方案所需的目录结构,并将许可证文件复制到 Web 应用程序中的必要位置。

结论

本文探讨了 ASP.NET 服务器控制许可和.NET Framework许可体系结构的要求。 我们演示了三个关键元素如何提供许可功能:控件中用于支持许可的代码、许可证数据以及颁发和验证许可证的许可证提供程序类。 我们提供了一个默认 ASP.NET 服务器控制许可实现,它为服务器控制许可方案创建管道,并演示了如何通过创建不同的自定义许可方案来扩展此默认许可实现。

© Microsoft Corporation. 保留所有权利。