本指南介绍为基于 Microsoft® .NET 的单层或多层应用程序设计和编写由应用程序管理的授权的指导原则,主要讨论常见的授权任务和方案,并提供相应的信息帮助您选择最佳方法和技术。本指南适用于体系结构设计人员和开发人员。
本指南假定读者已经了解 Windows 身份验证和授权、XML Web Service 以及 .NET Remoting 等主题的基本知识。有关设计分布式 .NET 应用程序的详细信息,请参阅 MSDN® Library 中的“Designing Applications and Services”。有关分布式应用程序安全设计的详细信息,请参阅 MSDN Library 中的 Building Secure ASP.NET Applications(英文)。有关其他的常规设计准则,请参阅 Microsoft TechNet 中的 .NET Architecture Center(英文)。
单击此处下载本指南(PDF 格式)。
本指南包括以下各节:
本指南介绍如何在基于 .NET 的应用程序中实现授权,解释术语“授权”并讨论几种执行授权的机制。本指南还包括以下内容:
采用何种授权机制通常取决于验证用户身份(标识)的方法。本指南将探讨以下内容:
在典型的企业应用程序中,需要在应用程序的不同层次执行不同类型的授权。为了帮助您识别各层的授权需要以及在不同的方案中选择合适的授权策略,本指南介绍在用户界面层、业务层和数据层中通常使用的典型授权任务。图 1 显示了企业应用程序的各个层上出现的一些重要授权问题。
图 1:在企业应用程序的各层中执行授权
.NET Framework 类库提供了多种接口和类,帮助您使用基于角色的 .NET 安全设置来执行授权。本指南介绍:
定义授权框架时所做的大部分工作,都可以在多个应用程序中重复使用。本指南将对以下内容进行总结:
注意:本指南适用于使用 .NET Framework 功能进行由应用程序管理的授权。Microsoft Windows® Server 2003 系列操作系统中的授权管理器 API 和 Microsoft Management Console (MMC) 管理单元,为应用程序提供了具有完整的基于角色的访问控制框架。授权管理器 API 也称为 AzMan,它提供了一种简化的开发模型,用于管理灵活的分组和业务规则,并可用于存储授权策略。有关详细信息,请参阅 MSDN Library 中的 Authorization Interfaces(英文)和 Authorization Objects(英文)。
“授权”是对通过验证的主体(用户、计算机、网络设备或程序集)是否具有执行某项操作的权限的确认。授权提供的保护只允许指定用户执行特定操作,并防止恶意行为。
本节的内容如下:
仅有授权还不足以保证应用程序的安全,因此,本指南将简要介绍应用程序面临的几种威胁。以下是一些常见的安全威胁,这些威胁通常缩写为“STRIDE”,包括:
您可以使用以下技术来解决 STRIDE 威胁:
有关 STRIDE 的详细信息,请参阅 MSDN Library 中的 Designing for Securability(英文)。
图 2 显示的模型说明了如何降低多层应用程序中的 STRIDE 安全威胁。
图 2:多层应用程序的安全模型
图 2 描述的是一种具有多个物理层的部署,但是,许多较小的应用程序是在一个物理层上完成的,这就简化了身份验证、授权和标识流。图 2 包含了以下降低安全威胁的措施:
您可以使用多种授权机制来控制应用程序的功能,使其按预期方式运行,不被意外或蓄意误用。这些授权机制包括以下几种:
可以综合使用这些方法创建安全的应用程序,如图 3 所示。
图 3:选择授权机制
“系统授权”是为操作系统控制的对象(例如打印机、文件)设置资源权限或 ACL 的过程。系统管理员负责维护这些设置。系统授权是一种非“是”即“否”的决策模式:用户要么被授权访问资源,要么不被授权访问资源。
系统授权的示例包括:
有关系统级安全和授权的详细信息,请参阅 MSDN Library 中的 Building Secure ASP.NET Applications(英文)。
系统授权可以对各种对象施加约束,而限制代码则需要采用 .NET 代码访问安全性授权。
.NET 公共语言运库使用代码访问安全性来限制可执行的代码。代码访问安全性根据证据向应用程序代码授予权限(称为“权限设置”)。这些证据可以包括代码的来源、发布者或其他证据,如程序集的严格名称。
权限设置使您能够控制应用程序可以执行的操作,如删除文件或访问 Internet。例如,您可以限制应用程序只使用隔离的存储单元或控制打印访问。
不管用户的身份如何,代码访问安全性只考虑证据,即使具有管理特权的用户使用应用程序,代码访问安全性权限仍旧保持不变。例如,如果代码来自 Internet,不管用户是谁,对其应用的限制(如删除文件的能力)保持不变。
代码访问安全性的应用示例包括:
注意:请使用 Caspol.exe 或 Microsoft .NET Framework 配置管理控制台配置代码访问安全性。
有关代码访问安全性的详细信息,请参阅 MSDN Library 中《.NET Framework Developer's Guide》中的 clearcase/" target="_blank" >ccesssecurity.asp">Code Access Security(英文)一文。
代码访问安全性通过检查代码权限来保证系统的安全性,但可能还需要使用应用程序授权来检查用户的权限,这取决于应用程序。
大多数应用程序会根据用户与系统的交互活动实现不同的功能或安全权限。设计“应用程序授权”指根据程序中的用户角色,实施业务规则或限制用户对应用程序资源的访问。
应用程序授权的主要目的是保护功能和其他无形内容,如业务逻辑。因此,很难使用当前的系统级技术实现应用程序授权,因为这些技术需要使用有关物理资源的设置,如 ACL。例如,您想确保对员工费用申请的批准操作的安全,却没有要保护的物理资源,因此,当您设计应用程序授权时,应该着眼于高级别的操作,而不是各种资源。
当系统授权机制分类过细或不考虑应用程序的数据与状态时,应用程序授权提供了另一种系统授权方法。例如,如果 XML Web Service 的系统级安全标准仍处于开发阶段,还在不断地发展丰富,那么您可以不必等到标准形成之后再向 XML Web Service 添加安全设置或创建安全的 XML Web Service。对于目前已创建的 XML Web Service,您可以实现应用程序授权,使用安全套接字层 (SSL) 或其他组合来保护对服务的调用。
应用程序授权的示例包括:
在本指南的后面部分中,您将学习如何设计这些应用程序授权以及如何编写相关代码。
为了防止操作被连续不断地错误执行而导致最终失败,您应该始终做到尽快地对用户的每个请求进行授权。每个授权点称为“看门人”。这种看门机制的示例包括 ASP.NET 入口中的文件和 URL 授权。在标识流向各个层次传递的过程中,可能会有若干个“看门人”。在门口进行检查可以减少在系统深层(通过入口点或门以后)必需的授权检查次数。
在系统深层执行授权检查的对象需要较少的授权失败补偿逻辑。单个组件不负责处理授权失败,不会抛出异常来通知失败的调用者。
.NET Framework 提供了基于角色的应用程序授权能力。“角色”指共享同一安全特权的一类或一组用户。
使用角色代替特定的用户标识具有以下优势:
角色可以代表用户在组织中的地位,例如:
使用这种方法的一个好处是信息通常可以从存储库(例如 Active Directory)中检索出来。通常情况下,这些角色在对实际业务需求进行建模时十分有用。
您还可以使用角色来指出用户执行的工作属于哪种操作类型。这样的角色可以将应用程序的功能链接到各个用户,例如:
第二种方法更灵活一些,因为您可以围绕应用程序的功能来设计角色,而不用过多考虑组织的结构,但维护起来可能比较困难,因为缺乏保存角色的结构。大多数情况下,需要在应用程序中综合使用这些方法。
有时您必须以用户是谁作为授权的基础,而不会过分关注用户在应用程序中扮演的角色。例如,您可能要实现只允许部门经理批准员工的费用申请,而要达到这种授权级别,可以将当前用户与提出申请的员工的经理进行比较。
.NET Framework 在 System.Security.Principal 命名空间中提供了基于角色的安全设置实现,您可以用此实现来授权应用程序。要在 .NET Framework 中使用应用程序授权,请创建 IIdentity 和 IPrincipal 对象来表示用户。IIdentity 封装的是一个通过验证的用户,IPrincipal 则是用户标识和用户角色的组合。
图 4 显示了 IIdentity 和 IPrincipal 对象之间的关系。
图 4:IIdentity 和 IPrincipal 对象之间的关系
请注意图 4 中的以下几点:
.NET Framework 提供了四种实现 IIdentity 接口的类:
每种类都允许您使用不同种类的用户标识。要访问使用 Windows 身份验证的应用程序的当前 WindowsIdentity 对象,可以使用 WindowsIdentity 类的静态 GetCurrent 方法,如以下代码所示:
您还可以通过在自己定义的类中实现 IIdentity 接口来创建自定义标识类。有关创建自定义标识的详细信息,请参阅本指南后面的扩展默认实现。有关如何使用默认 IIdentity 实现的详细信息,请参阅本指南后面的设计用于授权的身份验证。
.NET Framework 提供了链接用户角色和标识的 IPrincipal 接口。所有执行应用程序授权的托管代码都应该使用实现 IPrincipal 的类的对象。例如,WindowsPrincipal 和 GenericPrincipal 类提供了内置的 IPrincipal 实现。另外,您也可以根据 IPrincipal 创建自己的自定义主体类。
为了提高编码效率,您可以使用本指南的设计用于授权的身份验证一节中介绍的技术,通过使用 Thread 对象的静态 CurrentPrincipal 属性可以将 IPrincipal 对象链接到线程,这样当前线程就可以轻松地访问 IPrincipal 对象了,如以下代码所示:
然后,您可以测试用户是否属于某一特定的角色,从而执行授权检查。为此,可以使用 IPrincipal 接口的 IsInRole 方法,如以下代码所示:
ASP.NET 应用程序处理 IPrincipal 对象的方法与其他基于 .NET 的应用程序的处理方法有所不同。ASP.NET 通过无状态的 HTTP 协议创建会话外观。作为会话的一部分,执行用户请求的所有代码可以通过 HttpContext 对象的 User 属性,使用代表用户的 IPrincipal 对象。Global.asax 文件发生 OnAuthenticate 事件之后,公共语言运库使用 HttpContext.User 值自动更新 Thread.CurrentPrincipal。
ASP.NET 应用程序通常使用 User 属性执行授权检查,如以下代码所示:
注意:手动更改 HttpContext.User 将自动更新在同一 HTTP 上下文环境中执行的所有线程的 Thread.CurrentPrincipal。但是,改变 Thread.CurrentPrincipal 不会影响 HttpContext.User 属性,它只影响为请求中其余内容选择的线程。
有关创建自己的 IPrincipal 类型的详细信息,请参阅本指南后面的扩展默认实现。有关如何使用默认 IPrincipal 实现的详细信息,请参阅本指南后面的设计用于授权的身份验证。
使用 IIdentity 对象是一种敏感操作,因为在该操作中可以使用与用户相关的信息。允许应用程序更改当前的 IPrincipal 对象也应该受到保护,因为应用程序授权能力是以当前的主体为基础的。框架要求这些操作具有代码访问安全性权限,从而提供了保护。使用 Caspol.exe 或 .NET Framework 配置工具为需要管理这些对象的应用程序授予 SecurityPermissionAttribute.ControlPrincipal 权限。
默认情况下,所有本地安装的应用程序均具有该权限,因为它们是在“完全信任”的权限设置下运行的。
执行以下方法需要 ControlPrincipal 权限:
有关使用 CASPOL 设置安全权限的详细信息,请参阅 MSDN Library 中的 Configuring Security Policy Using the Code Access Security Policy Tool (Caspol.exe)(英文)。
公共语言运库在 Windows 安全结构之上有一个单独的安全结构。Windows 线程具有 Windows 授权用户的令牌,而运行时线程具有代表该用户的 IPrincipal 对象。
因此,在开发代码时,必须至少考虑两种代表用户的安全上下文。例如,设想一个使用窗体身份验证的 ASP.NET 应用程序:默认情况下,ASP.NET 进程在名为“ASPNET”的 Windows 服务帐户(专为应用程序创建的用户帐号)下运行。假设有一位名为 Bill 的用户登录到 Web 站点。Thread.CurrentPrincipal 属性代表窗体身份验证的用户 Bill。公共语言运库看到以 Bill 运行的线程,于是所有托管代码都将 Bill 看作主体。如果托管代码要求访问系统资源(如文件),则不管 Thread.CurrentPrincipal 的值如何,非托管代码都可以看到 ASPNET Windows 服务帐户。图 5 说明了公共语言运库和操作系统线程之间的授权关系。
图 5:公共语言运库和操作系统线程之间的授权关系
模拟允许您改变操作系统线程在与 Windows 的交互过程中执行的用户帐户。可以通过调用 WindowsIdentity.Impersonate 方法模拟 Windows 标识。这种模拟形式允许您作为特定用户访问本地资源。当调用 Impersonate 方法时,您就以 WindowsIdentity 所代表的用户登录。您必须有权访问服务器中的用户凭据且有权调用 LogonUser API。但是,手动创建 WindowsIdentity 的过程比较复杂,因此,如果不是十分必要,最好不要执行模拟。
当应用程序代码模拟其他用户之后,WindowsImpersonatationContext.Undo 方法将用户标识还原到原始标识。这些函数一般必须成对调用,如以下代码所示:
有关模拟 Windows 帐户的详细信息,请参阅 MSDN Library 中的 Impersonating and Reverting(英文)。
注意:在 Windows 2000 中,调用 LogonUser API 的进程必须具备 SE_TCB_NAME 特权。有关验证用户凭据的详细信息,请参阅文章 Q180548 HOWTO: Validate User Credentials on Microsoft Operating Systems(英文),位于 Microsoft Knowledge Base(英文)中。
授权取决于身份验证,也就是说,必须对用户或过程进行身份验证,然后才能对其授权,以便查看或使用受保护的资源。本节将分析两种身份验证机制并阐述这两种机制对授权的影响:
选择哪种身份验证机制通常受与授权无关的因素的影响,例如 Windows 用户帐户的可用性以及一些常见的环境问题,包括客户端的浏览器类型。但是,您选择的身份验证机制确实会影响您执行授权的方式。
所有应用程序在称为“Windows 标识”的 Windows 用户帐户下执行。在 Windows 身份验证过程中,您将使用这种 Windows 用户帐户或 Windows 用户所属的角色执行您的授权检查。Windows 根据由以下技术提供的凭据来选择用户帐户:
使用 Windows 身份验证进行授权的整个过程是这样的:
注意:先将 IPrincipal 对象链接到线程,然后再从 Thread.CurrentPrincipal 属性检索主体或标识信息。这样就可以在内存中维护一个角色集,从而降低查找这类信息的频率。
您可以使用 WindowsPrincipal 对象查看用户是否属于特定的 Windows 用户组(例如 “<domain>\Users”)。WindowsPrincipal 对象具有 Identity 属性,该属性返回一个 WindowsIdentity 对象,描述当前用户的标识。
您可以配置存储在 ASP.NET 中的 .NET 应用程序,使其通过启用 Web.config 中的 Windows 身份验证,从 Thread.CurrentPrincipal 属性自动访问 WindowsPrincipal。您可以在 ASP.NET Web 应用程序、XML Web Service 以及存储在 ASP.NET 中的 .NET Remoting 对象中使用该技术。
其他 .NET 应用程序,如 Windows 服务、控制台和基于 Windows 窗体的应用程序,需要您使用以下方法之一建立 Thread.CurrentPrincipal:
注意:使用 IIdentity 和 IPrincipal 对象的代码必须具备 ControlPrincipal 权限,请使用代码访问安全性为应用程序授予该权限。有关详细信息,请参阅本指南前面的在基于 .NET 的应用程序中使用基于角色的安全设置。
AppDomain 对象的 SetPrincipalPolicy 方法可以将特定类型的 IPrincipal 对象链接到应用程序域。请使用 PrincipalPolicy 枚举类型的 WindowsPrincipal 值,将当前的 WindowsPrincipal 对象链接到应用程序线程,如以下代码所示:
然后就可以授权检查主体了。在开始执行应用程序时,调用 SetPrincipalPolicy 以确保在执行任何授权检查之前 IPrincipal 对象已链接到线程。
请勿在 ASP.NET 应用程序中使用 SetPrincipalPolicy,因为在 Global.asax 中定义的 Application_AuthenticateRequest 事件处理程序过程中,ASP.NET 身份验证将为您管理此值。
注意:只有在应用程序域中不存在默认的主体时,SetPrincipalPolicy 才有效。
要设置 CurrentPrincipal 属性,首先必须创建一个新的 WindowsPrincipal 对象,方法是将 WindowsIdentity 对象传递给 WindowsPrincipal 构造函数。然后将新的 WindowsPrincipal 对象链接到 Thread.CurrentPrincipal 属性,如以下代码所示:
然后就可以授权检查 IPrincipal 对象了。
有关如何使用 WindowsPrincipal 执行授权检查的详细信息,请参阅本指南后面的执行授权检查。
要根据应用程序定义的角色(如 CanApproveClaims)创建授权检查,必须手动创建通用的或自定义的 IPrincipal 对象。您无需调用 SetPrincipalPolicy 方法,因为它的默认设置是 NoPrincipal,正是指定您自己的主体的正确设置。
大多数情况下,根据已通过身份验证的用户名来创建 GenericIdentity 对象。(通过使用 IIdentity 接口的 Name 属性,可以得到该用户名。)这样,在向其他组件提供 IIdentity 对象时,就可以避免所有涉及用户登录令牌(可以使用 WindowsIdentity 对象访问)的安全问题。然后,可以创建 GenericPrincipal,将 IIdentity 对象链接到自己的应用程序定义的角色列表中。
以下代码显示了使用 Windows 身份验证时,如何创建 GenericIdentity 和 GenericPrincipal 对象:
大多数情况下,应该从数据存储库中检索应用程序定义的角色,而不是为角色编写代码,从而在指定角色成员时获得更大的灵活性。有关使用该方法的示例,请参阅附录中的sqlserver" target=_self>如何使用 SQL Server 构建 GenericPrincipal。
有关如何使用 GenericPrincipal 执行授权检查的详细信息,请参阅本指南后面的执行授权检查。
您可能不想或不能使用 Windows 身份验证执行应用程序授权。出现这种情况的原因可能是用户没有 Windows 用户帐户,或者是因为技术限制而无法使用 Windows 身份验证来验证用户的凭据。
非 Windows 身份验证是使用非 Windows 技术验证用户凭据的过程。这种身份验证包括 .NET Framework 为您提供的验证(如 ASP.NET 窗体身份验证)和您自己创建的验证方法。
使用非 Windows 身份验证进行授权的整个过程如下:
常见的非 Windows 身份验证类型有:
窗体身份验证提供程序根据 FormsIdentity 对象创建 GenericPrincipal,但是没有角色成员关系。
有关如何使用 ASP.NET 窗体身份验证的详细信息,请参阅文章 Q301240 HOW TO: Implement Forms-Based Authentication in Your ASP.NET Application by Using C# .NET(英文),位于 Microsoft Knowledge Base(英文)中。
窗体身份验证几乎总是使用应用程序特定的凭据。有关如何使用窗体身份验证方式来验证获得的 Windows 凭据的示例,请参阅文章 Q316748 HOW TO: Authenticate Against the Active Directory by Using Forms Authentication and Visual C# .NET(英文),位于 Microsoft Knowledge Base(英文)中。
Passport 身份验证提供程序根据 PassportIdentity 对象创建一个 GenericPrincipal,但是没有角色成员关系。
有关 Passport 身份验证的详细信息,请参阅 MSDN Library 中的 The Passport Authentication Provider(英文)。
HTTP 传输提供用于构建通用或自定义的 IIdentity 对象和 IPrincipal 对象的信息(如 cookie)。
有关 ASP.NET 的身份验证技术的详细信息,请参阅 MSDN Library 中的 Authentication in ASP.NET: .NET Security Guidance(英文)。
以上列举的所有非 Windows 身份验证类型均涉及到 GenericPrincipal 对象或自定义 IPrincipal 对象。关于如何将 GenericPrincipal 链接到应用程序定义的角色的示例,请参阅附录中的sqlserver" target=_self>如何使用 SQL Server 构建 GenericPrincipal。
身份验证创建用于授权的 IIdentity 和 IPrincipal 对象,并确定如何通过编程方式将标识信息传递给其他计算机上远程部署的应用程序逻辑。已通过验证的标识的传递过程称为“标识流”。实现标识流的方法有两种:
如果所有需要标识信息的代码是在同一上下文中执行的,公共语言运库将自动提供标识流。调用者和被调用者共享相同的应用程序域时,它们处于同一上下文。如果客户端代码(称为“调用者”)和被调用的组件(称为“被调用者”)在同一上下文中运行,.NET 将对两者自动使用同一个 Thread.CurrentPrincipal 对象。有关被调用者和调用者处在不同线程的情况,请参阅本章后面的使用多线程执行授权。
在一个应用程序域中执行的代码示例包括:
由于技术限制,需要在不能执行身份验证的远程层进行授权检查时,可以实现手动标识流。例如,通过 TCP 通道使用 .NET Remoting 调用远程组件。有关在 .NET Remoting 应用程序中使用通道的详细信息,请参阅 MSDN Library 中的 Microsoft .NET Remoting: A Technical Overview(英文)。
如果您正在实现自己的标识流,请使用与调用代码不同的方式传递数据(称为“分离”)。如果您的组件接口涉及调用功能,并且使用参数传递数据,请使用其他方式发送标识信息。
例如,如果您正在使用 SOAP 调用 XML Web Service,则请勿将标识信息作为消息正文的一部分发送,而应将其放入自定义的 SOAP 标头中。这时对安全性的要求不象处理代码设计问题时那样严格。这种分隔方式可以增加代码接口的扩展性、可复用性和聚合性。这样做还使您可以随着技术标准的发展更改发送标识信息的方法,而无需改变接口约定。Web Services Security (WS-Security)(英文)规范说明了这一点。
请注意,标识流不同于传递凭据,后者是在服务器上进行的高效重验证过程。另外,您还可以在消息本身中传递已加密的凭据,但是,加密需要使用共享密钥或公钥系统,所以用这种方式传递凭证没有什么优势。
以下是一些实现标识流的最佳方案:
在信任环境中,被调用者将身份验证的责任交给了调用者。这时认为我们从调用者传递到被调用者的所有信息都是安全可靠的,例如 Web 层是调用者,内部应用程序中的组件是被调用者。
注意:如果非法用户可以调用其他(和潜在虚假)标识传递的代码,这种解决方案将是不安全的。为了防止这类欺诈攻击,请使用系统层提供的技术来保护安全套接字层 (SSL) 或 IP 安全性 (IPSec) 通信通道,并使用代码访问安全性来确保只有相应的代码才能调用您的函数。
在信任环境中,主要采用两种方法在应用程序层之间传递标识信息:
当您向数据库传递标识信息进行审核时,也可以使用这种方法。有关详细信息,请参阅本指南后面的在数据层执行授权。
该方法较为灵活,因为不需要强制调用者传递特定类型的对象或额外信息,例如应用程序角色。
通过传递自定义的 IPrincipal 对象,您可以传递角色、自定义的 IPrincipal 对象包含的任意附加信息(如用户配置文件)以及标识。
但是,不能传递封装了某种信息的 IPrincipal 对象。例如,不能传递 WindowsPrincipal 或其他所有以 WindowsIdentity 对象为基础的主体,因为这些标识信息中包含的令牌在其原始环境之外是没有意义的。
有关序列化 IPrincipal 对象的详细信息,请参阅附录中的如何在 .NET Remoting 组件中启用授权。
注意:只有在使用安全通信通道的信任环境中(如使用防火墙保护的某个公司 LAN),才可以传递标识信息,以免网络数据在网络上被截获或修改而遭到欺诈。
非信任环境是指调用者不信任被调用者的情形。在非信任环境中,被调用者必须验证调用者,然后才执行授权,例如 Web 应用程序验证浏览器的所有请求。
在非信任环境中的应用程序层之间传递标识信息有两种常见的方法:
传递签名数据包括对消息进行数字签名。数字签名是根据发件人的私钥和消息内容计算出的。为了实现验证,被调用者通过密钥管理系统获得发件人的公钥,并使用该密钥确定发件人在签署消息时是否使用了相应的私钥。
.NET Framework 提供的 SignedXml 类可以作为 XMLDSIG 标准的实现。该标准不涉及密钥管理或密钥信任等问题。收件人不必假设对该过程所使用的密钥的信任程度,这部分由应用程序代码负责。有关 XMLDSIG 标准的详细信息,请参阅 W3C 网站上的 XML-Signature Syntax and Processing(英文)。
有关如何使用 SignedXml 类的代码示例,请参阅 CAPICOM SDK。CAPICOM 是一种 Microsoft ActiveX® 控件,提供到 Microsoft CryptoAPI 的 COM 接口。该代码示例说明了使用证书授权、签名和验证 XMLDSIG 签名的全过程。您可以从 samples\c_sharp\xmldsig 目录中的 Platform SDK Redistributable: CAPICOM 2.0 找到该代码示例。有关 CAPICOM 的详细信息,请参阅 MSDN Library 中的 CAPICOM Reference(英文)。
传递令牌要求使用共享的身份验证机制或确定令牌有效性的方法。传递令牌可能包括在组件之间传递 Kerberos 票据。另一个例子是电子商务网站中 Internet Information 服务 (IIS) 应用程序根据身份验证创建的会话 cookie。每次浏览器发出调用命令时,cookie 都会传递回 Web 服务器,这是典型的 ISAPI 解决方案。
上面您已经了解了如何使用各种身份验证机制实现授权,以及如何在各层之间传递授权数据,下面将为您介绍如何在企业应用程序的各层进行授权。
目前大多数的应用程序都得益于多层设计,因为多个层次可以提供伸缩性、灵活性并且可以改善性能。尽管应用程序各层的授权目的相同(控制用户访问系统功能和数据),但是各层授权的设计方法和实现方法并不相同。企业应用程序中的三层是:
本节介绍在用户界面层执行授权的方法。本指南的后面将介绍以下主题:在业务层执行授权和在数据层执行授权。
在用户界面层执行应用程序授权,有助于确保只有得到授权的用户才可以查看和修改数据,或者执行与特定岗位相关的业务功能(例如与工资有关的操作)。对于还需要在系统的其他层进行授权的过程来说,在用户界面层授权用户,是限制对这些过程的访问的第一道关卡。
要实现以下目的时,请在用户界面层创建访问检查:
在用户界面层创建授权代码时,请使用以下最佳方案:
注意:以下主题的所有代码示例均以 Web 窗体为例。可以通过将 User.IsInRole 更改为 Thread.CurrentPrincipal.IsInRole,使代码适用于 Windows 窗体。有关 IsInRole 方法和执行授权检查的详细信息,请参阅本指南后面的执行授权检查。
在 Windows 窗体和 ASP.NET 应用程序中,都有可能需要根据授权改变控件在用户界面中的显示方式,包括改变某些控件的可见性或者禁用它们。
例如,在雇员费用申请的应用程序中,CanApproveClaims 角色的成员需要在窗体中使用一个按钮批准申请,而所有不属于该角色的用户都看不到这个 Approve 按钮。以下代码说明实现该目的的方法:
如果用户界面比较复杂或者有很多自定义的角色,那么在创建授权代码时,以上这种过于简单的方法会导致“if-then-else”语句链过长。
您可以使用以下技术来降低这种根据授权检查显示控件的复杂性:
以下代码显示了如何对 Web 窗体应用这种方法:
例如,如果您创建了一个使用 DataGrid 的 Web 窗体,则可以使用一个复选框模板列,允许已授权的用户同时批准多项申请。
以下代码说明了如何使用该技术在 DataGrid 中显示 Approve 列。
将加载窗体之后改变控件状态的授权代码放在这些控制器函数中,而不要放在事件处理程序中。
有时(比如典型向导的用户界面)会根据用户的角色成员关系,要求用户完成多个窗体(或页面)以执行业务流程。这种方法可以改变页面的顺序或“流”。
在上面的申请示例中,GeneralManager 成员可以查看并批准所有部门的员工申请,而 StoreManager 成员只能查看自己部门的员工申请。为了使 GeneralManager 成员可以选择部门,需要另外创建一个步骤来检索该信息,然后再显示员工选择窗体。
以下代码说明了创建这种页面流的方法。这段代码使用 ASP.NET Server.Transfer 方法向不同的页面传递控制。对 Server.Transfer 的调用包含在名为 DisplayNextPage 的私有方法中:
业务过程可以是很简单的业务逻辑,也可以是大型的、运行时间很长的、包含许多信任和非信任伙伴的工作流程。业务层代码必须保证所有的客户请求都能满足组织规定的授权规则和逻辑规则。
您有如下需求时,请在业务层中创建访问检查:
在业务层创建授权代码时,请使用以下最佳方案:
但要使解决方案更加安全,需要在每个可以公开访问的方法中执行授权,因为对这些方法的调用可能未通过授权检查。
COM+ 最有名的功能是可以为应用程序提供企业功能,如事务组件、异步激活和对象合并等。如果您要在 .NET 企业应用程序中使用这些功能,应该使用基于角色的 COM+ 安全设置,而不要使用基于角色的 .NET 安全设置。基于角色的 COM+ 安全系统和基于角色的 .NET 框架安全设置不能互相操作。因此,在设计初期,您就要确定在 COM+ 中驻留的组件和 .NET 类型的组件,否则以后将很难更改授权代码。
有关如何确定基于角色的 .NET 安全系统和基于角色的 COM+ 安全系统的示例,请参阅 MSDN 杂志中的 Unify the Role-Based Security Models for Enterprise and Application Domains with .NET(英文)。
除了基于角色的安全设置以外,COM+ 还内置有传递 Windows 用户上下文和前一调用者信息的能力。
注意:在组件中混合使用基于角色的 COM+ 和 .NET 安全设置是不安全的。因为在执行托管 COM+ 组件时,Windows 会在托管代码和非托管代码之间切换,从而导致重要信息不可靠。
有关基于角色的 COM+ 安全设置代码示例,请参阅附录中的如何使用 System.EnterpriseServices 基于角色的 COM+ 安全设置。有关在 .NET 应用程序中使用 COM+ 的详细信息,请参阅 MSDN Library 中的 Understanding Enterprise Services (COM+) in .NET(英文)。
包含业务逻辑的组件通常都需要执行授权检查。您可以使用 .NET Remoting 或封装业务组件的服务接口(如 XML Web Service)从用户界面层的代码中直接调用这些组件。
您创建的授权逻辑经常会与组件业务逻辑混合,并成为组件业务逻辑的一部分。有关如何分离业务逻辑和授权逻辑的详细信息,请参阅本指南后面的分离业务逻辑和授权逻辑。有关如何创建授权检查的详细信息,请参阅本指南后面的使用基于角色的 .NET 安全设置创建授权代码。
实体表示数据,也可能包括一些行为方式。可以使用的实体有许多,如 DataSet、XML 字符串、XML 文档对象模型 (XML DOM),以及应用程序中自定义的实体类,具体使用哪一类取决于应用程序的物理和逻辑设计限制。应用程序既生成实体又使用实体。
实体经常在应用程序层之间移动,包括从数据源检索数据、发送到用户界面再返回到数据源进行更新的整个过程。
您可以创建类来代表执行授权检查的实体。例如,只有属于某一角色的用户才能创建和初始化实体对象。
但是,大多数情况下,不应在实体中执行应用程序授权,因为:
服务接口是将内部业务逻辑呈现给外部世界的窗口。服务接口向业务流程提供了一个入口点,使您可以灵活地改变内部流程的方法签名,而不影响外部调用者。
非信任方或要求身份验证和授权的一方访问内部业务流程组件之前,服务接口为其提供入口。
在费用申请的示例中,服务接口使用 XML Web Service 允许非信任客户访问申请处理组件,使用 .NET Remoting 或分布式 COM (DCOM) 允许信任客户访问申请处理组件。服务接口也可以是自定义的消息队列 (MSMQ) 侦听器。
有关说明 XML Web Service 中授权的示例代码,请参阅附录中的如何在 XML Web Service 中执行授权。
服务代理是业务逻辑和外部服务之间的一座桥梁。XML Web Service 和 MSMQ 消息队列就是两个外部服务代理的例子。
可以通过授权从业务层内部调用服务,使用服务代理控制对外部服务的访问。服务代理作为外部服务的代理,在代码调用外部服务时,使您可以执行验证、授权、数据格式化和其他任务。使用服务代理还允许您改变被调用的外部服务及其签名,而不影响您的业务逻辑。可以在授权服务代理中编写代码,防止非授权的用户调用外部服务。
注意:这种授权与在外部服务中进行的授权不同。如果您保持使用外部服务,必须在被调用者内执行授权检查以确保系统的安全。
在下面的服务代理代码中,如果用户不属于 CanApproveClaims 角色,他/她将不能向 ApproveClaim XML Web Service 方法传递 claimXML 消息。
有关处理授权代码异常情况的详细信息,请参阅本指南中的处理授权错误。
如果外部服务授权时要求使用特殊的用户标识,请在服务代理中创建代码,以传递可接受的 IIdentity 对象,从而将标识流传递到组件之外。有关管理标识流的详细信息,请参阅本指南前面的设计用于授权的标识流。
数据层是最后一道防线,用于抵御来自用户界面、业务组件以及试图直接访问数据的攻击者的非法请求。从授权的角度看,数据层确保只有经过批准的用户才可以访问和修改数据,而不管其访问方式如何。
需要执行以下操作时,请在数据层中创建访问检查:
要在数据层执行授权,可以使用多种技术和对象,这取决于您的安全要求。可以在以下数据层组件中执行授权:
有关使用数据访问逻辑组件和实体的详细信息,请参阅 MSDN Library 中的 Designing Data Tier Components and Passing Data Through Tiers(英文)。
以下这些问题能帮助您选择数据层的授权策略:
如果创建执行相似操作的多个存储过程会增加维护的难度,则可以向存储过程传递一个值以减少存储过程的数量。
在数据层创建授权代码时,请使用以下最佳方案:
有关连接管理的详细信息,请参阅 MSDN Library 的 .NET Data Access Architecture Guide(英文)中的“Managing Database Connections”。
拒绝对数据表的直接访问可以使用应用程序(如查询分析器)防止连接到数据库的用户,而不会强制对数据的业务逻辑进行更新或查询。只有允许的用户/服务帐户可以访问存储过程和视图。
数据访问逻辑组件是在访问数据库之前最后一个提供功能的组件。因此,您可以利用它们来确保只有获得授权的用户才可以访问或修改数据。
出现以下情形时,请在数据访问逻辑组件中执行授权:
在数据访问逻辑组件中使用基于角色的安全设置的例子包括:在数据传递到其他应用程序层之前对数据进行过滤、控制代码执行的存储过程。
有关如何执行授权检查的详细信息,请参阅本指南后面的执行授权检查。
为了保证只向其他应用程序层返回适当的信息,应该在数据访问逻辑组件代码中有选择地删除敏感数据。
下面的代码示例在用户不属于 Manager 角色时,删除数据的 ConfidentialNotes 列。
从数据库中检索不需要的列是对资源的一种浪费,尤其是不需要的列很多时。相反,使用存储过程只选择必要的数据,可以改善数据库引擎的性能。
您可以创建仅为一个角色返回正确数据的多个存储过程。然后,数据访问逻辑组件代码执行授权检查,根据用户角色确定要使用哪些存储过程。
例如,可以为 Manager 创建一个存储过程版本,为 Employee 创建另一个不同的版本,两者只是检索的数据不同。数据访问逻辑组件代码将检查用户的角色成员关系,并调用正确的存储过程。
以下代码显示了数据访问逻辑组件方法如何根据用户的角色成员关系选择要调用的存储过程。这些代码使用了一个辅助类,以避免显示过多的 ADO.NET 代码。Microsoft Application Blocks for .NET(英文)提供了 SqlHelper 类:
在存储过程中执行授权的方法主要有两种:
以数据库安全功能作为安全解决方案的基础。然后在数据库安全功能之上使用存储过程设计应用程序授权,如本指南前面所述。
使用数据库安全功能执行授权:
在数据库中执行授权之前,必须选择应用程序连接到数据库的方式。
设计系统时,必须考虑使用 SQL Server 角色简化对 SQL Server 的维护。SQL Server 提供了多种角色,包括:
有关 SQL Server 安全性、授权和授权角色的详细信息,请参阅 MSDN Library 中的 Building Secure ASP.NET Applications(英文)。
考虑将应用程序角色链接到 SQL Server 角色,使应用程序和 SQL Server 的管理一致。将直接映射到 SQL 角色的服务帐户连接到数据库,以实现这种链接。下面一节介绍了这种技术。
为了控制对表、列、视图、存储过程和其他数据对象的访问,SQL Server 必许能够识别需要授权的用户,这种识别可以通过以下连接方法实现:
这种方法提供的连接最安全,因为它不通过线路传递凭据。
无论以何种方式连接到数据库,都要先创建几个代表应用程序角色的服务帐户,然后根据角色要求,为各个服务帐户授予权限。
例如,创建 USR_MANAGER 和 USR_APPROVER 等服务帐户以实现最少特权的原则,即只允许访问一部分应用程序功能。
这种方法会带来某些不便,因为当您使用 Windows 身份验证连接到数据库时,需要从数据访问逻辑组件模拟服务帐户。
有关管理数据库连接的详细信息,请参阅 MSDN Library 中的 .NET Data Access Architecture Guide(英文)。
使用 T-SQL 语句或 SQL Server 企业管理器实用程序,在列级别应用 GRANT、DENY 和 REVOKE 权限,从而实现对列的访问控制。请尽量少用这种授权技术,因为如果数据库很大,要维护的列权限列表会很长,在实际操作中非常耗时。如果您要使用这种技术,请在连接到数据库时使用服务帐户,以使维护工作易于管理。
SQL Server 没有隐含地让您控制对各行的访问。但是,您可以向数据表添加安全列,创建控制行的功能。安全列可以保存单独的用户名或访问数据需要的最低角色(假设角色的组织形式是分层的)。安全列允许您限制对各行的访问,但是如果用户可以直接访问数据表,该技术将没有效果。
可以结合使用视图或存储过程中数据表的安全列功能和 SUSER_SNAME 函数,控制对各行的访问。SUSER_SNAME 函数将检索连接的用户名(很可能是服务帐户名)。
以下代码显示如何创建一个视图,该视图只返回当前用户的行。
注意:该方法要求您在连接数据库时使用单独的用户标识,以便 SUSER_SNAME 函数能够检索到正确的信息