本单元内容
microsoft .NET Framework 在其安全性命名空间中提供了许多技术和大量的类型,帮助开发人员构建安全的代码,并创建安全的 Web 应用程序。本单元通过简单地介绍托管代码开发在安全性方面的优点,勾画出 .NET Framework 安全性的全貌。本单元还介绍和比较了 .NET Framework 应用程序中可以使用的两种相辅相成的安全形式:用户安全和代码安全。最后,本单元简要探讨了用来进行 .NET Framework 安全性编程的安全性命名空间。
本单元说明了如何将 .NET Framework 安全性应用于 ASP.NET Web 应用程序和 Web 服务中。
目标
通过本单元能够:
|
开始理解 .NET Framework 安全性的全貌。 |
|
列出托管代码在安全方面的优点。 |
|
比较和对照基于角色的安全性和代码访问安全性的异同。 |
|
了解何时使用基于角色的安全性,何时使用代码访问安全性。 |
|
探讨 .NET Framework 安全性命名空间。 |
应用范围
本单元可以应用于下列产品和技术:
|
microsoft? Windows? Server 2000 和 Windows Server? 2003 操作系统 |
|
microsoft .NET Framework 1.1 |
|
asp.net 1.1 |
如何使用本单元
本单元提供了有关 .NET 安全性以及 .NET Framework 应用程序中可以使用的两种不同的安全性形式:基于角色的安全性和代码访问安全性的概念性背景信息。在阅读“代码访问安全性实践”和“在 ASP.NET 中使用代码访问安全性”单元之前,请先阅读本单元,并理解其中的概念。
要想从本单元获得最大收益,您需要:
|
理解 .NET Framework 提供的两层防范模式。借助基于角色的安全性,我们可以控制用户对应用程序资源和操作的访问。借助代码访问安全性,我们可以根据代码的标识或发行者,控制哪些代码可以访问资源,执行特权操作。 |
|
|
创建使用本单元中所述安全性概念的应用程序。本单元讨论了何时应该使用基于用户的安全性,何时应该使用基于代码的安全性。在阅读本单元后,您就能够确定如何通过使用基于角色或基于代码的安全性,使所创建的任何新的应用程序更加安全。 |
本页内容
托管代码的优点
开发 .NET Framework 应用程序提供了一些直接的安全性优点,但是还有许多问题需要考虑。这些问题在本指南第三部分的几个“构建……”单元中进行了讨论。
.net Framework 程序集是用托管代码生成的。各种语言的编译器(例如 Microsoft Visual C#_ 开发工具和 Microsoft Visual Basic疇.NET 开发系统)都输出 Microsoft 中间语言 (MSIL) 指令,它们包含在标准 Microsoft Windows 可移植的执行文件 (PE) .dll 或 .exe 文件中。当加载程序集并且调用一个方法时,方法的 MSIL 代码将被实时 (JIT) 编译器编译为本机的机器指令,随后再执行这些机器指令。不会调用到的方法将不进行 JIT 编译。
中间语言与公共语言运行库所提供的运行库环境配合使用,为程序集开发人员提供了许多直接的安全性优点。
|
文件格式与元数据验证。 公共语言运行库能够验证 PE 文件格式的有效性,以及地址不指向 PE 文件之外。这有助于提供程序集独立。公共语言运行库还能验证程序集中所包含的元数据的完整性。 |
|
代码验证。MSIL 代码在进行 JIT 编译时会验证类型安全性。从安全角度来看,这是一个很大的优点,因为验证过程可以防止不良的指针操作,可以验证类型转换的合法性,检查数组边界,等等。这实际上消除了托管代码中出现缓冲溢出漏洞的可能,虽然仍旧需要仔细检查调用非托管应用程序编程接口 (API) 的代码是否存在缓冲溢出的可能。 |
|
|
完整性检查。强名称程序集的完整性需要使用数字签名来验证,以确保程序集从生成和签名之后没有发生任何改变。这意味着攻击者无法通过直接操作 MSIL 指令来改变代码。 |
|
|
代码访问安全性。公共语言运行库提供的虚拟执行环境还允许在运行时执行更多安全检查。说得具体一些,就是代码访问安全性能够根据调用代码的标识进行各种运行时安全决策。 |
用户安全性与代码安全性
用户安全性和代码安全性是 .NET Framework 应用程序中两种相辅相成的安全形式。用户安全性回答的是“用户是谁,用户能进行什么操作?”,而代码安全性回答的是这样的问题:“代码从何而来,谁编写了代码,代码能执行什么操作?” 代码安全性涉及的是授权对应用程序(而非用户)系统级资源(包括文件系统、注册表、网络、目录服务和数据库)的访问。在此情况下,无论最终用户是谁,或者哪个用户帐户运行代码都是无关紧要的,而什么代码以及是否允许进行这样的操作则非常重要。
.net Framework 用户安全性的实现称为基于角色的安全性。 代码安全性的实现称为代码访问安全性。
基于角色的安全性
.net Framework 基于角色的安全性允许 Web 应用程序根据与应用程序交互的用户的标识或者角色成员身份进行安全决策。如果应用程序使用 Windows 身份验证,则角色就是 Windows 组。如果应用程序使用其他形式的身份验证,则角色就是由应用程序定义的,用户和角色的详细信息通常维持在 SQL Server 或者基于 Active Directory 的用户存储中。
已经进行身份验证的用户的身份及其相关的角色成员身份是 Web 应用程序通过 principal 对象获得的,后者附加在当前 Web 请求中。
图 1 显示了 Web 应用程序中如何使用用户安全性来限制用户访问 Web 页、业务逻辑、操作和数据访问的典型逻辑视图。
图 1. 基于(用户)角色的安全性的逻辑视图
代码访问安全
代码访问安全可以在代码试图访问安全的资源(例如,文件系统、注册表、网络等等)时,或者试图执行其他特权操作(例如,调用非托管代码或者使用反射)时,对其进行授权。
代码访问安全性是一种重要的附加防范机制,可以用来提供对一段代码的约束。管理员可以配置代码访问安全策略以限制代码能够访问的资源类型以及能够执行的其他特权操作。从 Web 应用程序的角度来看,这意味着如果在某个危及安全的攻击过程中,攻击者控制了 Web 应用程序进程或者插入了在进程中运行的代码,则代码访问安全性提供的这些附加约束就能够限制可能带来的损害。
图 2 显示了在 Web 应用程序中如何使用代码访问安全性来限制应用程序访问系统资源、其他应用程序拥有的资源和特权操作(如调用非托管代码)的逻辑视图。
图 2. 基于代码的安全性的逻辑视图
代码的身份验证(标识)是根据代码的证据(例如,它的强名称、发行者,或者安装目录)进行的。而授权是根据安全策略授予代码的代码访问权限进行的。有关 .NET Framework 代码访问安全性的详细信息,请参阅“代码访问安全性实践”单元。
.NET Framework 基于角色的安全性
.net Framework 基于角色的安全性是在应用程序中用来授权用户操作的关键技术。角色经常用来强制执行一些业务规则。例如,财务应用程序可能只允许经理执行超过某个特定金额上限的转账操作。
基于角色的安全性由以下要素组成:
|
|
用户和标识 |
|
|
PrincipalPermission 对象 |
|
|
基于角色的安全性检查 |
|
|
URL授权 |
用户和标识
基于角色的安全性是通过 principal 和 identity 对象实现的。已进行身份验证的调用方的标识和角色成员身份是通过 principal 对象公开的,该对象附加在当前 Web 请求中。可以使用 httpcontext.current.user 属性检索该对象。如果应用程序不需要对调用方进行身份验证,例如,用户正在浏览站点中可以公开访问的部分,则 principal 对象代表的是匿名 Internet 用户。
Principal 对象有很多类型,准确的类型取决于应用程序所使用的身份验证机制。但是,所有 principal 对象都要实现 system.security.principal.iprincipal 接口,它们都维持着一个角色列表,用户是其中的一个成员。
principal 对象还包含 identity 对象,后者包括用户名以及指示身份验证类型和用户是否已经进行身份验证的标志。这样就能够区分已进行身份验证和匿名的用户。Identity 对象也有许多不同类型,取决于身份验证的类型,虽然所有标识对象都要实现 system.security.principal.iidentity 接口。
以下表格给出了可能的身份验证类型以及 ASP.NET Web 应用程序使用的 principal 和 identity 对象的各种不同类型。
|
Windows |
windowsprincipal + windowsidentity |
凭据的验证是自动的,使用安全帐户管理器 (SAM) 或者 Active Directory。Windows 组用于角色。 |
|
窗体 |
genericprincipal + formsidentity |
必须添加代码来验证凭据,从用户存储中检索角色成员身份。 |
|
Passport |
genericprincipal + passportidentity |
依赖 Microsoft Passport SDK 。passportidentity 提供了对 Passport 身份验证票的访问。 |
PrincipalPermission 对象
PrincipalPermission 对象表示当前用户执行代码所必需的标识和角色。principalpermission 对象可以在代码中声明性或命令性地使用。
声明性安全
可以通过在类或者方法定义中添加 principalpermissionattribute,准确地控制允许哪些用户访问这些类或者方法。类级别的属性自动应用于所有类成员,除非它已被成员级属性所重写。principalpermissionattribute 类型是在 system.security.permissions 命名空间中定义的。
注 还可以使用 principalpermissionattribute 限制对结构和其他成员类型(如属性和委托)的访问。
下面的示例说明了如何将对某个特定的类的访问权限制在 managers 组的成员中。请注意该示例假设使用 Windows 身份验证,其中角色名的格式是 machinename\rolename 或者 domainname\rolename。对于其他的身份验证类型,角色名的格式是特定于应用程序的,并取决于用户存储中保存的角色名字符串。
[PrincipalPermissionAttribute(SecurityAction.Demand, Role=@"DOMAINNAME\Managers")]
public sealed class OnlyManagersCanCallMe
{
}
注 属性类型名中最后的 attribute 可以省略。这样属性类型名就与其相关的权限类型名相同了,在此例中为 principalpermission。它们是截然不同的(但逻辑上是相关的)类型。
下面的示例说明了如何限制对一个类的特定方法的访问。在此示例中,访问仅限于本地管理员组的成员,这是通过特殊的 "builtin\administrators" 标识符来标识的。
[PrincipalPermissionAttribute(SecurityAction.Demand,
Role=@"BUILTIN\Administrators")]
public void SomeMethod()
{
}
其他内置的 Windows 组名可以通过在组名前加上"builtin\"(例如,"builtin\users" 和 "builtin\power Users" )使用。
命令性安全
如果方法级安全性对于您的安全需求来说还不够细致,可以在代码中执行命令性安全检查,方法是使用 system.security.permissions.principalpermission 对象。
以下示例说明了使用 principalpermission 对象的命令性安全语法。
PrincipalPermission permCheck = new PrincipalPermission(
null, @"DomainName\WindowsGroup");
permCheck.Demand();
为了避免使用局部变量,以上代码还可以写成:
(new PrincipalPermission(null, @"DomainName\WindowsGroup")).Demand();
以上代码用一个空的用户名和指定的角色名创建了一个 principalpermission 对象,然后调用了 demand 方法。这将导致公共语言运行库查询附加于当前线程的当前 principal 对象,检查相关联的标识是否为指定角色的成员。因为本例中使用了 Windows 身份验证,所以角色的检查使用的是 Windows 组。如果当前标识不是指定角色的成员,将发生 securityexception 异常。
以下示例说明了如何限制对某个单独用户的访问。
(new PrincipalPermission(@"DOMAINNAME\James", null)).Demand();
声明性安全与命令性安全
基于角色的安全(和代码访问安全)既可以通过属性声明性地使用,也可以在代码中命令性地使用。一般而言,声明性安全能够提供最多好处,但是有时候必须使用命令性安全(例如,需要使用只能在运行时访问的变量时)帮助进行安全性决策。
声明性安全的优点
声明性安全的主要优点如下:
|
|
它允许管理员或者程序集的使用者准确地看到特定的类和方法必须以何种安全权限运行。诸如 permview.exe 这样的工具可以提供此类信息。在部署时了解这些信息可以帮助解决安全性问题,还能帮助管理员配置代码访问安全性策略。 |
|
|
它提供了更好的性能。声明性请求只能在加载时计算一次。方法中的命令性请求则在每次调用包含此请求的方法时都要计算。 |
|
|
安全属性确保了权限请求在方法中其他任何代码可能运行之前执行。这消除了因为安全检查执行得太迟带来的潜在错误。 |
|
|
类级的声明性检查应用于所有类成员。命令性检查则在调用处应用。 |
命令性安全的优点
命令性安全的主要优点以及有时候必须使用命令性安全的主要原因有:
|
|
它允许您使用只能在运行时获得的值动态地形成请求。 |
|
|
它允许您通过在代码中实现条件逻辑,执行更细致的授权。 |
基于角色的安全性检查
对于较细的授权决策,还可以执行显式的角色检查,方法是使用 iprincipal.isinrole 方法。以下示例假设使用 Windows 身份验证,虽然代码与使用窗体身份验证时非常相似,只不过需要将 user 对象强制转换为 genericprincipal 类型的对象。
// Extract the authenticated user from the current HTTP context.
// The User variable is equivalent to HttpContext.Current.User if you are using
// an .aspx or .asmx page
WindowsPrincipal authenticatedUser = User as WindowsPrincipal;
if (null != authenticatedUser)
{
// Note: If you need to authorize specific users based on their identity
// and not their role membership, you can retrieve the authenticated user's
// username with the following line of code (normally though, you should
// perform role-based authorization).
// string username = authenticatedUser.Identity.Name;
// Perform a role check
if (authenticatedUser.IsInRole(@"DomainName\Manager") )
{
// User is authorized to perform manager functionality
}
}
else
{
// User is not authorized to perform manager functionality
// Throw a security exception
}
URL 授权
管理员可以通过使用 Machine.config 或 Web.config 中的 <authorization >元素配置基于角色的安全性。该元素配置 ASP.NET urlauthorizationmodule,它使用附加于当前 Web 请求中的 Principal 对象,以做出授权决策。
授权元素还包含子元素 <allow >和 <deny>,这些子元素用来确定允许或者不允许哪些用户或者组访问特定的目录和页面。除非 <location> 元素中包含 <authorization> 元素,否则 Web.config 中的 <authorization> 元素将控制对 Web.config 文件所在目录的访问。该目录通常是 Web 应用程序的虚拟根目录。
web.config 的以下示例将使用 Windows 身份验证,允许 Bob 和 Mary 访问,但是拒绝其他所有人:
<authorization>
<allow users="DomainName\Bob, DomainName\Mary" />
<deny users="*" />
</authorization>
以下语法和语义适用于 <authorization >元素的配置:
|
|
"*" 指代所有标识。 |
|
|
"?" 指代未进行身份验证的标识(即匿名标识)。 |
|
|
使 URL 授权起作用不需要进行模拟。 |
|
|
url 授权的用户和角色是由身份验证设置决定的:
|
|
当有 <authentication mode="Windows" />时,将授权可以对 Windows 用户和组帐户进行访问。
用户名的形式是“DomainName\WindowsUserName”。
角色名的形式是“DomainName\WindowsGroupName”。
注 本地管理员组称为“BUILTIN\Administrators”。本地用户组称为“BUILTIN\Users”。 |
|
|
当有 <authentication mode="Forms" />时,存储在当前 HTTP 上下文中的 iprincipal 对象的用户和角色将得到授权。例如,如果使用窗体在数据库中对用户进行身份验证,则从数据库中检索到的角色将得到授权。 |
|
|
当有 <authentication mode="Passport" />时,从存储中检索到的 Passport 用户 ID (PUID) 或者角色将获得授权。例如,可以将一个 PUID 映射到存储在 Microsoft SQL Server 数据库或 Active Directory 中特定的帐户和角色集。 |
|
|
当有 <authentication mode="None" />时,可能就无需进行授权了。“None”指定了不需要执行任何身份验证,或者不需要使用任何 ASP.NET 身份验证模块,而是使用自己的自定义机制。
但是,如果使用自定义身份验证,应该创建一个包含多个角色的 iprincipal 对象,并将其存储在 httpcontext.current.user 属性中。在以后执行 URL 授权时,将对 iprincipal 对象中维持的用户和角色(无论它们如何检索而来)进行授权。 | |
配置对特定文件的访问
要配置对特定文件的访问,请将 <authorization> 元素放在 <location>元素内,如下所示。
<location path="somepage.aspx" />
<authorization>
<allow users="DomainName\Bob, DomainName\Mary" />
<deny users="*" />
</authorization>
</location>
还可以将 path 属性指向一个特定文件夹,将访问控制应用到该特定文件夹中的所有文件。有关 <location> 元素的更多信息,请参阅“保护 ASP.NET 应用程序安全性”单元。
.NET Framework 安全性命名空间
对 .NET Framework 安全性进行编程,要使用 .NET Framework 安全性命名空间中的类型。本节介绍了开发安全的 Web 应用程序时可能用到的这些命名空间和类型。有关类型的完整列表,请参阅 .NET Framework 文档。安全性命名空间如下,同时显示在图 3 中。
|
|
System.Security |
|
|
System.Web.Security |
|
|
System.Security.Cryptography |
|
|
System.Security.Principal |
|
|
System.Security.Policy |
|
|
System.Security.Permissions |
图 3. .NET Framework 安全性命名空间
System.Security
这个命名空间包含 codeaccesspermission 基类,所有其他的代码访问权限类型都由此派生。直接使用基类是不可能的。更可能的情形是:使用特定权限类型表示代码访问特定资源类型或执行其他特权操作的权限。例如,fileiopermission 表示执行文件 I/O 的权限,eventlogpermission 表示代码访问事件日志的权限,等等。有关代码访问权限类型的完整列表,请参阅本单元后面的表 2。
System.Security 命名空间还包含封装了权限集的类。这包括 permissionset 和 namedpermissionset 类。在构建安全的 Web 应用程序时最可能使用的类型是:
|
|
SecurityException 。用来表示安全性错误的异常类型。 |
|
|
AllowPartiallyTrustedCallersAttribute 。这是一个程序集级属性,用于必须支持部分受信任调用方的那些具有强名称的程序集。如果不使用这个属性,具有强名称的程序集只能被完全受信任的调用方(有无限权限的调用方)调用。 |
|
|
SupressUnmanagedSecurityAttribute 。用来优化性能,并使平台调用服务 (P/Invoke) 和组件对象模型 (COM) 互操作层不再需要授予非托管代码权限。这个属性必须小心使用,因为它暴露了一个潜在的安全风险。如果攻击者获得了对非托管代码的控制,他就不再受代码访问安全性的限制了。有关如何安全使用这一属性的更多信息,请参阅单元“代码访问安全性实践”中“非托管代码”一节。 |
System.Web.Security
这个命名空间包含用于管理 Web 应用程序身份验证和授权的类。这包括 Windows、窗体和 Passport 身份验证、URL 和文件授权,这些分别是由 urlauthorizationmodule 和 fileauthorizationmodule 类控制的。在构建安全的 Web 应用程序时最可能使用的类型是:
|
|
FormsAuthentication 。提供静态方法辅助窗体身份验证和身份验证票的操作。 |
|
|
FormsIdentity 。用于封装已经过窗体身份验证的用户标识。 |
|
|
PassportIdentity。用于封装已经过 Passport 身份验证的用户标识。 |
System.Security.Cryptography
这个命名空间包含了用来执行加密和解密、哈希和随机数生成的类型。这是一个很大的命名空间,包含许多类型。许多加密算法都是用托管代码实现的,而其他一些加密算法则通过此命名空间中的类型公开,包装了基于 Microsoft Win32? 的 CryptoAPI 所提供的基础加密功能。
System.Security.Principal
这个命名空间包含了用于支持基于角色的安全性的类型。它们用来限制哪些用户能够访问类和类成员。此命名空间包括 iprincipal 和 iidentity 接口。在构建安全的 Web 应用程序时最可能使用的类型是:
|
|
GenericPrincipal 和 genericidentity。可以用来定义自己的角色和用户标识。它们通常都在自定义身份验证机制中使用。 |
|
|
WindowsPrincipal 和 windowsidentity 。表示一个已经用 Windows 身份验证与用户相关联的 Windows 组(角色)列表进行身份验证的用户。 |
System.Security.Policy
这个命名空间包含用于实现代码访问安全性策略系统的类型。它包括表示代码组、成员条件、策略级别和证据的类型。
System.Security.Permissions
这个命名空间包含用来封装代码访问资源、执行特权操作的权限的主要权限类型。下表列出了这个命名空间中定义的权限类型(以字母顺序排列)。
|
directoryservicespermission |
访问 Active Directory 所必需。 |
|
dnspermission |
访问网络上域名系统 (DNS) 服务器所必需。 |
|
endpointpermission |
定义了一个 socketpermission 对象授权的终结点。 |
|
environmentpermission |
控制对单独环境变量的读写访问。还可以用来限制所有对环境变量的访问。 |
|
eventlogpermission |
访问事件日志所必需。 |
|
filedialogpermission |
只在交互式用户通过系统提供的文件对话框指定了文件名时允许对文件的只读访问。通常它是在没有授予 fileiopermission 权限的时候才使用。 |
|
fileiopermission |
控制读、写并追加访问文件和目录树。它还可以用来限制所有对文件系统的访问。 |
|
isolatedstoragefilepermission |
控制应用程序的私有虚拟文件系统(由独立存储提供)的使用。独立存储创建了一个唯一和私有的存储区域供应用程序或者组件单独使用。 |
|
isolatedstoragepermission |
访问独立存储所必需。 |
|
messagequeuepermission |
访问 Microsoft 消息队列所必需。 |
|
odbcpermission |
使用 ADO.NET ODBC 数据提供程序所必需。(完全信任也是必需的。) |
|
oledbpermission |
使用 ADO.NET OLE DB 数据提供程序所必需。(完全信任也是必需的。) |
|
oraclepermission |
使用 ADO.NET Oracle 数据提供程序所必需。(完全信任也是必需的。) |
|
performancecounterpermission |
访问系统性能计数器所必需。 |
|
principalpermission |
用来限制根据用户的标识和角色成员身份访问类和方法。 |
|
printingpermission |
访问打印机所必需。 |
|
reflectionpermission |
控制对元数据的访问。带有适当 reflectionpermission 的代码可以获得有关类型的公共、保护和私有成员的信息。 |
|
registrypermission |
控制读、写并创建对注册表项(包括子项)的访问。它还可用于限制所有对注册表的访问。 |
|
securitypermission |
这是一个元权限,可以控制安全基础结构本身的使用。 |
|
servicecontrollerpermission |
可以用于限制对 Windows 服务控制管理器的访问,以及启动、停止和暂停服务的功能。 |
|
socketpermission |
可以用于限制在传输地址上进行和接受连接的能力。 |
|
sqlclientpermission |
可以用于限制对 SQL Server 数据源的访问。 |
|
uipermission |
可以用于限制对剪贴板的访问,并限制窗口的使用,使之成为“安全的”窗口,避免攻击模仿系统对话框提示输入密码等敏感信息。 |
|
webpermission |
可以用于控制对 HTTP Internet 资源的访问。 |
SecurityPermission 类值得特别注意,因为它表示代码执行特权操作的权限,包括断言代码访问权限、调用非托管代码、使用反射、控制策略和证据等等。 securitypermission 类确定的准确权限是由其 flags 属性确定的,后者必须设置为 securitypermissionflags 枚举类型(例如,securitypermissionflags.unmanagedcode )定义的枚举值之一。
小结
本单元通过比较用户安全性和代码安全性,以及探讨安全性命名空间,向您介绍了 .NET Framework 安全性的全貌。.NET Framework 分别将这两种安全性类型称为基于角色的安全性和代码访问安全性。两种形式的安全性都位于 Windows 安全性的顶层。
基于角色的安全性关心的是对用户访问应用程序管理的资源(例如 Web 页)和操作(例如业务和数据访问逻辑)进行访问。代码访问安全性关心的是约束特权代码,准确控制哪些代码能够访问资源并执行其他特权操作。对于 Web 应用程序而言,这是一种强大的附加安全性机制,因为它限制了攻击者的所作所为,即使攻击者会想方设法对 Web 应用程序进程产生威胁。对于提供应用程序独立性而言,这也是一种极为强大的功能。对于宿主公司或者任何在同一个 Web 服务器上宿主多个 Web 应用程序的单位来说,尤其如此。
其他资源
有关更多信息,参阅下列资源: