ASP.NET 服务器控件授权
摘要:了解 ASP.NET 服务器控件的授权要求,了解可用于 .NET 框架版本 1.0 和 1.1 的 ASP.NET 控件授权实现。该实现可以进行扩展,以创建自定义的服务器端授权方案。(23 页打印页)
![*](http://www.microsoft.com/library/gallery/templates/MNP2.Common/images/3squares.gif)
Nikhil Kothari Microsoft ASP.NET 小组
Vandana Datye 自由撰稿人
2003 年 7 月
适用于: Microsoft? ASP.NET
请下载 ASPNETControlLicensing.msi。
背景
本文假设读者熟悉 Microsoft? ASP.NET 编程和 ASP.NET 服务器控件创建。
Developing Microsoft ASP.NET Server Controls and Components (ISBN 0-7356-1582-9)(Microsoft Press,2002。保留所有权利。)一书中,关于授权部分内容更详细的阐述。该部分内容的使用已获得 Microsoft Press 的许可。有关该书的详细信息,请参阅 http://www.microsoft.com/mspress/books/5728.asp。
本页内容
![]() |
简介 |
![]() |
ASP.NET 服务器控件授权要求 |
![]() |
已授权控件演练 |
![]() |
.NET 框架授权结构 |
![]() |
ASP.NET 服务器控件授权基础结构 |
![]() |
扩展默认授权方案 |
![]() |
过期许可方案 |
![]() |
加密的许可方案 |
![]() |
授权实现核对清单 |
![]() |
小结 |
简介
Microsoft .NET 框架有一个内置的可扩展授权结构,支持所有托管组件(包括业务对象、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,并将它作为参数传递到组件的类型中。如果该控件没有有效许可,LicenseManager 的 Validate 方法将引发 System.ComponentModel.LicenseException。另一种方法是,在构造函数中,您可以调用 LicenseManager 类的静态方法 IsValid,这样就不会引发异常。如果您希望在没有有效许可的情况下启用控件(在简装版本上就是如此),请调用 IsValid 方法。 |
• |
将 System.ComponentModel.LicenseProviderAttribute 元数据属性应用于您的组件,并向它传递执行组件授权的许可提供程序(从 System.ComponentModel.LicenseProvider 派生的类)的类型。本文中 ASP.NET 服务器控件授权基础结构一节显示了 LicensedLabel 控件的许可提供程序 ServerLicenseProvider 的实现。 |
如图 1 所示,您为支持授权而必须对控件所做的更改是最小的。真正的授权功能在许可提供程序类中,稍后再说明这部分内容。
如果您已经在 Windows 窗体控件中实现了授权,您可能很惊奇地发现,LicensedLabel 不处置其许可。这是因为,LicensedLabel 使用一个在服务器上缓存许可的许可提供程序。
许可数据
许可数据提供由授权结构进行验证并合并到许可中的信息。您可以用许多不同的方式提供许可数据(如过期日期、使用计数或唯一密钥)。许可数据的类型和位置由特定的授权方案来指定。通常在扩展名为 .lic 的文件中提供许可数据。图 1 中的 LicensedLabel 控件的许可数据位于一个名为 LicensedControls.LicensedLabel.lic 的文件中,该文件只包含文本 "LicensedControls.LicensedLabel is licensed"。
在页面上使用已授权控件
随本文的代码示例提供的 ReadMe 文档描述了如何构建这些示例。
在页面中使用 LicensedLabel 控件
1.将 LicensedControls 程序集(包含 LicensedLabel 控件)复制到应用程序的 \Bin 目录。如果您使用的是 Microsoft Visual Studio? .NET 并在您的 Web 应用程序项目中添加了对 LicensedControls 项目的引用,则不需要此步骤。
2.将 LicensedControls.LicensedLabel.lic 文件复制到应用程序的 Licenses\LicensedControls\1.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 页。该页将生成下图中显示的错误。
![](http://www.microsoft.com/china/MSDN/library/WebServices/ASP.NET/art/aspnetcontrollicensing01.gif)
图 1. LicensedLabelTest.aspx 页尝试在没有有效许可的情况下使用 LicensedLabel 时生成的错误
.NET 框架授权结构
下图(图 2)说明了 .NET 框架的授权结构。从中可以看出当一个页面尝试对前面一节描述的 LicensedLabel 控件进行实例化时发生的主要步骤。虽然实际步骤发生在服务器控件的上下文中,但该图显示了构成 .NET 框架授权结构的类,以及任何运行时授权方案所共有的关键步骤。许可提供程序执行的确切步骤是特定于提供程序实现的具体授权方案的。例如,正如本文中 ASP.NET 服务器控件授权基础结构一节所描述的,图中显示的许可缓存功能就是特定于 ServerLicenseProvider 的。以粗体显示的类是 .NET 框架类,以斜体显示的类是实现的派生类。
![](http://www.microsoft.com/china/MSDN/library/WebServices/ASP.NET/art/aspnetcontrollicensing02.gif)
图 2. .NET 框架的授权结构
对控件实施授权的主要步骤包括:
1.已授权控件在其构造函数中调用静态方法 System.ComponentModel.LicenseManager.Validate。(该控件也可以在其构造函数中调用静态方法 LicenseManager.IsValid。在这种情况下,返回类型与图中显示的会有所不同,并且不会引发异常。)
2.LicenseManager.Validate 方法检查组件的元数据,从应用于该组件的 LicenseProviderAttribute 属性获得许可提供程序的类型。许可提供程序类必须从 System.ComponentModel.LicenseProvider 类派生。
3.LicenseManager 对许可提供程序类(System.ComponentModel.LicenseProviderAttribute 元数据属性中指定了它的类型)进行实例化,将该组件的类型传递到该许可提供程序,并指出该组件在设计时使用还是在运行时使用。
4.许可提供程序在许可缓存中查找组件的许可。如果找到一个许可,许可提供程序就验证该许可。注意,许可缓存查找和许可存储不是一般的要求,而是特定于 ServerLicenseProvider - 我们已经实现的许可提供程序的。
a.(仅限第一次)许可提供程序获取许可数据,并进行验证。如果该数据无效,许可提供程序将引发 System.ComponentModel.LicenseException 异常。
b.(仅限第一次)如果许可数据有效,许可提供程序将创建一个许可(从 System.ComponentModel.License 派生的类)。此外,许可提供程序还会验证许可,如果许可有效,则将它存储在许可缓存中。
5.许可提供程序将一个有效许可返回许可管理器,或引发许可异常。
6.LicenseManager.Validate 方法返回一个有效许可,或将许可异常传递到调用代码中。
7.如果 LicenseManager 返回有效许可,构造函数将对该类进行初始化,该控件将被实例化。否则,构造函数将 LicenseException 异常传递到试图实例化该控件的代码。本文已授权控件演练一节中的图所显示的错误消息是 ASP.NET 运行时产生的,ASP.NET 运行时处理当某页在没有有效许可的情况下使用已授权控件时,由该控件的构造函数传递的许可异常。
初次创建指的是组件在 Web 应用程序中的第一次实例化。如果在同一页上或者在应用程序中的另一页上创建了该组件的另一个实例(在同一个请求中或者在后来的请求中),则不会发生步骤 4a 和 4b。出于性能方面的原因,ServerLicenseProvider 按每个应用程序对许可进行缓存(而不是按每页或每个会话)。
.NET 框架中授权结构的设计使得非法使用组件非常困难(但并非不可能)。如果用户试图在没有许可的情况下使用一个已授权组件,授权机制就会使用户很明显地看出该组件正在被非法使用。授权不产生组件篡改证据。
.NET 框架中的授权结构是由 System.ComponentModel 命名空间中的以下四个类提供的:
• |
LicenseManager:该类负责对组件的元数据中指定的许可提供程序进行实例化。许可管理器还向许可提供程序传递组件的类型和授权上下文,授权上下文指明该组件是在设计时使用还是在运行时使用。除了在组件的构造函数中调用 LicenseManager 类的 Validate 或 IsValid 方法之外,您无需知道有关 LicenseManager 的其他详细信息。 |
• |
LicenseProviderAttribute:此属性指定负责创建和验证组件许可的许可提供程序的类型。您必须将此属性应用于支持授权的组件。 |
• |
LicenseProvider:该类包含任何授权方案的核心功能 - 即发放和验证许可的任务。要实现授权支持,您必须通过从 LicenseProvider 派生来创建自定义许可提供程序,并实现基类的抽象方法 GetLicense,以提供授权逻辑。许可提供程序 ServerLicenseProvider 的实现将在本文下一节讨论。 |
• |
License:该类是许可数据(如包含在 .lic 文件中的许可数据)的软件抽象。要实现许可类,您必须从 License 类派生,并实现基类的抽象属性 LicenseKey。在本文的下一节,我们将实现一个与 ServerLicenseProvider 一起使用的许可类。 |
.NET 框架在 System.ComponentModel.LicFileLicenseProvider 类中提供了许可提供程序的默认实现。该许可提供程序依靠可视设计器(如 Visual Studio .NET)在设计时和编译期间获取授权数据,将许可数据作为资源嵌入使用已授权组件的应用程序的程序集中。LicFileLicenseProvider 类可以由 Windows 窗体控件使用,但它不满足本文中 ASP.NET 服务器控件授权要求一节描述的 ASP.NET 服务器控件授权要求。
ASP.NET 服务器控件授权基础结构
本节将描述核心授权实现,它提供了 ASP.NET 服务器控件授权方案的具体部署。该实现包含在两个类中,ServerLicenseProvider 和 ServerLicense,它们分别从 LicenseProvider 和 License 类中派生。我们希望在将来的 ASP.NET 版本中,类似的一组基类中有内置的授权支持。您可以使用和扩展 ServerLicenseProvider 和 ServerLicense 类,而不必检查其源代码,就像您使用 .NET 框架中的类一样。不过,为完整起见,本节包括了这些类的代码。
ServerLicenseProvider 类
ServerLicenseProvider 类是从 LicenseProvider 派生的,它覆盖了 GetLicense 方法,以实现核心服务器控件授权要求。ServerLicenseProvider 满足了本文前面说明的服务器授权要求 - 不编译模型、运行时授权支持、授权缓存和 XCOPY 部署。ServerLicenseProvider 将实现从 .lic 文本文件加载许可数据的默认授权方案,该文件存储在 Web 应用程序根目录中一个名为 Licenses 的目录中。此目录下的结构基于本文已授权控件演练一节中显示的程序集的名称和版本。该默认方案依靠在 .lic 文件中找到以下内容:“<组件的完整类型名> is licensed.”。
// 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() { } } }
扩展默认授权方案
ServerLicenseProvider 和 ServerLicense 类实现了一个简单的默认授权方案。您可以对这些类进行扩展,以实现您自己的具有更复杂验证逻辑的自定义授权方案。要实现自定义授权方案,请从 ServerLicenseProvider 派生您自己的许可提供程序,并覆盖它的一个或多个虚拟方法(见下表)。要完成您的授权方案的实现,您可能还必须实现从 ServerLicense 派生的并与许可提供程序一起使用的许可类。
下表描述了 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; } } } }
ExpiringLicenseProvider 类是从 ServerLicenseProvider 派生的,它覆盖了该基类的下列方法:
• |
CreateEmptyLicense,以创建不与真正的许可数据相关联的许可。空的许可适于在设计时使用。 |
• |
CreateLicense,以创建具有 .lic 文件指定的使用计数的许可。 |
• |
ValidateLicense,以检查许可是否已过期。ValidateLicense 在进行检查之前将递增使用计数。 |
• |
ValidateLicenseData,以检查 .lic 文件中的文本字符串是否包含用分号分隔的两部分:“<完整类型名> is licensed.;<使用计数>”。 |
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++; } } }
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)); } } }
ExpiringLicensedLabel 的.lic 文件中的内容是 "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); } } }
EncryptedLicenseProvider 类是从 ServerLicenseProvider 派生的,并覆盖了 GetLicenseDataStream 方法,该方法创建 System.IO.Stream 对象来读取许可数据。EncryptedLicenseProvider 用 System.Security.Cryptography.CryptoStream 来包装此流,以便在许可数据被读入时对其进行解密。示例中使用的 CryptoStream 采用了带有 64 位加密密钥的数据加密标准 (DES) 密码算法,加密密钥作为私有字段 encryptionKeyBytes 嵌入到了 EncryptedLicenseProvider 类本身。之所以允许用这种方式嵌入密钥,是因为授权结构的设计使得破坏许可非常困难,甚至不可能。Win32 安全 API 提供了更多存储加密密钥的更加复杂的机制;不过,那些技术通常不适于 XCOPY 部署。
ServerLicense 很适合作为 EncryptedLicenseProvider 的许可类,因此无需为此许可提供程序实现任何派生的许可类。
下面代码中显示的 EncryptedLicensedLabel 控件使用了在 EncryptedLicenseProvider 类中实现的授权方案。
// 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 文件。
![](http://www.microsoft.com/china/MSDN/library/WebServices/ASP.NET/art/aspnetcontrollicensing03.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 框架授权结构。我们讲述了三个关键要素 - 控件中支持授权的代码、许可数据以及发放和验证许可的许可提供程序类 - 是如何提供授权功能的。我们提供了一个默认的 ASP.NET 服务器控件授权实现,该实现创建了服务器控件授权方案的具体部署,我们还说明了如何通过创建不同的自定义授权方案来扩展此默认授权实现。