基于角色授权
大多数 .NET Web 应用程序都使用基于角色的授权方法。您需要考虑不同的角色类型,选择最适合您的应用方案的方法。以下选项可供您选择:
| • | .NET 角色
|
| • | 企业服务 (COM+) 角色
|
| • | SQL Server 用户定义的数据库角色
|
| • | SQL Server 应用程序角色
|
.NET 角色.NET 角色非常灵活,它围绕
IPrincipal 对象展开,这些对象包含已通过验证的标识所属的角色列表。.NET 角色可以在 Web 应用程序、Web 服务或驻留在 ASP.NET 中(并使用 HttpChannel 进行访问)的远程组件中使用。
可以通过 .NET 角色以两种方式进行授权:一种是以声明方式用
PrincipalPermission 命令;一种是以编程方式,在代码中用强制的
PrincipalPermission 命令或
IPrincipal.IsInRole 方法。
Windows 身份验证中的 .NET 角色如果您的应用程序使用 Windows 身份验证,ASP.NET 会自动构建一个
WindowsPrincipal ,并将其附加到当前 Web 请求的上下文(使用
HttpContext.User)。待身份验证进程完成并且 ASP.NET 将该对象附加到当前请求中后,该对象将用于后面所有基于 .NET 角色的授权。
已验证身份的调用方的 Windows 组成员身份用于确定角色集。在 Windows 身份验证中,.NET 角色与 Windows 组相同。
非 Windows 身份验证中的 .NET 角色如果您的应用程序使用除 Windows 身份验证之外的身份验证机制(例如窗体身份验证或 Passport 身份验证),您必须编写代码以创建
GenericPrincipal 对象(或自定义的
IPrincipal 对象),并使用从自定义身份验证数据存储(如 SQL Server 数据库)中获得的角色集填充它。
自定义的 IPrincipal 对象基于 .NET 角色的安全机制是可扩展的。您可以自己开发用来实现
IPrincipal 和
IIdentity 的类,并提供您自己的基于角色的扩展授权功能。
只要自定义的
IPrincipal 对象(包含从自定义数据存储中获得的角色)附加到当前请求的上下文(使用
HttpContext.User)之上,就保证了基本的角色检查功能。
通过实现
IPrincipal 接口,可以确保声明形式的和强制形式的
PrincipalPermission 命令都适用于您的自定义标识。此外,您还可以实现扩展的角色语义;例如,提供允许您测试和断言多个角色的成员身份的其他方法,例如
IsInMultipleRoles (string [] roles)。
更多信息
| • | 有关基于 .NET 角色的授权的详细信息,请参见第 8 章 ASP.NET 安全性。
|
| • | 有关创建 GenericPrincipal 对象的详细信息,请参阅本指南中的如何利用窗体身份验证创建 GenericPrincipal 对象。
|
企业服务 (COM+) 角色使用企业服务 (COM+) 角色可以将访问权限检查推至中间层,并且允许您在连接到后端数据库时使用数据库连接池。但是,对于有意义的基于企业服务 (COM+) 角色的授权,您的前端 Web 应用程序必须模拟原调用方标识并将其传递到企业服务应用程序(使用 Windows 访问令牌)。为此,必须在 Web 应用程序的 Web.config 文件中放入以下条目。
<authentication mode="Windows" /><identity impers />如果在方法级使用声明性检查(以确定哪些用户可以调用哪些方法)就足够的话,可以使用“组件服务”管理工具部署应用程序和更新角色成员身份。
如果需要用方法代码以编程方式进行检查,您将会失去企业服务 (COM+) 角色的一些管理和部署优势,因为角色逻辑是硬编码的。
SQL Server 用户定义的数据库角色在这一方法中,您在数据库中创建角色、基于角色分配权限并将 Windows 组帐户和用户帐户映射到角色。此方法要求您将调用方的标识传递到后端(如果您使用的是首选的 Windows 身份验证,而非 SQL Server身份验证)。
SQL Server 应用程序角色在这一方法中,对数据库中的角色授予权限,但是 SQL Server 应用程序角色不包含用户帐户或组帐户。因此,您将丢失原调用方的粒度。
利用应用程序角色,您将针对具体的应用程序进行授权(与用户组授权相对)。此应用程序使用接受角色名称和密码的内置存储过程激活角色。这一方法的一个主要缺点是它要求应用程序安全管理凭据(角色名称和关联的密码)。
更多信息
有关 SQL Server 用户定义的数据库角色和应用程序角色的详细信息,请参阅第 12 章数据访问安全性。
.NET 角色与企业服务 (COM+) 角色的对比下表显示了 .NET 角色和企业服务 (COM+) 角色的功能对比。
表 3.2: 企业服务角色与 .NET 角色的对比
| | 功能 | 企业服务角色 | .NET 角色 |
| 管理
| “组件服务”管理工具
| 自定义
|
| 数据存储
| COM+ 目录
| 自定义数据存储(如 SQL Server 或 Active Directory)
|
| 声明性
| 是
[SecurityRole("Manager")]
| 是
[PrincipalPermission( SecurityAction.Demand,Role="Manager")]
|
| 强制性
| 是
ContextUtil.IsCallerInRole()
| 是
IPrincipal.IsInRole
|
| 类、接口和方法级别的细化程度
| 是
| 是
|
| 可扩展
| 否
| 是
(使用自定义 IPrincipal 实现)
|
| 可供所有 .NET 组件使用
| 只供派生自 ServicedComponent 基类的组件使用
| 是
|
| 角色成员身份
| 角色包含 Windows 组帐户或用户帐户
| 当使用 WindowsPrincipals 时,角色是 Windows 组 – 没有额外的抽象级别
|
| 需要显式接口实现
| 是
若要获取方法级的身份验证,必须明确定义并实现接口
| 否
|
使用 .NET 角色您可以使用 .NET 角色保护以下各项的安全:
| • | 文件
|
| • | 文件夹
|
| • | Web 页(.aspx 文件)
|
| • | Web 服务(.asmx 文件)
|
| • | 对象
|
| • | 方法和属性
|
| • | 方法中的代码块
|
您可以使用 .NET 角色保护操作(由方法和属性执行)和特定代码块的事实,意味着您可以保护您的应用程序对本地和远程资源的访问。
注意:使用
UrlAuthorizationModule 保护上述列表的前四项(文件、文件夹、Web 页和 Web 服务),
UrlAuthorizationModule 可以利用调用方的角色成员身份(以及调用方的标识)做出授权决定。
如果您使用 Windows 身份验证,系统会为您完成使用 .NET 角色所需的大部分工作。ASP.NET 会构建
WindowsPrincipal 对象,用户的 Windows 组成员身份确定关联的角色集。
要在非 Windows 身份验证机制中使用 .NET 角色,必须编写代码执行以下操作:
| • | 捕获用户的凭据。
|
| • | 对照自定义数据存储(如 SQL Server 数据库)验证用户的凭据。
|
| • | 检索角色列表,构建 GenericPrincipal 对象并将它与当前的 Web 请求关联。
GenericPrincipal 对象表示已验证身份的用户并用于后面的 .NET 角色检查,例如声明性 PrincipalPermission 命令和编程方式的 IPrincipal.IsInRole 检查。
|
更多信息
有关为窗体身份验证创建
GenericPrincipal 对象所涉及过程的详细信息,请参阅第 8 章 ASP.NET 安全性。
检查角色成员身份可使用下列类型的 .NET 角色检查:
重要说明:.NET 角色检查依赖于
IPrincipal 对象(表示已验证身份的用户)是否与当前请求关联。对于 ASP.NET Web 应用程序,
IPrincipal 对象必须附加到
HttpContext.User。对于 Windows 窗体应用程序,
IPrincipal 对象必须附加到
Thread.CurrentPrincipal。
| • | 手动角色检查。对于细分授权,您可以调用 IPrincipal.IsInRole 方法,以便基于调用方的角色成员身份授予对特定代码块的访问权限。当检查角色成员身份时,AND 和 OR 逻辑都可以使用。
|
| • | 声明性角色检查(方法入口)。您可以使用 PrincipalPermissionAttribute 类(可以简写为 PrincipalPermission)对方法进行注释,以声明方式要求调用方提供角色成员身份。这些检查只支持 OR 逻辑。例如,您可以要求调用方至少有一个特定的角色(例如,调用方必须是出纳或经理)。用声明性检查无法指定调用方必须是经理和出纳。
|
| • | 强制性角色检查(在方法内检查)。可以在代码中调用 PrincipalPermission.Demand 来执行细分的授权逻辑。支持 AND 和 OR 等逻辑操作。
|
角色检查示例下面的代码片段显示了一些使用编程式、声明性和强制性方法进行角色检查的示例。
授权 Bob 执行操作:
注意:尽管可以对用户进行个别授权,但通常应该基于角色成员身份进行授权,这使您可以授权用户组,而组中的所有用户在应用程序中将具有相同的权限。
| • | 直接检查用户名
GenericIdentity userIdentity = new GenericIdentity("Bob"); if (userIdentity.Name=="Bob") { } |
| • | 声明性检查
[PrincipalPermissionAttribute(SecurityAction.Demand, User="Bob")] public void DoPrivilegedMethod() { } |
| • | 强制性检查
PrincipalPermission permCheckUser = new PrincipalPermission( "Bob", null); permCheckUser.Demand(); |
授权出纳执行操作:
| • | 直接检查角色名称
GenericIdentity userIdentity = new GenericIdentity("Bob"); // 角色名将从自定义数据存储中检索 string[] roles = new String[]{"Manager", "Teller"}; GenericPrincipal userPrincipal = new GenericPrincipal(userIdentity, roles); if (userPrincipal.IsInRole("Teller")) { } |
| • | 声明性检查
[PrincipalPermissionAttribute(SecurityAction.Demand, Role="Teller")] void SomeTellerOnlyMethod() { } |
| • | 强制性检查
public SomeMethod() { PrincipalPermission permCheck = new PrincipalPermission( null,"Teller"); permCheck.Demand(); // 只有 Teller(出纳)可以执行以下代码 // 安全异常情况中没有生成 Teller 角色的成员 . . . } |
授权经理或出纳执行操作:
| • | 直接检查角色名称
if (Thread.CurrentPrincipal.IsInRole("Teller") || Thread.CurrentPrincipal.IsInRole("Manager")) { // 执行特权操作 } |
| • | 声明性检查
[PrincipalPermissionAttribute(SecurityAction.Demand, Role="Teller"), PrincipalPermissionAttribute(SecurityAction.Demand, Role="Manager")] public void DoPrivilegedMethod() { ... } |
| • | 强制性检查
PrincipalPermission permCheckTellers = new PrincipalPermission( null,"Teller"); PrincipalPermission permCheckManagers = new PrincipalPermission( null,"Manager"); (permCheckTellers.Union(permCheckManagers)).Demand(); |
只授权那些既是经理又是出纳的用户执行操作:
| • | 直接检查角色名称
if (Thread.CurrentPrincipal.IsInRole("Teller") && Thread.CurrentPrincipal.IsInRole("Manager")) { // 执行特权操作 } |
| • | 声明性检查
不能用 .NET 角色以声明方式执行 AND 检查。将 PrincipalPermission 命令堆叠在一起使用会产生逻辑 OR。
|
| • | 强制性检查
PrincipalPermission permCheckTellers = new PrincipalPermission( null,"Teller"); permCheckTellers.Demand(); PrincipalPermission permCheckManagers = new PrincipalPermission( null, "Manager"); permCheckManagers.Demand(); |
选择身份验证机制
本节提供指导信息,旨在帮助您选择适合常见应用方案的身份验证机制。您应该从考虑以下问题着手:
| • | 标识。仅当应用程序用户的 Windows 帐户可由受信任的机构进行身份验证,并且应用程序的 Web 服务器可以访问该机构时,才适合使用 Windows 身份验证机制。
|
| • | 凭据管理。Windows 身份验证的一个主要优点是它使您可以让操作系统负责凭据管理。在非 Windows 方法(如“窗体”身份验证)中,您必须仔细考虑在何处以及如何存储用户凭据。两种最常见的方法是:
| • | SQL Server 数据库
| | • | Active Directory 中的用户对象
| 有关将 SQL Server 用作凭据存储的安全注意事项的详细信息,请参见第 12 章数据访问安全性。
有关对照自定义数据存储(包括 Active Directory)使用“窗体”身份验证的详细信息,请参见第 8 章 ASP.NET 安全性。
|
| • | 标识传递。您需要实现模拟/委派模型并在操作系统级的各层间传递原调用方的安全性上下文吗?例如,支持审核或每用户(粒度)授权。如果需要,您需要能够模拟调用方并将它们的安全性上下文委派给下一个下游子系统。
|
| • | 浏览器类型 您的用户是否都安装了 Internet Explorer,或者您是否需要支持使用混合浏览器类型的用户群?表 3.3 阐释了哪些身份验证机制要求 Internet Explorer 浏览器,以及哪些身份验证机制支持多种常见的浏览器类型。
|
表 3.3: 身份验证对浏览器的要求
| | 身份验证类型 | 是否要求 Internet Explorer | 备注 |
| 窗体
| 否
|
|
| Passport
| 否
|
|
| 集成 Windows(Kerberos 或 NTLM)
| 是
| Kerberos 还要求在客户机和服务器上安装 Windows 2000 或更高版本的操作系统以及为委派配置的帐户。有关详细信息,请参见本指南中的如何为 Windows 2000 实现 Kerberos 委派。
|
| 基本
| 否
| 基本身份验证是几乎所有浏览器都支持的 HTTP 1.1 协议的一部分。
|
| 摘要式
| 是
|
|
| 证书
| 否
| 客户端要求 X.509 证书
|
Internet 方案Internet 方案的基本假设条件是:
| • | 用户在服务器的域中或可由服务器访问的受信任域中没有 Windows 帐户。
|
| • | 用户没有客户端证书。
|
图 3.4 显示了一个为 Internet 方案选择身份验证机制的决策树。

图 3.4
为 Internet 应用程序选择身份验证机制
有关 Web 服务安全以及 WS 安全规范(全球 XML 体系结构 (GXA) 提案的一部分)的详细信息,请参见第 10 章 Web 服务安全性。
窗体/Passport 比较本节概述了窗体身份验证和 Passport 身份验证的相对优点。
窗体身份验证的优点
| • | 支持对照自定义数据存储(通常为 SQL Server 数据库或 Active Directory)进行身份验证。
|
| • | 支持基于角色授权(包括从数据存储中查找角色)。
|
| • | 与 Web 用户界面无缝集成。
|
| • | ASP.NET 构成基础结构的大部分。与传统的 ASP 比较,需要的自定义代码相对较少。
|
Passport 身份验证的优点
| • | Passport 是一个集中式解决方案。
|
| • | 它解决了应用程序中的凭据管理问题。
|
| • | 它可用于基于角色的授权方案。
|
| • | 非常安全,因为它建立在加密技术之上。
|
更多信息
| • | 有关 Web 服务身份验证方法的详细信息,请参见第 10 章 Web 服务安全性。
|
| • | 有关在 SQL Server 中使用窗体身份验证的详细信息,请参见本指南的如何将窗体身份验证用于 SQL Server 2000。
|
Intranet / Extranet 方案图 3.5 显示的决策树可协助人们为 Intranet 和 Extranet 应用方案选择身份验证机制。

图 3.5
为 Intranet 和 Extranet 应用程序选择身份验证机制
身份验证机制比较下表对目前可用的身份验证机制进行了比较。
表 3.4: 可用的身份验证方法
| | | 基本 | 摘要式 | NTLM | Kerberos | 证书 | 窗体 | Passport |
| 用户需要在服务器的域中有 Windows 帐户
| 是
| 是
| 是
| 是
| 否
| 否
| 否
|
| 支持委派*
| 是
| 否
| 否
| 是
| 可以
| 是
| 是
|
| 需要 Win2K 客户端和服务器
| 否
| 是
| 否
| 是
| 否
| 否
| 否
|
| 凭据以明文传递(需要 SSL)
| 是
| 否
| 否
| 否
| 否
| 是
| 否
|
| 支持非 IE 浏览器
| 是
| 否
| 否
| 否
| 是
| 是
| 是
|
*
总结
设计分布式应用程序身份验证和授权方法是一项具有挑战性的任务。在应用程序开发的早期设计阶段,正确设计身份验证和授权有助于减少很多重大的安全风险。下面对本章的内容进行总结:
| • | 使用受信任的子系统资源访问模型可获得数据库连接池的种种好处。
|
| • | 如果您的应用程序没有使用 Windows 身份验证,请使用 .NET 角色检查提供授权。对照自定义数据存储验证凭据,检索角色列表并创建 GenericPrincipal 对象。将该对象与当前 Web 请求 (HttpContext.User) 关联。
|
| • | 如果您的应用程序使用的是 Windows 身份验证但没有使用企业服务,则请使用 .NET 角色。请记住,对于 Windows 身份验证,.NET 角色即为 Windows 组。
|
| • | 如果您的应用程序使用了 Windows 身份验证和企业服务,请考虑使用企业服务 (COM+) 角色。
|
| • | 对于使用企业服务 (COM+) 角色的有意义的基于角色授权,原调用方标识必须传递到企业服务应用程序。如果从 ASP.NET Web 应用程序中调用企业服务应用程序,这意味着 Web 应用程序必须使用 Windows 身份验证并且配置为使用模拟。
|
| • | 用 PrincipalPermission 属性对方法进行注释,以声明方式要求调用方提供角色成员身份。如果调用方没有指定的角色成员身份,则不会调用方法并且会产生安全异常。
|
| • | 在方法代码内调用 PrincipalPermission.Demand (或使用 IPrincipal.IsInRole),以实现细分授权决策。
|
| • | 考虑实现自定义 IPrincipal 对象以获得额外的角色检查语义。
|