智能客户端体系结构与设计指南 第 5 章 — 安全性考虑事项

来源:百度文库 编辑:神马文学网 时间:2024/04/28 00:55:46
show toc
欢迎来到 MSDN >体系结构 >智能客户端体系结构与设计指南
第 5 章 — 安全性考虑事项
发布日期: 8/20/2004 | 更新日期: 8/20/2004

智能客户端体系结构与设计指南
David Hill, Brenton Webster, Edward A. Jezierski, Srinath Vasireddy and Mohammad Al-Sabt, Microsoft Corporation; Blaine Wastell, Ascentium Corporation; Jonathan Rasmusson and Paul Gale, ThoughtWorks; and Paul Slater, Wadeware LLC
相关链接
Microsoft® patterns & practices 库http://www.microsoft.com/resources/practices/default.mspx
.NET 的应用程序体系结构:设计应用程序和服务http://msdn.microsoft.com/library/en-us/dnbda/html/distapp.asp
摘要:本章介绍智能客户端的安全性问题。智能客户端将逻辑和数据分布到客户端计算机;因此需要考虑的安全性和与瘦客户端应用程序相关的安全性是不同的,后者的数据和逻辑更多地被限制到服务器。本章讨论智能客户端应用程序中的数据安全性、身份验证、授权以及代码访问角色的安全性。

本页内容
身份验证
授权
输入验证
处理敏感数据
代码访问安全
小结
智能客户端是分布式应用程序,通常跨越多种不同的产品和技术。管理这些应用程序中的安全性是一件极具挑战性的事情。在服务器端,需要采用一种方法来保护网络、服务器本身及其应用程序。在客户端,应集中于利用平台(其中包括操作系统和 Microsoft? .NET Framework)的安全特性、客户端代码可以执行的特权操作(代码访问安全)以及与服务器平台(域)和服务器应用程序的交互。
有效的安全性取决于深层防御方法。在保护智能客户端时,考虑安全性的各个方面非常重要,其中包括以下几个方面:

身份验证。这唯一地标识了客户端应用程序的用户,从而只有经过认可的用户才能访问应用程序的全部或部分。

授权。这确定唯一标识的用户可以执行的操作。这些操作可以是任务,也可以是对授予经过身份验证的用户访问权限的资源进行的操作。

数据验证。这确保只有适当的和有效的数据才能被应用程序接受。如果允许任何用户输入而不首先验证数据,则攻击者就可以通过插入恶意的输入来危及应用程序的安全。

保护敏感数据。这意味着确保应用程序存储和传输的敏感数据(例如密码或机密的商业数据)是安全的。对敏感数据进行加密可以确保数据不可能以明文形式获得;取决于算法的选择,这还可以确保信息不会被篡改,从而维护其完整性。

审核和日志记录。这包括保存对事件和用户操作的记录。应该考虑将关键的用户操作或活动记录在服务器上,或者安全地记录在客户端上,因为客户端计算机上的日志可能被篡改或清除。

异常管理。这确保应用程序适当地处理异常和失败,并且返回用户友好的非敏感信息。异常详细信息可以记录到事件日志或应用程序日志中。

更改和配置管理。这确保跟踪 IT 环境的配置以及对其进行的任何更改。通过这样做,可以查看是否出现任何未经授权的更改,并且确定任何经授权的更改所涉及的安全性含义。
本章详细描述了在设计智能客户端应用程序时将会面临的一些关键安全性问题,具体来说,重点介绍了身份验证、授权、数据验证和保护敏感数据。同时,本章还介绍了代码访问安全,它是 .NET Framework 中的一项关键技术,用于管理代码级而非用户级安全性。
在检验智能客户端安全性时还需要考虑的另一个重要方面是如何部署智能客户端。有关影响部署的安全性问题的详细信息,请参阅第 7 章:智能客户端的部署与更新。
注在应用程序中使用的任何代码都应该使用 FxCop 进行分析。通过这个工具,可以检查托管代码程序集是否符合 .NET Framework 设计指导原则,其中包括符合基本级别的安全性。可以从 GotDotNet 站点http://www.gotdotnet.com/team/fxcop/ 下载 FxCop。
身份验证
身份验证是通过检验他的或她的凭据来唯一标识用户的过程。当用户试图运行或安装应用程序时,或者当应用程序建立到远程服务的连接或访问本地保存的数据时,都可能需要用户身份验证。
这一节分析智能客户端常见的一些身份验证方案,介绍对网络调用进行身份验证的几种不同方式,并讨论如何收集用户凭据和在脱机时检验这些凭据。
智能客户端身份验证方案
取决于智能客户端应用程序的样式和功能,在用户与应用程序交互的过程中,可能需要在一个或多个阶段对用户进行身份验证。对用户进行身份验证可供选择的阶段有四个:

在应用程序安装时

在应用程序运行时

在用户访问本地保存的敏感数据时

在用户通过网络访问外部服务时
经过身份验证的安装
如果应用程序是集中部署的(例如,使用无接触部署),则可能需要保护 Web 服务器上的应用程序,以便只有经过授权的用户才能安装它们。这些用户必须首先经过 Web 服务器的身份验证,Web 服务器查看他们是否被授权访问该应用程序和将其下载到他们的客户端计算机上。
保护对无接触部署的智能客户端应用程序的访问与保护任何其他位于 Web 服务器的构件(例如 Web 页面)相似。Microsoft Internet Information Services (IIS) 提供了许多身份验证机制,例如集成 Windows、摘要或基本身份验证。
注如果使用无接触部署并且应用程序使用配置文件来存储它的配置设置,则不适合使用摘要和基本身份验证,因为 .NET Framework 不能使用这些机制自动下载配置文件。
在选择适当的身份验证机制并且 IIS 可以根据他的或她的凭据标识用户之后,就可以通过设置应用程序和程序集文件上的文件权限来保护应用程序及其依赖程序集。为了简化对大量用户的管理,可以考虑提供对 Microsoft Windows 组(例如 SmartClientAppUsers)的访问,并将各个用户放在这个组中。
所有需要进行身份验证的用户都必须具有服务器上的 Windows 标识,这样 IIS 就可以保护对应用程序及其程序集的访问,但是他们未必需要使用这一标识登录到他们的客户端计算机上。如果用户的登录帐户不为服务器端所知,则当他或她单击指向应用程序的可执行文件的链接时,系统就会提示用户输入用户名和密码。
如果使用集成 Windows 身份验证,则系统就会自动使用已登录用户的凭据来尝试获取对应用程序的访问权限。当用户通过为客户端和服务器所共有的标识登录时,就可以进行无缝且安全的访问。
经过身份验证的应用程序访问
对安装应用程序的用户进行身份验证可以确保,只有经过身份验证和授权的用户才能从中心位置运行应用程序。然而,因为应用程序及其依赖构件可能缓存在客户端计算机上,所以不能在应用程序每次运行时都依赖于这一机制来对用户进行身份验证。在这种情况下,或者当用户有意在本地部署应用程序时,就需要仔细考虑如何通过应用程序对用户进行身份验证。可能需要在用户每次运行应用程序时就对他们进行身份验证,特别是如果应用程序提供敏感功能,或者需要能够撤消用户在任何时候运行应用程序的授权。
在用户使用为客户端和服务器所共有的标识登录到客户端计算机的情况下,可以依赖用户能够登录客户端计算机的事实,并以此作为对运行应用程序的充分身份验证。通过这种方法,可以使用 Microsoft Windows 操作系统来提供用户身份验证,从而使得不必需要用代码实现用户身份验证。另外,因为 Windows 可以在脱机时缓存用户凭据,所以您不需要自己缓存它们。
对于您对用户访问没有任何控制的客户端计算机(例如您的组织的 intranet 外的客户端计算机),您可能需要采用一种自定义身份验证机制来收集用户凭据,并且根据远程安全性授权对他们进行身份验证。如果应用程序能够脱机操作,您就需要在客户端缓存有效的凭据,这样,当他或她启动应用程序时,您就可以根据它们重新对用户进行身份验证。您应该定期强制执行联机重新身份验证,这样就可以防止对这样的应用程序的无限制使用。
经过身份验证的本地数据访问
智能客户端应用程序常常缓存从远程服务获得的数据(例如,为了提高响应速度或者为了支持脱机功能)。如果数据是敏感的,则可能需要在授予用户访问数据的权限之前对其进行身份验证。在这种情况下,可以选择允许未经身份验证的用户运行应用程序,但是需要在授予用户访问敏感数据的权限之前对其进行充分的身份验证和授权。
注确保只有授权用户访问的数据才能在本地缓存非常重要。如果数据是敏感的,则还需要确保采取足够的措施来保证数据的安全。详细信息请参阅本章后面的“处理敏感数据”。本地保存的数据应该保存在安全的位置并进行加密。不管如何对用户进行身份验证,通常都需要以某种方式使用他们的凭据来访问和解密数据。
您可能能够使用用于登录到客户端计算机的默认凭据,也可能需要获得自定义凭据来根据远程安全性授权对用户进行身份验证。对于在 intranet 环境中运行的应用程序,前一种方式最为合适,而对于在 Internet 或外部网环境中运行的应用程序,由于用户通常不在与他们访问的远程服务相同的域中,所以后一种方式比较合适。使用集成 Windows 身份验证的一个好处是,操作系统可以对用户进行身份验证,保护应用程序和本地数据,并且可以在用户脱机时缓存用户凭据。
有关在本地缓存敏感数据的详细信息,请参阅本章后面的“处理敏感数据”。
经过身份验证的网络访问
您可能会选择支持对应用程序的匿名访问并允许任何用户下载和运行它。然而,在应用程序运行在客户端之后,它通常需要通过网络访问远程服务(例如 Web 服务)以获得数据和服务。
通常需要保护对网络数据和服务的访问,以防止未经授权的访问。保护远程服务访问的方式可能有许多种,但通常需要将用户凭据传送到远程服务,以便它可以执行用户身份验证。
当用户通过网络访问远程服务时对他们进行身份验证是一项非常重要的内容。本章后面的“网络访问身份验证类型”比较完整地描述了确保对网络服务调用进行身份验证的一些选择。
选择正确的身份验证模型
前一节描述了您可能选择用来对用户进行身份验证的四个阶段。您可能选择在一个或多个阶段对用户进行身份验证,这取决于您的应用程序及其功能的性质。选择正确的阶段是非常重要的,它既可以帮助确保应用程序和数据的安全,又可以将对应用程序的使用的任何影响降低到最低限度。
如果您的应用程序是集中部署的(例如,如果使用无接触部署进行部署,或者部署到文件共享),则您可能选择将访问仅限于经过授权的用户。如果您想让您的应用程序对任何想要使用它的人都可用,则不需要在安装应用程序时对用户进行身份验证。
客户端计算机通常在物理上并不安全,甚至可能能够进行公开访问。如果是这种情况,并且应用程序提供敏感功能,则往往需要在应用程序运行时对用户进行身份验证。如果应用程序提供的是匿名用户可用的常规功能,则不需要在这一阶段对用户进行身份验证。然而,您可能会选择给匿名用户提供应用程序的部分功能,但是需要在允许他们访问更多受限功能之前对其进行身份验证。
保护对本地保存的敏感数据的访问非常重要。如果应用程序部署在物理上不安全或者可以公开访问的设备上,则应该对敏感数据进行保护,并且只有经过身份验证和授权的用户才能访问它们。应用程序可以给匿名用户提供常规功能,但是在用户试图访问敏感数据时需要对他们进行身份验证。
在应用程序脱机运行时,使用集成 Windows 身份验证也有好处。在这种情况下,Windows 缓存用户凭据,以便当用户登录到脱机客户端计算机时对他(她)进行身份验证。如果您需要在应用程序运行或访问本地保存的敏感数据时对用户进行身份验证,则这一行为使得您的客户端不需要对用户进行身份验证。
网络访问身份验证类型
有许多不同的技术可以用来在用户访问远程服务时对其进行身份验证,其中包括:

集成 Windows 身份验证。

HTTP 基本身份验证。

HTTP 摘要身份验证。

基于证书的身份验证。

基于 Web Services Enhancements (WSE) 的身份验证。

自定义身份验证。
集成 Windows 身份验证
对于集成 Windows 身份验证,用户是通过使用有效的 Windows 帐户登录到他的或她的计算机来进行身份验证的。凭据可以是本地帐户(对于计算机来说,帐户是本地的)或域帐户(Windows 域的有效成员)。在用户的整个会话期间,应用程序都可以透明地使用登录时所建立的标识来访问资源。这意味着应用程序可以用一种安全的方式提供对网络资源(例如 Web 服务)的无缝访问,而不必提示用户输入其他凭据或重复输入凭据。
注只有当网络资源也使用集成 Windows 身份验证时,对网络资源的访问才是无缝的。
集成 Windows 身份验证使用以下两种身份验证协议中的一种:Kerberos 或 NTLM。这两种技术都不通过网络传送用户名和密码组合来对用户进行身份验证或验证。因此,您的应用程序或基础结构在传输的过程中不必提供附加的安全性来管理凭据。
依赖于 Windows 安全性的客户端应用程序使用名为 WindowsIdentity 的 IIdentity 接口的实现。
注.NET Framework 也提供紧密相关的 IPrincipal 接口。有关 IIdentity 和 IPrincipal 接口的更多详细信息,请参阅本章后面的“授权”。
使用集成 Windows 身份验证的 Web 服务
对于为集成 Windows 身份验证配置的 Web 服务,客户端应用程序可以在调用 Web 服务之前提供当前登录的用户的凭据来进行身份验证。当从 Microsoft Visual Studio .NET 开发系统将引用添加到应用程序中的 Web 服务时,系统会自动生成一个代理类并将其添加到项目中,以便通过编程方式访问 Web 服务。下面的代码演示了如何设置当前登录的用户的用户凭据。
MyService service = new MyService(); // A proxy for a web service. service.Credentials = CredentialCache.DefaultCredentials; service.SomeServiceMethod(); // Call the web service.
在本例中,DefaultCredentials 使用应用程序正在其中运行的安全性上下文,它通常是运行应用程序的用户的 Windows 凭据(用户名、密码和域)。
HTTP 基本身份验证
HTTP 基本身份验证是由 IIS 提供的。在进行基本身份验证时,IIS 会提示用户输入一个有效的 Windows 帐户和密码。这一组合是作为经过编码的明文从客户端传送到服务器,并用于在 Web 服务器上对用户进行身份验证。
注为了保护基本身份验证,您需要保护客户端和服务器之间的通信信道(例如,通过启用服务器上的安全套接字层 [SSL]),以确保用户名/密码组合被加密并且在传输时不会被篡改或截获。同时,您还需要保护位于服务器上的密码。然而,SSL 只能保护两个已定义的终结点之间的通信。如果您需要保护两个以上的终结点之间的通信,则需要使用基于消息的安全性。
使用基本身份验证的 Web 服务
对于与为基本身份验证配置的 Web 服务交互的客户端应用程序,客户端可以使用登录对话框来接受有效的用户凭据,并用它来进行身份验证。下面的代码演示了如何为需要基本身份验证的 Web 服务代理设置用户凭据。
CredentialCache cache = new CredentialCache(); cache.Add( new Uri( service.Url ), // Web service URL. "Basic", // Basic Authentication. new NetworkCredential( userName, password, domain ) ); service.Credentials = cache;
在本例中,userName、password 和 domain 是作为登录对话框的一部分被接受的。
HTTP 摘要身份验证
HTTP 摘要身份验证提供与 HTTP 基本身份验证相同的功能,但是采用不同的方式来传输身份验证凭据。身份验证凭据是在一种称为哈希 的单向处理中进行转换的。这种处理的结果称为哈希 或 消息摘要,使用当前的技术不可能对其进行解密。
摘要身份验证按照以下过程进行:
1.
服务器给浏览器发送将在身份验证过程中使用的特定信息。
2.
浏览器将此信息以及一些其他信息添加到它的用户名和密码中,然后对其进行哈希运算。附加信息有助于防止有人复制哈希值并再次使用它。
3.
得到的哈希连同附加信息以明文的方式通过网络发送到服务器。
4.
服务器将附加信息添加到它所拥有的客户端密码的明文副本中,并对所有的信息进行哈希。
5.
服务器将它接收到的哈希值与它刚产生的哈希值进行比较。
6.
只有当两个值相同时才授予访问权限。
附加信息是在哈希之前添加到密码中的,这样就没有人可以捕获密码哈希并利用它来冒充真实客户端。帮助标识客户端、客户端计算机和客户端所属的领域或域的值也被添加到密码中。同时添加的还有时间戳,以防止客户端在密码被撤消后还使用密码。
因为摘要身份验证以哈希形式通过网络发送密码,所以它明显优于基本身份验证,特别是在使用基本身份验证而没有对通信信道进行加密时。因此,如果可能,您应该使用摘要身份验证来代替基本身份验证。
注如同基本身份验证的情况一样,只有在向其发出请求的域服务器有请求用户的密码的明文副本时,摘要身份验证才完成。因为域控制器有密码的明文副本,所以您必须确保该服务器是安全的,不会受到物理和网络攻击。
基于证书的身份验证
通过使用证书,客户端和服务器应用程序可以使用计算机上安装的数字密钥相互进行身份验证,或建立安全连接。客户端应用程序可以使用客户端证书来向服务器标识自己,同样,服务器也可以使用服务器证书来向客户端标识自己。一个双方都信任的第三方(称为证书颁发机构)可以确认这些证书的标识。客户端证书可以映射到 Microsoft Active Directory? 目录服务中的 Microsoft Windows 帐户。
您可以建立一个站点,使没有证书的用户以来宾身份登录,而有证书的用户以他的或她的证书所映射的用户的身份登录。然后,您可以基于该证书自定义站点。
如果您需要对单个用户进行身份验证,您可以使用一种称为“一对一映射”的技术,这种技术会将证书映射到单个帐户。如果您需要对来自特定组或组织的所有用户进行身份验证,您可以使用多对一映射,例如,它可以将包含共同的公司名称的所有证书映射到单个帐户。
在基于证书的身份验证中,客户端应用程序使用的是可以由 Web 服务进行身份验证的证书。在这种情况中,客户端应用程序使用 X.509 证书来对 SOAP 消息进行数字签名,以确保消息来自受信任的来源,并且在到达指定的 Web 服务之前没有在传输的过程中被篡改。
基于证书的身份验证的一个主要考虑事项是如何处理证书将不再有效的情况。例如,如果一个雇员使用证书来进行身份验证,之后该雇员被解雇了,则此证书应该不再允许该用户访问资源。因此,证书安全性基础结构包括对证书撤消列表的管理非常重要。这些列表放在服务器上,每当客户端连接到网络资源时都将进行检查。
当智能客户端脱机工作时,不能对基于服务器的撤消列表进行检查,因此一个不能访问服务器上的资源的用户有可能对客户端上的资源进行本地访问。为了帮助解决这个问题,您可以选择相对短的客户端证书租约时间。短的租约时间会强制客户端定期连接到证书服务器,并且在续订租约和允许连接到服务器端应用程序之前确认证书尚未撤消。
有关详细信息,请参阅http://www.microsoft.com/resources/documentation/windowsserv/2003/standard/proddocs/en-us/sec_auth_certabout.asp 上的“About Certificates”。
基于 WSE 的身份验证
使用 Web Services Enhancements 版本 2.0,可以通过编程方式签署 Web 服务的 SOAP 消息。WSE 2.0 是一个实现,它支持各种新兴的 Web 服务标准,例如 WS-Security、WS-SecureConversation、WS-Trust、WS-Policy、WS-Addressing、WS-Referral、WS-Attachments 和 Direct Internet Message Encapsulation (DIME)。WSE 提供一种编程模型来实现它所支持的各种规范。
使用 WSE 的客户端应用程序可以使用 X509CertificateStore 类中的某种 Find 方法(例如 FindCertificateByHash 或 FindCertificateByKeyIdentifier)通过编程方式从存储区中选择一个证书,使用该证书创建数字签名,将其添加到 WS-Security SOAP 头并调用 Web 服务。另外,客户端应用程序还可以选择打开当前登录用户的证书存储区,如下面的代码示例所示。
X509CertificateStore store; store = X509CertificateStore.CurrentUserStore( X509CertificateStore.MyStore ); bool open = store.OpenRead();
有关详细信息,请参阅http://msdn.microsoft.com/webservices/building/wse/default.aspx 上的“Web Services Enhancements”。
有关使用客户端证书的详细信息,请参阅 WSE 2.0 文档中的“Signing a SOAP Message Using an X.509 Certificate”。
自定义身份验证
在某些情况下,Windows 提供的标准身份验证选择并不适用于您的应用程序,您将可能需要设计您自己的身份验证形式。幸运的是,.NET Framework 提供了一些选择来帮助您设计自定义身份验证解决方案。
.NET Framework 支持 IIdentity 的一个实现,称为 GenericIdentity。您可以使用 GenericIdentity,也可以创建您自己的自定义标识类。设计自定义身份验证解决方案可能比较困难,因为您必须自己采取措施来确保方法是安全的。您可能还需要为您的标识维护单独的存储区。
收集和验证用户凭据
不管您使用的是什么形式的身份验证,您都需要收集用户凭据,然后可以对其进行验证。对于已经使用集成 Windows 身份验证登录的用户,您可能只需收集现有的凭据,而对于自定义身份验证解决方案,您可能需要通过您自己的登录对话框来安全地收集凭据。
注不要将用户凭据存储在代码中超过必要的时间。特别是,不要用全局变量存储凭据,它们是通过可公开访问的方法或属性提供访问的,另外,也不要将凭据保存在磁盘上。
收集当前登录的用户的凭据
如果您使用的是集成 Windows 身份验证,则您的用户是在他们的 Windows 会话开始时登录的。然后,您的应用程序可以使用这一信息来确保他们具有适当的凭据用于运行。
下面的代码演示了基本功能。
using System.Security.Principal; // Get principal of the currently logged in user. WindowsPrincipal wp = new WindowsPrincipal( WindowsIdentity.GetCurrent() ); // Display the current user name. label1.Text = "User:" + wp.Identity.Name; // Determine if user is part of a windows group. if( wp.IsInRole( "YourDomain\\YourWindowsGroup" ) ) { // Is a member. } else { // Is not a member. } 使用登录对话框收集用户凭据
如果您设计您自己的登录对话框接受来自用户的凭据,则需要采取大量的措施来确保您满足您的组织的安全性要求(例如强制执行强密码策略和在定期的时间间隔内让密码过期)。在设计登录对话框时,请考虑使用下列指导原则:

不要盲目信任用户输入。如果您盲目信任用户输入,恶意用户就可能危及您的应用程序的安全。例如,如果不对动态构造 SQL 代码进行验证,使用输入的应用程序就容易受到 SQL 插入攻击。

检查输入数据的类型、格式或范围。考虑使用正则表达式来进行这些检查。使用正则表达式可以检查类型(例如,字符串或整型)、格式(例如,强制密码策略要求使用诸如数字、特殊符号及大小写字母字符混合等)和范围(例如,用户名称最少包含 6 个字符,最多包含 25 个字符)。
下面的代码示例强制要求密码长度为 8 至 10 个字符,并且是大小写字母和数字字符的组合。
// Validate the user supplied password. if( !Regex.Match( textBox1.Text, @"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$", RegexOptions.None ).Success ) { // Invalid email address. }

在设计带有密码字段文本框的对话框时,请确保 PasswordChar 属性设置为当文本输入控件时显示的字符,如下面的示例所示。
// The password character is set to asterisk. textBox1.PasswordChar = ?*‘;
身份验证指导原则
在为应用程序设计身份验证时,应该考虑使用下列指导原则:

确定在用户与应用程序交互时什么阶段需要进行身份验证。

考虑在用户登录到客户端时使用集成 Windows 身份验证对他们进行身份验证,之后用户就可以访问应用程序、应用程序的数据和任何远程服务。

如果应用程序是集中部署的并且需要将访问仅限于经过授权的用户,就应该在应用程序运行时使用 IIS 提供的身份验证机制之一对用户进行身份验证。

如果应用程序提供敏感功能或者提供对本地保存的敏感数据的访问,则应该确保在允许用户访问之前对他们进行适当的身份验证。

如果应用程序需要自定义身份验证,则应该确保应用程序强制执行强用户名和密码策略。作为惯例,应该需要最少 8 个字符和大小写字母字符、数字和特殊字符的混合。

如果远程服务提供敏感功能或者提供对敏感数据的访问,则需要对用户进行身份验证,以便他们通过网络访问远程服务。

确保用户凭据在没有保护措施的情况下不通过网络进行传输。有些身份验证形式根本不通过网络传送用户凭据,但是如果必须传送,就应该确保它们经过加密,或者通过安全连接进行传送。
有关详细信息,请参阅 Improving Web Application Security: Threats and Countermeasures 的“Chapter 4 — Design Guidelines for Secure Web Applications”中的“身份验证”,地址在http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/THCMCh04.asp。
返回页首
授权
在对用户进行身份验证之后,可以通过授权来确定他们有权访问系统内的什么内容。授权是确认经过身份验证的用户具有执行某种操作的权限的过程。授权管理经过身份验证的用户可以访问的资源(例如,文件和数据库)和可以执行的操作(例如,更改密码或删除文件)。没有经过身份验证的用户(即匿名用户)不能进行特别授权,而是需要分配一组默认权限。
许多因素严格地确定了您可以如何在您的环境中执行授权。您需要确定是基于应用程序的功能还是基于系统资源来管理授权。您需要确定是执行方法内的细粒度授权还是执行方法级的检查。此外,您还需要确定在何处存储授权所需的用户信息(例如,在 Active Directory 或 Microsoft SQL Server 数据库中)。如果您将允许您的智能客户端能够脱机工作,则需要有对脱机客户端进行授权的策略。
.NET Framework 提供 IPrincipal 接口,它可以与 IIdentity 接口一起,用于定义管理运行代码的安全上下文的属性和方法。同时提供的还有该接口的两个实现:WindowsPrincipal 和 GenericPrincipal。使用集成 Windows 身份验证的客户端应用程序使用的是 WindowsPrincipal,而使用自定义身份验证的客户端应用程序使用的是 GenericPrincipal。
授权的类型
Windows 操作系统中常用的授权方法有两种:基于资源的授权和基于角色的授权。基于资源的授权依赖于访问控制列表 (ACL),而基于角色的授权则基于用户角色执行授权。
基于资源的授权
对于基于资源的授权,可以将任意访问控制列表 (DACL) 附加到保险对象。然后,系统作出访问决定,方法是比较令牌中的组成员身份与 ACL 的内容,以确定该用户是否具有所请求的访问权限。对于许多类型的应用程序,ACL 模型是非常理想的。然而,它并非适合于所有的情况。例如,您可能需要基于业务逻辑或者基于在需要时创建的非持久性对象来作出访问决定。
基于角色的授权
基于角色的授权允许您将用户和组与他们完成其工作所需的权限相关联。当将一个用户或组添加到一个角色中时,该用户或组就会自动地继承各种安全权限。这些权限可以是执行操作的权限,也可以是访问各种资源的权限。图 5.1 说明了基于角色的授权中角色与权限之间的关系。

图 5.1 基于角色的授权
在 Microsoft Windows 2000 Server Service Pack 4 (SP4) 和 Windows Server 2003 操作系统中,基于角色的授权通常是通过授权管理器管理的。授权管理器是一组基于 COM 的运行时接口、以及用于配置的 Microsoft 管理控制台 (MMC) 管理单元。开发人员可以使用授权管理器确保应用程序能够管理和验证客户端对执行应用程序操作的请求,而应用程序管理员则可以用它来管理用户角色和权限。通过使用授权管理器,可以将低级操作聚集成称为“Tasks”的组,并管理这一级别的授权。同时它还使得您能够在授权前后运行自定义授权逻辑。
授权管理器的一个显著优点是,它可以从需要授权的应用程序中进一步抽象授权存储,这意味着开发人员可以一直与授权管理器通信,而不管存储是在 Active Directory 中还是基于文件的。
给应用程序添加授权功能
.NET Framework 提供了许多选择来给应用程序添加授权功能。这些选择包括:

使用 PrincipalPermissionAttribute 执行声明性 Demand。

使用 PrincipalPermission 对象执行命令性 Demand。

使用 IsInRole 方法执行角色检查。

执行用于自定义身份验证的角色检查。
使用 PrincipalPermissionAttribute 执行声明性 Demand
可以将 Demand 放在类级或成员级的单个方法、属性或事件上。如果将声明性 Demand 同时放在类级和成员级上,则成员级的声明性 Demand 会重写(或替换)类级的 Demand。
下面的代码示例显示了 PrincipalPermission 对象的声明性 Demand。
// Declarative example. [PrincipalPermissionAttribute( SecurityAction.Demand, Role="Teller" )] void SomeTellerOnlyMethod() { } 使用 PrincipalPermission 对象执行命令性 Demand
可以执行命令性 Demand,方法是通过编程方式调用 PrincipalPermission 对象的 Demand 方法,如下面的代码示例所示。
// Programmatic example. public SomeMethod() { PrincipalPermission permCheck = new PrincipalPermission( null, "Teller" ); permCheck.Demand(); // Only Tellers can execute the following code. // Non members of the Teller role result in a security exception. . . . }
通过编程方式调用该方法的一个优点是,可以确定主体是否在多个角色中。.NET Framework 不允许以声明的方式这样做。下面的代码示例显示了如何执行这一检查。
// Using PrincipalPermission. PrincipalPermission permCheckTellers = new PrincipalPermission( null, "Teller" ); permCheckTellers.Demand(); PrincipalPermission permCheckMgr = new PrincipalPermission( null, "Manager" ); permCheckMgr.Demand(); 使用 IsInRole 方法执行角色检查
可以选择直接访问主体对象的值并执行检查,而不使用 PrincipalPermission 对象。在这种情况下,可以读取当前线程的主体的值,也可以使用 IsInRole 方法来执行授权,如下面的代码示例所示。
// Using IsInRole. if ( Thread.CurrentPrincipal.IsInRole( "Teller" ) && Thread.CurrentPrincipal.IsInRole( "Manager" ) ) { // Perform privileged operation. } 执行用于自定义身份验证的角色检查
如果应用程序不是基于 Windows 的,则可以通过编程方式将一组从自定义身份验证数据存储区(例如 SQL Server 数据库)中获得的角色填充 GenericPrincipal 对象,如下面的代码示例所示。
GenericIdentity userIdentity = new GenericIdentity( "Bob" ); // Typically role names would be retrieved from a custom data store. string[] roles = new String[]{ "Manager", "Teller" }; GenericPrincipal userPrincipal = new GenericPrincipal( userIdentity, roles ); if ( userPrincipal.IsInRole( "Teller" ) ) { // Perform privileged operation. } 授权指导原则
授权对控制用户访问应用程序功能和资源的权限非常关键。不适当的或弱的授权会导致信息泄露和数据篡改。请考虑使用下列指导原则:

尽可能地使用多个网关守卫以在用户访问资源或执行操作时强制执行授权检查。使用客户端检查和服务器上的检查相结合的方法来提供深层防御,以防止设法绕开其中一个网关守卫的恶意用户的攻击。服务器上常见的网关守卫包括 IIS Web 权限、NTFS 文件系统权限、Web 服务文件授权(只适用于 Windows 身份验证)和主体权限 Demand。

使用用户的安全上下文授权访问系统资源。可以使用基于角色的授权来基于用户标识和角色成员身份授权访问。集成 Windows 身份验证与安全资源(例如文件或注册表)上的 Windows ACL 共同确定是否允许调用者访问资源。对于程序集,可以基于诸如程序集的强名称或位置等证据来授权调用代码。

确保使用足够的粒度来定义角色,以便充分分离特权。避免只为满足某些用户的要求而授予提高的特权;相反,考虑添加新的角色来满足这些要求。

尽可能地使用声明性 Demand 而非命令性 Demand。声明性 Demand 提供或拒绝对方法的全部功能的访问。另外,它们还可以更好地与安全工具一起使用,并且有助于进行安全审核,因为这些工具能够通过检验应用程序来访问这些 Demand。

如果您需要确定主体是否在多个角色中,则可以考虑使用 IsInRole 方法进行命令性检查。.NET Framework 版本 1.1 不允许以声明的方式执行 AND 检查;然而,可以通过编程的方式在方法内执行它们,如下面的代码示例所示。
// Checking for multiple roles. if ( Thread.CurrentPrincipal.IsInRole( "Teller" ) && Thread.CurrentPrincipal.IsInRole( "Manager" ) ) { // Perform privileged operation. }

使用代码访问安全来授权调用对特权资源或操作的代码访问(基于诸如程序集的强名称或位置这样的证据)。有关详细信息,请参阅本章后面的“代码访问安全”。
客户端脱机时授权使用功能
当用户连接到网络时,可以直接根据网络授权存储对他们进行授权;但是当他们没有连接到网络时,仍然可能需要对他们进行授权。
任何形式的授权都不会比所使用的身份验证机制更强。如果您允许匿名身份验证,您就应该特别注意允许用户访问什么功能,并且通常不应该授权用户访问系统资源。
如果您授权用户使用应用程序,则可以让 Windows 担当唯一的网关守卫来确定哪些资源可用于已登录用户的配置文件。在这种情况下,通常只允许用户访问本地系统资源。
您可以选择为不同的角色创建同一个应用程序的不同版本。当用户连接到网络时,他或她只被允许安装与其角色相符的应用程序版本。然后,当用户脱机运行应用程序时,无需为应用程序建立连接即可自动提供正确的功能。
授权和配置文件应用程序块
Microsoft 提供了一个应用程序块,它能够提供基础结构来简化在应用程序中加入授权功能的过程。
授权和配置文件应用程序块为基于角色的授权和对配置文件信息的访问提供基础结构。该应用程序块允许您执行下列操作:

对应用程序或系统的用户进行授权。

使用多个授权存储提供程序。

插入操作验证的业务规则。

将多个标识映射到单个用户,从而将标识的概念扩展到包括身份验证方式。

访问可以存储在多个配置文件存储区中的配置文件信息。
有关详细信息,请参阅http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/authpro.asp 上的 Authorization and Profile Application Block。
返回页首
输入验证
输入验证不严格的应用程序可能会受到攻击者恶意输入的威胁。验证用户输入是保护应用程序的最前沿防线中的一道防线。请考虑使用下面针对智能客户端应用程序的输入验证指导原则:

确保智能客户端应用程序在处理输入或将输入传送到下游资源和程序集之前验证了所有的输入。

如果您要将输入数据传送给非托管 API,请对用户输入数据进行彻底的验证。这样做有助于防止缓冲区溢出。您还应该限制用户输入传送到非托管 API 的数据。

始终验证从所有外部来源(例如 Web 站点和 Web 服务)获得的数据。

切勿依赖对传送到 Web 服务器或者 Web 应用程序的数据进行的客户端验证。首先在客户端验证数据,然后再次在服务器上验证数据,以便防止绕过客户端验证的恶意输入。

切勿允许用户直接输入 SQL 查询。始终提供针对安全问题进行了彻底的检查的预先打包的或参数化查询。允许用户直接输入 SQL 查询可能带来 SQL 插入攻击。

用已知的正确值或模式(而不是用错误的输入)来约束并验证用户输入。检查一个有限已知值的列表比检查一个无限未知恶意输入类型的列表简单得多。在对输入值采取行动之前,既可以拒绝有害的输入,也可以对输入进行净化(即除去潜在的不安全字符)。

通过验证输入的类型、长度、格式和范围来约束它。一种这样做的方法就是使用正则表达式(可以从 System.Text.RegularExpressions 命名空间中获得)来验证用户输入。

拒绝未知的有害数据,然后净化输入。如果应用程序需要接受某些自由格式的用户输入(例如,文本框中的注释),可以对输入进行净化,如下面的示例所示:
private string SanitizeInput( string input ) { // Example list of characters to remove from input. Regex badCharReplace = new Regex( @"([<>""?%;()&])" ); string goodChars = badCharReplace.Replace( input, "" ); return goodChars; }

考虑集中验证例程,以减少开发工作量并有助于未来的维护。
有关详细信息,请参阅 Improving Web Application Security: Threats and Countermeasures 的“Chapter 4 — Design Guidelines for Secure Web Applications”中的“输入验证”,地址在http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/THCMCh04.asp。
返回页首
处理敏感数据
如果您经常设计 Web 应用程序,您就会理解保护存储的数据和传输的数据的重要性。存储在 Web 服务器上的数据通常被写入物理上安全的位置,这样的位置已经很好地被保护起来以防止受到攻击。在智能客户端应用程序中,您同样需要仔细考虑驻留在客户端上的数据。如果这些数据是敏感的,适当地对其进行处理以确保安全就非常重要。为了保护传输的数据,您可以使用 SSL 保护传输层,并且使用 WS-Security 或 Message Queuing 加密工具保护消息内容。
只有授权用户访问的数据才应该可用于客户端应用程序。如果一台计算机上的多个人都可以使用客户端应用程序,则与每个单独的用户相关的数据都应该被认为是敏感数据,并且应该采取一些措施来确保只有经过授权的用户可以访问它。
敏感数据包括攻击者可能发现对访问或修改有用的任何数据,因为这些信息是机密的或者对攻击有帮助。敏感数据可以是服务器提供给客户端的数据,但也可以包括应用程序配置文件、本地数据库或注册表信息。
在通常情况下,应该尽力确保敏感数据不本地缓存。然而,在智能客户端应用程序的情况下,可能需要缓存这些数据(例如,为允许偶尔连接的操作,将数据保存在本地存储区以供日后使用)。
注在某些情况下,敏感数据可能由于内存中的分页而发送到磁盘上。因此,在确定需要对什么样的数据进行加密时,也应该考虑存在于内存中的数据。
确定将哪些数据存储在客户端上
根据定义,用户(因而是潜在的攻击者)可以在物理上接触客户端。如果给予足够的时间,攻击者常常能够获得足够的管理权限来访问几乎任何数据,因此您应该仔细考虑应该将什么样的数据存放在客户端。作为一般的规则,您应该对服务器进行授权决策,这样,就只有您从服务器传送到客户端的数据是允许用户访问的数据。除了提高性能之外,对服务器进行授权决策也可以确保这种数据不会被客户端上的潜在攻击者访问得到。
您应该从不将敏感数据存储在基于文本的文件中,并且应该始终对这些数据进行加密,这样就只有经过授权的用户才可以轻松地进行访问。您应该避免使用基于文本的配置文件来存储敏感的安全信息,例如密码或数据库连接字符串。如果这些信息必须在本地存储,您就应该对信息进行加密,并将其存储在文件或注册表项中,然后使用 DACL 来限制对该对象的访问。此外,还必须保护针对登录用户个人的任何永久性数据的隐私和安全,特别是当计算机在用户之间共享时。
在许多情况下,如果应用程序需要脱机运行,则较多的数据就会存储在客户端上。然而,您应该确定脱机时是否需要所有的数据,或者是否您想要限制用户在脱机时执行某些操作,这样您就不必将敏感数据缓存在本地。
在某些情况下,如果数据是机密的,并且可以按需要由用户输入,则可以选择根本不将数据本地存储在客户端上,而是在需要时从用户获得。
如果应用程序需要在本地存储敏感数据,通常应该避免使用可移动存储器(例如软盘、zip 磁盘、或 USB 存储设备)或外部便携式存储器来存储敏感数据。然而,当可以确保可移动媒体为该用户所有(例如,通过使用证书或智能卡)时,就可以将特定于用户的数据存储在可移动媒体中。因此,可以将特定于用户的数据存放在随用户一起移动的安全位置,这样,漫游用户就可以在不将数据保留在本地计算机上的情况下访问应用程序及其数据。
注当考虑将哪些敏感数据存储在客户端上时,您应该确保存储关于雇员或顾客的信息没有违反隐私法规。这些法律因国家/地区而异,因此您应该熟悉使用您的应用程序的国家/地区的隐私法规。
保护敏感数据的技术
对于需要存储在客户端上的数据,有许多可以采取的附加措施来防止未经授权的访问。这些措施包括下列内容:

确保只有经过授权的用户才可以访问数据。

考虑使用 EFS 加密文件。

考虑使用 DPAPI 避免密钥管理问题。

考虑存储哈希值而不是明文。

考虑部分受信任的应用程序的隔离存储。

保护私钥。
确保只有经过授权的用户才可以访问数据
数据常常需要进行保护,以帮助确保只有经过授权的用户才可以访问。取决于应用程序的性质和数据保持有效的时间,可以选择使用基于资源的安全性或基于角色的安全性来保护数据。有关详细信息,请参阅本章前面的“授权指导原则”。
考虑使用 EFS 加密文件
确保将文件安全地存放在智能客户端上的一种方法是使用加密文件系统 (Encrypting File System, EFS) 对敏感数据文件进行加密。这个解决方案在可扩展方面乏善可陈;然而,它对某些特定的文件非常有用,它也可以用于在客户端上本地缓存数据的情况(例如,用于启用偶尔连接的智能客户端)。
考虑使用 DPAPI 避免密钥管理问题
Windows 2000 和 Windows 操作系统的更高版本提供了用于加密和解密数据的 Win32? 数据保护 API (DPAPI)。它是加密 API (Crypto API) 的一部分,并且是用 crypt32.dll 实现的。它包含两个方法:CryptProtectData 和 CryptUnprotectData。
DPAPI 特别有用,因为它可以消除暴露给使用密码的应用程序的密钥管理问题。虽然加密可以确保数据是安全的,但是您必须采取额外的步骤来确保密钥的安全。为了派生加密密钥,DPAPI 使用与调用 DPAPI 函数的代码相关的用户帐户的密码。因此,是由操作系统(而不是由应用程序)管理密钥。
DPAPI 可以与机器存储区或用户存储区一起工作。用户存储区是基于登录用户的配置文件自动加载的。客户端应用程序通常与用户存储区一起使用 DPAPI,除非需要存储为所有可以登录到计算机的用户共有的机密。
DPAPI 用来加密和解密敏感数据的密钥是特定于计算机的。系统会为每台计算机生成一个不同的密钥,这可以防止一个服务器能够访问由另一个服务器加密的数据。
非托管 DPAPI 需要程序集具有完全的信任。具有完全和部分受信任的程序集的应用程序可以使用高特权来分离代码,并且使其能够从部分受信任的代码中进行调用。有关更多信息,请参阅http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secmod/html/secmod115.asp 上的“How To Create a Custom Encryption Permission”。
考虑存储哈希值而不是明文
有时,存储数据是为了使之可以用于验证用户输入(例如,用户名和密码的组合)。在这样的情况下,可以存储数据的加密哈希,而不是以明文的形式存储数据本身。然后,当用户进行输入时,还可以对该数据进行哈希运算,并且可以比较两个哈希。存储哈希减少了机密被发现的风险,因为从其哈希推导原始数据或从其他数据生成相同的哈希在计算上是不可能的。
考虑部分受信任的应用程序的隔离存储
隔离存储允许应用程序将数据保存到唯一的数据舱,数据舱与代码的标识的某些方面相关联,例如它的 Web 站点、发布者或签名。数据舱是一个抽象的概念,而不是一个具体的存储位置;它由一个或多个隔离的存储文件(称为存储)组成,存储包含存储数据的实际目录位置。
对于需要存储特定于特定用户和程序集的状态数据的部分受信任的应用程序,隔离存储特别有用。部分受信任的应用程序并不直接访问文件系统来保持状态,除非通过更改安全策略显式地授予它们这样做的权限。
存储在隔离存储中的数据是隔离的,并且受到保护,不会被其他部分受信任的应用程序访问,但是无法保护它不会被完全受信任的代码或有权访问客户端计算机的其他用户访问。为了在这些情况下保护数据,您应该通过使用 DACL 采用数据加密或者文件系统安全性。有关详细信息,请参阅http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconIntroductionToIsolatedStorage.asp 上的 .NET Framework Developer‘s Guide 中的“Introduction to Isolated Storage”。
保护私钥
未受保护的私钥易于受到恶意用户或恶意代码进行的各种各样的攻击。用于签署程序集的私钥不应该放在不安全的位置,例如开发人员的计算机或开放共享的环境。攻击者可以使用窃取的私钥签署带有您的强名称的恶意代码。您确实应该通过您的组织内为此目的而指定的中央安全机构来保护您的私钥。您也可以将私钥保存在物理上安全的隔离计算机上,并且在必要时使用便携式媒体来传递密钥。
有关有效地存储机密的详细信息,请参阅 Michael Howard 和 David LeBlanc 撰写的 Writing Secure Code, Second Edition。
返回页首
代码访问安全
代码访问安全是 .NET Framework 技术,它将身份验证和授权原则应用于代码而不是用户。代码访问安全是一种强大的机制,它确保只有用于运行的代码才可以被用户运行。
所有的托管代码都遵循代码访问安全机制。当加载一个程序集时,会授予它一组代码访问权限,以确定它可以访问什么资源和执行什么类型的特权操作。为了授予这些权限,.NET Framework 安全系统使用证据来对代码进行身份验证(标识)。
注 程序集是代码访问安全的配置和信任单元。相同程序集中的所有代码都接收相同的权限,因而是同等受信任的。
代码访问安全包括下列要素:

权限。权限代表代码访问安全资源或执行特权操作的权力。Microsoft .NET Framework 提供代码访问权限 和代码标识权限。代码访问权限封装访问特特定资源或者执行特定特权操作的能力。例如,在应用程序可以执行任何文件的 I/O 操作之前都需要 FileIOPermission。代码标识权限用来基于调用代码的标识的某个方面(例如,代码的强名称)限制对代码的访问。

权限集。.NET Framework 定义了许多权限集,它们代表一组通常作为一个整体分配的权限。例如,.NET Framework 定义的 FullTrust 权限集将所有的权限分配给完全受信任的代码;而 LocalIntranet 权限集则分配数量非常有限的权限。

证据。.NET Framework 安全系统使用证据来标识程序集。代码访问安全策略使用证据来帮助将恰当的权限授予恰当的程序集。证据可以是位置相关的(例如,URL、站点、应用程序目录或区域),也可以是作者相关的(例如,强名称、发布者或哈希)。

策略。代码访问安全策略是由管理员配置的,它确定授予程序集的权限。策略可以建立在企业、机器、用户和应用程序域级别上。每个策略都用一个 XML 配置文件定义。

代码组。每个策略文件都包含一些分层代码组。代码组用来给程序集分配权限。代码组由成员身份条件(基于证据)和权限集组成。.NET Framework 定义了许多默认的代码组,例如 Internet、本地 Intranet、受限制的和受信任的区域。
有关代码访问安全的详细信息,请参阅http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/THCMCh07.asp 上的 Improving Web Application Security: Threats and countermeasures:“Chapter 7 — Building Secure Assemblies”和http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/THCMCh08.asp 上的“Chapter 8 — Code Access Security in Practice”。
代码访问安全权限解析
代码访问安全使用图 5.2 中概述的步骤来确定将哪些权限分配给程序集。

图 5.2 确定将哪些权限分配给程序集
下面的步骤更详细地描述了这些过程:
1.
加载程序集,收集证据并将其提供给主机。
2.
根据用于宿主环境的安全策略来评估证据。
3.
这种评估的输出是一组授予程序集的权限。这些权限定义了在此环境中程序集能够做什么和不能做什么。
4.
当程序集请求执行特权操作时,系统就会将操作的请求与程序集的权限进行比较。如果程序集具有这样的权限,就允许代码执行该操作,否则,引发安全异常。
代码访问安全设计
分配给代码的权限取决于与代码相关的证据和客户端计算机上的适当安全策略。为了在保持应用程序的功能的同时确保其安全,您需要仔细考虑应用程序所需的权限、以及授予这些权限的方式。
授予所有权限的应用程序(这些应用程序定义在 FullTrust 权限集中)称为完全受信任的应用程序。没有授予所有权限的应用程序称为部分受信任的应用程序。
在理论上,将应用程序设计为部分受信任通常更可取。然而,智能客户端应用程序常常需要执行许多部分受信任的应用程序在默认情况下不能执行的操作。这些操作包括:

访问运行应用程序的服务器以外的服务器,或者访问使用不同协议的服务器。

访问本地文件系统。

访问本地 Microsoft Office 应用程序并与其交互。

访问并与非托管代码(例如 COM 对象)交互。
如果需要智能客户端执行这些种类的操作,则应该考虑使它成为一个完全受信任的应用程序,或者授予它正确地进行操作所需的其他特定权限。
注在默认情况下,使用无接触部署方式部署的应用程序自动成为部分受信任的。如果智能客户端需要执行部分受信任的应用程序不能执行的额外操作,则需要部署新的安全策略或者使用其他方法来部署应用程序。
设计和构建部分受信任的应用程序可能比较复杂,但是仔细考虑并限制授予应用程序的权限有助于确保应用程序不能执行不恰当的操作或者访问明显不需要的资源。
所有的代码在运行之前都必须给其授予运行的权限,但是访问安全资源或者执行其他安全敏感操作(例如调用非托管代码或访问本地文件系统)的代码必须由 .NET Framework 授予额外的权限才能运行。这样的代码称为特权代码。相反,非特权代码并不需要访问敏感资源,而只是需要运行的权限。当设计和构建应用程序及其程序集时,应该标识特权和非特权代码并将其存档。这样做有助于确定代码需要的权限。
还应该仔细检验 .NET Framework 使用哪些证据来给代码分配权限。只有在中心位置安全的情况下才应该考虑基于应用程序的位置(例如,文件共享或 Web 服务器)的证据。类似地,只有在密钥安全的情况下才应该使用具有基于一般密钥 — 用于签署所有的代码(例如,由组织的 IT 部门)— 的证据的应用程序。然而,依赖于强名称证据而不是任何基于位置的证据(例如 Web 服务器地址)通常更安全一些。
设计部分受信任的应用程序
当设计部分受信任的应用程序时,请遵循下列指导原则:

了解应用程序的部署方案。

避免引起异常的权限请求。

对部分受信任的调用程序使用 Demand/Assert 模式。

考虑对程序集使用强名称。

避免给受限区域赋予完全的信任。
了解应用程序的部署方案
在设计期间应该清楚地了解应用程序的部署方案,因为部署应用程序的位置对默认授予应用程序的权限有很大的影响。诸如显示对话框(例如,使用 SaveFileDialog)或访问系统资源的应用程序功能都可能因为应用程序的部署位置而受到限制。
特别地,授予应用程序的权限取决于它所在的区域(例如,Internet 区域、本地 Intranet 区域或受信任的区域)。用户可以对受信任的区域内的应用程序的成员身份有一定的控制,但是不应该依赖用户将应用程序放在此区域来确保正确的功能。您应该这样设计您的应用程序,使之在运行时被授予的权限不够时会正常地失败。
如果用户尝试执行某种操作,而应用程序却没有足够的权限来执行该操作,则这种尝试可能会导致失败的权限请求,这又会引发安全异常。应用程序需要处理这些异常,否则它就会出现故障。您应该确保可以很好地处理这样的故障,并且应该给用户提供足够的信息来解决问题,而同时不泄露与安全有关的不适当的或敏感的信息。
注系统可以按照使用 .NET Framework 2.0 版本的 ClickOnce 部署功能部署的应用程序的部署清单授予它们特定的权限。在部署应用程序时,授予的权限将固定,而且将应用程序的位置放在受信任的区域中不会影响授予的权限。
避免引起异常的权限请求
确定每个应用程序功能在不引起异常的情况下正确地运行所需的权限。请考虑使用如下措施:

设计一个替代解决方案来避免可能引起异常的权限请求。例如,对于基于 Intranet 的应用程序,您可以使用 OpenFileDialog 来显示一个对话框,指示用户选择文件,而不要让应用程序自动从硬盘打开并读取文件。

适当地处理异常的检查权限(特别是 SecurityException)。在您的代码中,您可以创建一个特定于您正试图访问的资源的权限类的实例,并且在访问资源之前检查必要的权限。例如,当您必须使用 OpenFileDialog 或 SaveFileDialog 显示对话框时,可以使用 FileDialogPermission 类和 SecurityManager.IsGranted 静态方法来检查权限,如下所示。
FileDialogPermission fileDialogPermission = new FileDialogPermission( FileDialogPermissionAccess.Save ); if ( !SecurityManager.IsGranted( fileDialogPermission ) ) { // Not allowed to save file. }
注IsGranted 并不保证操作将成功,因为它没有遍历堆栈来确定是否所有的上游代码都具有必需的权限。

考虑原型设计并对各种部署区域测试您的应用程序方案:

如果您的应用程序设计成能从文件共享运行,您可以将该应用程序的地址设置为网络共享(例如,\\MachineName\c$\YourAppPath\YourApp.exe)并从您的硬盘运行它来模拟这种方案。

如果您的应用程序设计成是从 Web Internet 区域运行,您可以使用您的计算机的 IP 地址(例如,\\对部分受信任的调用程序使用 Demand/Assert 模式
Demand/Assert 模式用于完全受信任的程序集中,以允许部分受信任的调用程序在调用时访问特权操作。当部分受信任的调用程序需要以安全的方式执行特权操作而又不具有必要的特权时,这种模式非常有用。通过使用 Assert,您可以担保您的代码的调用程序是值得信任的。
注只有当您完全理解它的使用可能带来的安全风险时,才能使用 Demand/Assert 模式。断言权限关闭了正常的 .NET Framework 权限检查,这种权限检查会检查堆栈中所有的调用代码。关闭这种机制可能会将严重的安全漏洞引入代码中,因此只有在您完全理解该模式的含义并用尽所有其他可能的解决方案时,才能尝试这种模式。
在这种模式中,Demand 调用发生在 Assert 调用之前。Demand 查看调用程序是否具有该权限,然后,Assert 关闭特定权限的堆栈遍历,这样公共语言运行库就不会检查调用程序是否具有适当的权限。
为了让部分受信任的调用程序成功地调用完全受信任的程序集方法,可以请求适当的权限来确保部分受信任的调用程序不会损害系统,然后断言特定的权限以执行高特权操作。
在进行特权操作之前,应该调用完全受信任的程序集中的 Assert,并随后调用 RevertAssert以确保方法调用后面的代码不会因疏忽而成功,因为 Assert 仍然在发挥作用。您应该将此代码放在私有函数中,这样,Assert 就会自动在方法返回之后从堆栈中删除(使用 RevertAssert 调用)。使此方法私有非常重要,这样攻击者就无法使用外部代码来调用该方法。
考虑下面的示例。
Private void PrivilegedOperation() { // Demand for permission. new FileIOPermission( PermissionState.Unrestricted ).Demand(); // Assert to allow caller with insufficient permissions. new FileIOPermission( PermissionState.Unrestricted ).Assert(); // Perform your privileged operation. }
在默认的情况下,完全受信任的程序集并不允许从部分受信任的应用程序或程序集中进行调用;这样的调用会引起安全异常。为了避免这些异常,可以将 AllowPartiallyTrustedCallersAttribute (APTCA) 添加到 Visual Studio .NET 生成的 AssemblyInfo.cs 文件中,如下所示。
[assembly: AllowPartiallyTrustedCallersAttribute()]
注应该对使用 APTCA 的代码进行评审,以确保它不会被任何部分受信任的恶意代码所利用。有关详细信息,请参阅 Improving Web Application Security: Threats and Countermeasures 的“Chapter 8 — Code Access Security in Practice”中的“APTCA”,地址在http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/THCMCh08.asp。
考虑使用强名称的程序集
使用强名称可以提高程序集的安全性。您应该考虑使用强名称来签署您的应用程序中的所有程序集,并且修改安全策略以信任此强名称。您可以使用 Sn.exe 工具签署带有强名称密钥对的程序集。要手动更改安全策略,可以使用 .NET Framework Configuration MMC 管理单元或 Caspol.exe,一个命令行工具(位于 %SystemRoot%\Microsoft.NET\Framework\< version>\CasPol.exe)。
使用私钥签署程序集的过程应该进行如下考虑:

将延迟签署用于开发。编译代码的编译过程可以使用延迟签署,从而使用强名称密钥对的公共部分而不是私钥。要使用延迟签署,开发人员可以将下面的属性添加到项目的 Assembly.cs 文件中。
[assembly:AssemblyKeyFile("publickey.snk")] [assembly:AssemblyDelaySign(true)]

保护生成的私钥。下面的命令行展示了强名称工具 (Sn.exe) 的使用,该工具是 .NET Framework SDK 附带的,用于生成直接针对可移动存储设备的密钥对 (Keypair.snk)。(在本例中,使用的 F 驱动器是 USB 驱动器)
sn -k f:\keypair.snk sn -p f:\keypair.snk f:\publickey.snk
公钥 (Publickey.snk) 用于开发人员进行延迟签署。该密钥对存储在具有高度受限的访问权限的安全位置。

禁用对测试的验证。要测试已经延迟签署的程序集,可以使用 Sn.exe 在测试计算机上注册它。表 5.1 列出了常用的命令行变体。
表 5.1 常用的命令行变体
命令 描述
sn -Vr assembly.dll
禁用对特定程序集的验证。
sn -Vr *,publickeytoken
禁用对具有特定公钥的所有程序集的验证。星号 (*) 通过对应于为忽略验证提供的公钥令牌的密钥来注册所有延迟签署的程序集。

通过供发布的私钥进行签署。要完成签署过程,可以使用下面的命令通过私钥进行签署。
sn -r assembly.dll f:\keypair.snk
在签署程序集以供在组织中使用之前,指定的组成员应该对其进行测试和评审。
有关延迟签署和本节中解释的过程的详细信息,请参阅 Improving Web Application Security: Threats and Countermeasures 中的下列资源:

http://msdn.microsoft.com/library/en-us/dnnetsec/html/THCMCh03.asp 上的“Chapter 3 — Threat Modeling”。

http://msdn.microsoft.com/library/en-us/dnnetsec/html/THCMCh07.asp 上的“Chapter 7 — Building Secure Assemblies”中的“Delay Signing”。

http://msdn.microsoft.com/library/en-us/dnnetsec/html/THCMCh05.asp 上的“Chapter 5 — Architecture and Design Review for Security”。

http://msdn.microsoft.com/library/en-us/dnnetsec/html/THCMCh21.asp 上的“Chapter 21 — Code Review”。
同时参阅下列资源:

http://www.dotnetthis.com/Articles/SNandSmartCards.htm 上的“Strong Name Signing Using Smart Cards in Enterprise Software Production Environment”。
避免给受限区域赋予完全的信任
作为解决部分受信任的应用程序的安全问题的一个快速替代解决方案,您可能非常想给受限区域(例如 Internet 或本地 Intranet 区域)赋予完全的信任。这样做会允许任何应用程序在没有进行代码访问安全检查的情况下就运行在本地系统上,如果应用程序来自恶意的来源,这就成为一个问题。然而,如果在设计阶段考虑部署方案,就不必开放安全性来允许应用程序运行。
设计完全受信任的应用程序
因为部分受信任的应用程序可能对系统资源具有非常少的权限,所以为了正确地运行,应用程序可能需要比默认分配给它的权限更多的权限。需要能够执行诸如启动 Office 应用程序或 Microsoft Internet Explorer、调用旧式 COM 组件、写入文件系统这样的任务的应用程序必须具有显式启用这些操作的权限才能运行。
将应用程序指定为完全受信任的应用程序,这样就可以给它分配所有可能的权限,这对我们是很有吸引力的。然而,更安全的做法是,在设计和部署应用程序时,只要求应用程序正确运行所需的最小权限。如果您确实需要将应用程序作为完全受信任的应用程序来运行,就必须考虑使用下列指导原则:

确定程序集需要访问的资源的类型,评估在程序集受到损害时可能发生的潜在威胁。

确定目标环境的信任级别,因为代码访问安全策略可能对程序集的功能进行限制。

使用只用于构成程序集公共接口的一部分的类和成员的公共访问修饰符来减少攻击面。只要有可能,就使用 private 或 protected 访问修饰符来限制对所有其他类和成员的访问。

使用 sealed 关键字来防止对没有设计为基类的类的继承,如下面的代码所示。
public sealed class NobodyDerivesFromMe {...}

只要有可能,就使用声明性类级别或方法级别属性来限制对指定的 Windows 组的成员的访问,如下面的代码所示。
[PrincipalPermission(SecurityAction.Demand,Role=@"DomainName\WindowsGroup")] public sealed class Orders() {...}

为延迟签署、测试、安全评审和保护私钥建立安全构建过程。
返回页首
小结
智能客户端应用程序在本质上是分布式的,因此,为了有效地管理它们的安全性,需要考虑服务器上的安全、客户端上的安全、以及两者之间的网络连接的安全。具体的智能客户端考虑事项包括设计安全身份验证、授权、数据验证和保护敏感数据。您还应该研究如何使用代码访问安全机制来管理代码级而不是用户级的安全性。
转到原英文页面
返回页首

 适合打印机打印的版本 通过电子邮件发送此页面
个人信息中心 |MSDN中文速递邮件 |联系我们
©2007 Microsoft Corporation. 版权所有.  保留所有权利 |商标 |隐私权声明