• 软件测试技术
  • 软件测试博客
  • 软件测试视频
  • 开源软件测试技术
  • 软件测试论坛
  • 软件测试沙龙
  • 软件测试资料下载
  • 软件测试杂志
  • 软件测试人才招聘
    暂时没有公告

字号: | 推荐给好友 上一篇 | 下一篇

设计由应用程序管理的授权

发布: 2007-6-11 12:53 | 作者: 网络转载 | 来源: 网络 | 查看: 111次 | 进入软件测试论坛讨论

领测软件测试网

摘要

本指南介绍为基于 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 的应用程序中实现授权,解释术语“授权”并讨论几种执行授权的机制。本指南还包括以下内容:

  • 标识和主体等重要概念。
  • 如何使用基于角色的安全设置来授权具有相同安全特权的一类用户。
  • 基于角色的 .NET 和 COM+ 安全设置之间的主要区别。

采用何种授权机制通常取决于验证用户身份(标识)的方法。本指南将探讨以下内容:

  • Windows 身份验证和非 Windows 身份验证之间的区别。
  • 这些身份验证机制如何影响授权。
  • 如何向远程应用程序层传递用于授权的标识信息。

在典型的企业应用程序中,需要在应用程序的不同层次执行不同类型的授权。为了帮助您识别各层的授权需要以及在不同的方案中选择合适的授权策略,本指南介绍在用户界面层、业务层和数据层中通常使用的典型授权任务。图 1 显示了企业应用程序的各个层上出现的一些重要授权问题。

图 1:在企业应用程序的各层中执行授权

.NET Framework 类库提供了多种接口和类,帮助您使用基于角色的 .NET 安全设置来执行授权。本指南介绍:

  • 几种检查用户是否属于某个特定角色的技术。
  • 如何处理授权错误。
  • 在多线程的 .NET 应用程序中出现的特殊授权问题。

定义授权框架时所做的大部分工作,都可以在多个应用程序中重复使用。本指南将对以下内容进行总结:

  • 如何定义可重复使用的授权框架。
  • 使框架的安全性和性能达到最佳的原则。
注意:本指南适用于使用 .NET Framework 功能进行由应用程序管理的授权。Microsoft Windows® Server 2003 系列操作系统中的授权管理器 API 和 Microsoft Management Console (MMC) 管理单元,为应用程序提供了具有完整的基于角色的访问控制框架。授权管理器 API 也称为 AzMan,它提供了一种简化的开发模型,用于管理灵活的分组和业务规则,并可用于存储授权策略。有关详细信息,请参阅 MSDN Library 中的 Authorization Interfaces(英文)和 Authorization Objects(英文)。

了解授权

“授权”是对通过验证的主体(用户、计算机、网络设备或程序集)是否具有执行某项操作的权限的确认。授权提供的保护只允许指定用户执行特定操作,并防止恶意行为。

本节的内容如下:

  • 授权提供的保护。
  • 基本授权。
  • .NET Framework 的授权功能。

降低安全威胁

仅有授权还不足以保证应用程序的安全,因此,本指南将简要介绍应用程序面临的几种威胁。以下是一些常见的安全威胁,这些威胁通常缩写为“STRIDE”,包括:

  • 标识欺骗 - 未授权的用户冒充应用程序的合法用户
  • 篡改数据 - 攻击者非法更改或毁坏数据
  • 可否认性 - 用户否认执行了操作的能力
  • 信息泄露 - 敏感数据被泄露给本应无权访问的人或位置
  • 拒绝服务 - 导致用户无法使用应用程序的破坏行为
  • 特权升级 - 用户非法获得过高的应用程序访问特权

您可以使用以下技术来解决 STRIDE 威胁:

  • 身份验证 - 严格的身份验证有助于减少标识欺骗。当用户登录到 Windows 或启动应用程序时,他(她)会输入“凭据”信息,如用户名和密码。Windows 使用 NTLM 或 Kerberos 等协议验证用户的凭据,并让用户登录到系统。应用程序则通常使用系统登录产品,或者以实现自定义身份验证作为授权的基础。有关身份验证的详细信息,请参阅 MSDN Library 中的 Building Secure ASP.NET Applications(英文)。
  • 授权 - 使用本指南介绍的授权技术来应对数据篡改和信息泄露的威胁。
  • 标识流 - 跨多台计算机部署的应用程序,有时需要在系统中的计算机之间传递代表通过验证的用户标识的信息。如果在第一台计算机中进行身份验证,而其他应用程序逻辑驻留在单独的计算机上,通常会用到标识流。有关标识流的详细信息,请参阅本指南后面的设计用于授权的标识流
  • 审核 - 记录已授权的和未授权的操作,减少可否认性带来的威胁。本指南不对审核进行详细介绍。有关审核的详细信息,请参阅 MSDN Library 中的 Building Secure ASP.NET Applications(英文)。

有关 STRIDE 的详细信息,请参阅 MSDN Library 中的 Designing for Securability(英文)。

图 2 显示的模型说明了如何降低多层应用程序中的 STRIDE 安全威胁。

图 2:多层应用程序的安全模型

图 2 描述的是一种具有多个物理层的部署,但是,许多较小的应用程序是在一个物理层上完成的,这就简化了身份验证、授权和标识流。图 2 包含了以下降低安全威胁的措施:

  1. 通过带宽限制来降低拒绝服务 (DoS) 的攻击。这可以防止恶意的应用程序和用户对应用程序连续进行不受欢迎的干扰,从而避免应用程序出现问题。
  2. 使用加密来保证通信安全。
  3. 身份验证可以防止标识欺骗。
  4. 身份验证根据数据存储库来验证凭证。
  5. 应用程序层之间使用标识流(可选)。
  6. 通过审核来保证可否认性。
  7. 通过授权来应对篡改和泄露数据的威胁。

选择授权机制

您可以使用多种授权机制来控制应用程序的功能,使其按预期方式运行,不被意外或蓄意误用。这些授权机制包括以下几种:

  • 系统授权 - Windows 使用访问控制列表 (ACL) 保护资源,如文件和存储过程。ACL 指定哪些用户可以访问安全资源。
  • .NET 代码访问安全性授权 - 代码访问安全性根据代码的来源授权代码以执行操作。例如,代码访问安全性根据证据标准,确定 .NET 应用程序中可以访问其他程序集的程序集。
  • 应用程序授权 - 应用程序代码自身授权用户操作。

可以综合使用这些方法创建安全的应用程序,如图 3 所示。

图 3:选择授权机制

系统授权

“系统授权”是为操作系统控制的对象(例如打印机、文件)设置资源权限或 ACL 的过程。系统管理员负责维护这些设置。系统授权是一种非“是”即“否”的决策模式:用户要么被授权访问资源,要么不被授权访问资源。

系统授权的示例包括:

  • 基于 Microsoft ASP.NET 的应用程序授权设置,用于限制对 Web.config 文件中指定的 URL 文件或路径的访问。
  • Active Directory® 目录服务中的权限设置。
  • NTFS 文件系统访问控制项。
  • 消息排队权限。
  • 服务器产品(如 Microsoft SQL Server™)中授予的权限。这种权限可能涉及各种对象,如表或视图。

有关系统级安全和授权的详细信息,请参阅 MSDN Library 中的 Building Secure ASP.NET Applications(英文)。

系统授权可以对各种对象施加约束,而限制代码则需要采用 .NET 代码访问安全性授权。

.NET 代码访问安全性授权

.NET 公共语言运库使用代码访问安全性来限制可执行的代码。代码访问安全性根据证据向应用程序代码授予权限(称为“权限设置”)。这些证据可以包括代码的来源、发布者或其他证据,如程序集的严格名称。

权限设置使您能够控制应用程序可以执行的操作,如删除文件或访问 Internet。例如,您可以限制应用程序只使用隔离的存储单元或控制打印访问。

不管用户的身份如何,代码访问安全性只考虑证据,即使具有管理特权的用户使用应用程序,代码访问安全性权限仍旧保持不变。例如,如果代码来自 Internet,不管用户是谁,对其应用的限制(如删除文件的能力)保持不变。

代码访问安全性的应用示例包括:

  • 限制下载组件的操作权利并将其放置到隔离环境中,防止下载组件执行危险操作。隔离有助于防止欺诈代码损坏系统。
  • 为在 Web 服务器或应用程序服务器上运行的宿主代码创建隔离环境。
  • 限制组件的操作权利,防止被恶意代码误用。
注意:请使用 Caspol.exe 或 Microsoft .NET Framework 配置管理控制台配置代码访问安全性。

有关代码访问安全性的详细信息,请参阅 MSDN Library 中《.NET Framework Developer's Guide》中的 Code Access Security(英文)一文。

代码访问安全性通过检查代码权限来保证系统的安全性,但可能还需要使用应用程序授权来检查用户的权限,这取决于应用程序。

应用程序授权

大多数应用程序会根据用户与系统的交互活动实现不同的功能或安全权限。设计“应用程序授权”指根据程序中的用户角色,实施业务规则或限制用户对应用程序资源的访问。

应用程序授权的主要目的是保护功能和其他无形内容,如业务逻辑。因此,很难使用当前的系统级技术实现应用程序授权,因为这些技术需要使用有关物理资源的设置,如 ACL。例如,您想确保对员工费用申请的批准操作的安全,却没有要保护的物理资源,因此,当您设计应用程序授权时,应该着眼于高级别的操作,而不是各种资源。

当系统授权机制分类过细或不考虑应用程序的数据与状态时,应用程序授权提供了另一种系统授权方法。例如,如果 XML Web Service 的系统级安全标准仍处于开发阶段,还在不断地发展丰富,那么您可以不必等到标准形成之后再向 XML Web Service 添加安全设置或创建安全的 XML Web Service。对于目前已创建的 XML Web Service,您可以实现应用程序授权,使用安全套接字层 (SSL) 或其他组合来保护对服务的调用。

应用程序授权的示例包括:

  • 检查用户是否具有执行应用程序中特定操作(如批准费用申请)的权限。
  • 检查用户是否具有访问应用程序资源(如从数据库中检索敏感数据列)的权限。

在本指南的后面部分中,您将学习如何设计这些应用程序授权以及如何编写相关代码。

入口检查

为了防止操作被连续不断地错误执行而导致最终失败,您应该始终做到尽快地对用户的每个请求进行授权。每个授权点称为“看门人”。这种看门机制的示例包括 ASP.NET 入口中的文件和 URL 授权。在标识流向各个层次传递的过程中,可能会有若干个“看门人”。在门口进行检查可以减少在系统深层(通过入口点或门以后)必需的授权检查次数。

在系统深层执行授权检查的对象需要较少的授权失败补偿逻辑。单个组件不负责处理授权失败,不会抛出异常来通知失败的调用者。

使用角色执行授权

.NET Framework 提供了基于角色的应用程序授权能力。“角色”指共享同一安全特权的一类或一组用户。

使用角色代替特定的用户标识具有以下优势:

  • 发生变化(如添加用户、提升用户或用户调离工作)时,不需要改变应用程序。
  • 维护角色的权限比维护各个用户的权限容易。例如,处理 10 个角色比处理 120 个用户容易,而且还节省时间。
  • 一个用户可以具备多个角色,这增强了分配和测试权限的灵活性。

根据业务组织定义角色

角色可以代表用户在组织中的地位,例如:

  • Manager
  • Employee
  • ClaimApprovalDepartment

使用这种方法的一个好处是信息通常可以从存储库(例如 Active Directory)中检索出来。通常情况下,这些角色在对实际业务需求进行建模时十分有用。

与组织的变化无关

您还可以使用角色来指出用户执行的工作属于哪种操作类型。这样的角色可以将应用程序的功能链接到各个用户,例如:

  • CanApproveClaim
  • CanAccessLab
  • CanViewBenefits

第二种方法更灵活一些,因为您可以围绕应用程序的功能来设计角色,而不用过多考虑组织的结构,但维护起来可能比较困难,因为缺乏保存角色的结构。大多数情况下,需要在应用程序中综合使用这些方法。

不使用角色执行授权

有时您必须以用户是谁作为授权的基础,而不会过分关注用户在应用程序中扮演的角色。例如,您可能要实现只允许部门经理批准员工的费用申请,而要达到这种授权级别,可以将当前用户与提出申请的员工的经理进行比较。

在基于 .NET 的应用程序中使用基于角色的安全设置

.NET Framework 在 System.Security.Principal 命名空间中提供了基于角色的安全设置实现,您可以用此实现来授权应用程序。要在 .NET Framework 中使用应用程序授权,请创建 IIdentityIPrincipal 对象来表示用户。IIdentity 封装的是一个通过验证的用户,IPrincipal 则是用户标识和用户角色的组合。

图 4 显示了 IIdentityIPrincipal 对象之间的关系。

图 4:IIdentity 和 IPrincipal 对象之间的关系

请注意图 4 中的以下几点:

  1. IIdentity 对象是实现 IIdentity 类的实例。IIdentity 对象表示特定的用户。
  2. IIdentity 接口具有 NameIsAuthenticatedAuthenticationType 属性。实现 IIdentity 的类通常还包含有特定用途的其他私有成员,例如,WindowsIdentity 类封装了正运行代码的用户的帐户令牌。
  3. IPrincipal 对象是实现 IPrincipal 的类的实例。IPrincipal 对象是代表用户的 IIdentity 及其具有的角色的组合。这样就可以实现单独的身份验证和授权。
  4. IPrincipal 对象使用 IsInRole 方法执行授权,并通过 Identity属性提供对 IIdentity 对象的访问。

使用标识

.NET Framework 提供了四种实现 IIdentity 接口的类:

  • WindowsIdentity
  • GenericIdentity
  • PassportIdentity
  • FormsIdentity

每种类都允许您使用不同种类的用户标识。要访问使用 Windows 身份验证的应用程序的当前 WindowsIdentity 对象,可以使用 WindowsIdentity 类的静态 GetCurrent 方法,如以下代码所示:

您还可以通过在自己定义的类中实现 IIdentity 接口来创建自定义标识类。有关创建自定义标识的详细信息,请参阅本指南后面的扩展默认实现。有关如何使用默认 IIdentity 实现的详细信息,请参阅本指南后面的设计用于授权的身份验证

使用主体

.NET Framework 提供了链接用户角色和标识的 IPrincipal 接口。所有执行应用程序授权的托管代码都应该使用实现 IPrincipal 的类的对象。例如,WindowsPrincipalGenericPrincipal 类提供了内置的 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 对象的权限

使用 IIdentity 对象是一种敏感操作,因为在该操作中可以使用与用户相关的信息。允许应用程序更改当前的 IPrincipal 对象也应该受到保护,因为应用程序授权能力是以当前的主体为基础的。框架要求这些操作具有代码访问安全性权限,从而提供了保护。使用 Caspol.exe 或 .NET Framework 配置工具为需要管理这些对象的应用程序授予 SecurityPermissionAttribute.ControlPrincipal 权限。

默认情况下,所有本地安装的应用程序均具有该权限,因为它们是在“完全信任”的权限设置下运行的。

执行以下方法需要 ControlPrincipal 权限:

  • AppDomain.SetThreadPrincipalPolicy()
  • WindowsIdentity.GetCurrent()
  • WindowsIdentity.Impersonate()
  • Thread.CurrentPrincipal()

有关使用 CASPOL 设置安全权限的详细信息,请参阅 MSDN Library 中的 Configuring Security Policy Using the Code Access Security Policy Tool (Caspol.exe)(英文)。

Windows 和公共语言运库之间管理授权的区别

公共语言运库在 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 用户帐户或 Windows 用户所属的角色执行您的授权检查。Windows 根据由以下技术提供的凭据来选择用户帐户:

  • 使用用户登录到计算机时提供的信息
  • 使用通过配置工具(如组件服务管理实用程序)专为应用程序创建的服务帐户

使用 Windows 身份验证进行授权的整个过程是这样的:

  1. Windows 使用 NTLM 或 Kerberos 验证用户的凭据。有关使用 Kerberos 委托的详细信息,请参阅 Building Secure ASP.NET Applications(英文)中的“How To: Implement Kerberos Delegation for Windows 2000”。
  2. 如果登录成功,将生成一个 Windows 访问令牌,对 .NET 应用程序来说,该令牌封装在 WindowsIdentity 中。
  3. 运行时通常会自动为包含 Windows 角色概念的 WindowsPrincipal 对象设置 Thread.CurrentPrincipal 属性,但有些情况下,您必须亲自按本节后面介绍的方法进行设置。
  4. 现在可以使用 Thread.CurrentPrincipal 进行授权。
  5. 当您远程调用应用程序逻辑时,有时需要手动向被调用者提供标识信息。本指南后面的实现手动标识流将详细讨论该问题。
注意:先将 IPrincipal 对象链接到线程,然后再从 Thread.CurrentPrincipal 属性检索主体或标识信息。这样就可以在内存中维护一个角色集,从而降低查找这类信息的频率。

使用 Windows 角色执行授权

您可以使用 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

  • 调用 SetPrincipalPolicy
  • 设置 CurrentPrincipal
注意:使用 IIdentityIPrincipal 对象的代码必须具备 ControlPrincipal 权限,请使用代码访问安全性为应用程序授予该权限。有关详细信息,请参阅本指南前面的在基于 .NET 的应用程序中使用基于角色的安全设置

调用 SetPrincipalPolicy

AppDomain 对象的 SetPrincipalPolicy 方法可以将特定类型的 IPrincipal 对象链接到应用程序域。请使用 PrincipalPolicy 枚举类型的 WindowsPrincipal 值,将当前的 WindowsPrincipal 对象链接到应用程序线程,如以下代码所示:

然后就可以授权检查主体了。在开始执行应用程序时,调用 SetPrincipalPolicy 以确保在执行任何授权检查之前 IPrincipal 对象已链接到线程。

请勿在 ASP.NET 应用程序中使用 SetPrincipalPolicy,因为在 Global.asax 中定义的 Application_AuthenticateRequest 事件处理程序过程中,ASP.NET 身份验证将为您管理此值。

注意:只有在应用程序域中不存在默认的主体时,SetPrincipalPolicy 才有效。

设置 CurrentPrincipal

要设置 CurrentPrincipal 属性,首先必须创建一个新的 WindowsPrincipal 对象,方法是将 WindowsIdentity 对象传递给 WindowsPrincipal 构造函数。然后将新的 WindowsPrincipal 对象链接到 Thread.CurrentPrincipal 属性,如以下代码所示:

然后就可以授权检查 IPrincipal 对象了。

有关如何使用 WindowsPrincipal 执行授权检查的详细信息,请参阅本指南后面的执行授权检查

使用应用程序定义的角色执行授权

要根据应用程序定义的角色(如 CanApproveClaims)创建授权检查,必须手动创建通用的或自定义的 IPrincipal 对象。您无需调用 SetPrincipalPolicy 方法,因为它的默认设置是 NoPrincipal,正是指定您自己的主体的正确设置。

大多数情况下,根据已通过身份验证的用户名来创建 GenericIdentity 对象。(通过使用 IIdentity 接口的 Name 属性,可以得到该用户名。)这样,在向其他组件提供 IIdentity 对象时,就可以避免所有涉及用户登录令牌(可以使用 WindowsIdentity 对象访问)的安全问题。然后,可以创建 GenericPrincipal,将 IIdentity 对象链接到自己的应用程序定义的角色列表中。

以下代码显示了使用 Windows 身份验证时,如何创建 GenericIdentityGenericPrincipal 对象:

大多数情况下,应该从数据存储库中检索应用程序定义的角色,而不是为角色编写代码,从而在指定角色成员时获得更大的灵活性。有关使用该方法的示例,请参阅附录中的sqlserver" target=_self>如何使用 SQL Server 构建 GenericPrincipal。

有关如何使用 GenericPrincipal 执行授权检查的详细信息,请参阅本指南后面的执行授权检查

根据非 Windows 身份验证执行授权

您可能不想或不能使用 Windows 身份验证执行应用程序授权。出现这种情况的原因可能是用户没有 Windows 用户帐户,或者是因为技术限制而无法使用 Windows 身份验证来验证用户的凭据。

非 Windows 身份验证是使用非 Windows 技术验证用户凭据的过程。这种身份验证包括 .NET Framework 为您提供的验证(如 ASP.NET 窗体身份验证)和您自己创建的验证方法。

使用非 Windows 身份验证进行授权的整个过程如下:

  1. 应用程序收集用户凭据。
  2. 应用程序验证凭据,通常是以自定义的数据存储库(如 SQL Server 数据库)为参照。
  3. 开始是将 Thread.CurrentPrincipal 属性设置为不包含任何角色的 GenericPrincipal 对象。然后用新的 GenericPrincipal 对象,或者用实现 IPrincipal 的自定义类对象取代 Thread.CurrentPrincipal,以提供应用程序定义的角色。
  4. 现在可以使用 Thread.CurrentPrincipal 进行授权。
  5. 当远程调用应用程序逻辑时,需要向被调用者手动提供标识信息。

常见的非 Windows 身份验证类型有:

  • ASP.NET 窗体身份验证 - ASP.NET 窗体身份验证自动创建 FormsIdentity 对象,代表已验证的标识。这种验证包含一个 FormsAuthenticationTicket,提供用户身份验证会话的信息。

    窗体身份验证提供程序根据 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 身份验证 - ASP.NET 应用程序可以使用 .NET Passport 执行身份验证。Passport 身份验证生成一个表示用户标识的 PassportIdentity 对象。PassportIdentity 对象扩展了基本的 IIdentity 接口,以便包括 Passport 配置文件信息。

    Passport 身份验证提供程序根据 PassportIdentity 对象创建一个 GenericPrincipal,但是没有角色成员关系。

    有关 Passport 身份验证的详细信息,请参阅 MSDN Library 中的 The Passport Authentication Provider(英文)。

  • ISAPI 身份验证解决方案 - 是一种备用方法,它使用 HTTP 标头来实现手动或自定义的身份验证机制,例如 Microsoft Commerce Server 中的身份验证机制。有关 Microsoft Commerce Server 中安全设置的详细信息,请参阅 MSDN Library 中的 Commerce Server Security(英文)。

    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。

设计用于授权的标识流

身份验证创建用于授权的 IIdentityIPrincipal 对象,并确定如何通过编程方式将标识信息传递给其他计算机上远程部署的应用程序逻辑。已通过验证的标识的传递过程称为“标识流”。实现标识流的方法有两种:

  • 自动标识流
  • 手动标识流

使用自动标识流

如果所有需要标识信息的代码是在同一上下文中执行的,公共语言运库将自动提供标识流。调用者和被调用者共享相同的应用程序域时,它们处于同一上下文。如果客户端代码(称为“调用者”)和被调用的组件(称为“被调用者”)在同一上下文中运行,.NET 将对两者自动使用同一个 Thread.CurrentPrincipal 对象。有关被调用者和调用者处在不同线程的情况,请参阅本章后面的使用多线程执行授权

在一个应用程序域中执行的代码示例包括:

  • 在进程内组件中包含所有必需的代码且未远程部署逻辑的 ASP.NET Web 站点。
  • 未远程部署逻辑、基于 Windows 窗体的单机应用程序。

实现手动标识流

由于技术限制,需要在不能执行身份验证的远程层进行授权检查时,可以实现手动标识流。例如,通过 TCP 通道使用 .NET Remoting 调用远程组件。有关在 .NET Remoting 应用程序中使用通道的详细信息,请参阅 MSDN Library 中的 Microsoft .NET Remoting: A Technical Overview(英文)。

如果您正在实现自己的标识流,请使用与调用代码不同的方式传递数据(称为“分离”)。如果您的组件接口涉及调用功能,并且使用参数传递数据,请使用其他方式发送标识信息。

例如,如果您正在使用 SOAP 调用 XML Web Service,则请勿将标识信息作为消息正文的一部分发送,而应将其放入自定义的 SOAP 标头中。这时对安全性的要求不象处理代码设计问题时那样严格。这种分隔方式可以增加代码接口的扩展性、可复用性和聚合性。这样做还使您可以随着技术标准的发展更改发送标识信息的方法,而无需改变接口约定。Web Services Security (WS-Security)(英文)规范说明了这一点。

请注意,标识流不同于传递凭据,后者是在服务器上进行的高效重验证过程。另外,您还可以在消息本身中传递已加密的凭据,但是,加密需要使用共享密钥或公钥系统,所以用这种方式传递凭证没有什么优势。

以下是一些实现标识流的最佳方案:

  • 使用功能要求和技术限制允许的最有力的身份验证。
  • 有些标识流技术会涉及存储和/或传递机密信息,这些信息对于标识流机制的安全性至关重要。对机密信息进行加密或者使用安全的通道来保证信息的安全。System.Security.Cryptography 命名空间中的类或许能对您有所帮助。另外,Building Secure ASP.NET Applications(英文)中的“How To: Create a DPAPI Library”的示例也可以供您借鉴。
  • 使用诸如数据保护 API (DPAPI) 之类的技术保存机密信息。有关 DPAPI 的信息,请参阅 MSDN Library 中的 Windows Data Protection(英文)。
  • 确保您的实现允许您的审核达到所需的深度。有关审核的详细信息,请参阅 Microsoft TechNet 中的 Monitoring and Auditing for End Systems(英文)。

在信任环境中传递标识信息

在信任环境中,被调用者将身份验证的责任交给了调用者。这时认为我们从调用者传递到被调用者的所有信息都是安全可靠的,例如 Web 层是调用者,内部应用程序中的组件是被调用者。

注意:如果非法用户可以调用其他(和潜在虚假)标识传递的代码,这种解决方案将是不安全的。为了防止这类欺诈攻击,请使用系统层提供的技术来保护安全套接字层 (SSL) 或 IP 安全性 (IPSec) 通信通道,并使用代码访问安全性来确保只有相应的代码才能调用您的函数。

在信任环境中,主要采用两种方法在应用程序层之间传递标识信息:

  • 只传递用户名 - 调用者只向被调用者传递用户名。然后,如本指南前面提到的那样,被调用者查询授权存储库,创建通用的或自定义的 IPrincipal 对象,并将对象连接到线程中。

    当您向数据库传递标识信息进行审核时,也可以使用这种方法。有关详细信息,请参阅本指南后面的在数据层执行授权

    该方法较为灵活,因为不需要强制调用者传递特定类型的对象或额外信息,例如应用程序角色。

  • 传递角色(使用 IPrincipal 对象)- 验证用户标识之后,调用者通过序列化通用或自定义 IPrincipal 对象向被调用者传递角色。然后,如前面提到的那样,被调用者反序列化对象,并将对象连接到线程中。这与在单个应用程序的域中自动发生的过程的效果相同。。

    通过传递自定义的 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 解决方案。

上面您已经了解了如何使用各种身份验证机制实现授权,以及如何在各层之间传递授权数据,下面将为您介绍如何在企业应用程序的各层进行授权。

在企业应用程序中执行授权

目前大多数的应用程序都得益于多层设计,因为多个层次可以提供伸缩性、灵活性并且可以改善性能。尽管应用程序各层的授权目的相同(控制用户访问系统功能和数据),但是各层授权的设计方法和实现方法并不相同。企业应用程序中的三层是:

  • 用户界面层
  • 业务层
  • 数据层

在用户界面层执行授权

本节介绍在用户界面层执行授权的方法。本指南的后面将介绍以下主题:在业务层执行授权在数据层执行授权

在用户界面层执行应用程序授权,有助于确保只有得到授权的用户才可以查看和修改数据,或者执行与特定岗位相关的业务功能(例如与工资有关的操作)。对于还需要在系统的其他层进行授权的过程来说,在用户界面层授权用户,是限制对这些过程的访问的第一道关卡。

要实现以下目的时,请在用户界面层创建访问检查:

  • 根据用户的角色成员关系,启用或禁用以及显示或隐藏控件。有关详细信息,请参阅本节后面的改变窗体中的控件
  • 根据用户的角色成员关系,将流从一个窗体(或页面)更改到下一个窗体(或页面)。有关详细信息,请参阅本节后面的改变页面流

在用户界面层创建授权代码时,请使用以下最佳方案:

  • 只要可能,就配置系统授权,以控制用户界面的输入。例如,配置 ASP.NET 应用程序中的 Web.config 文件,只允许得到授权的用户访问输入页。有关 ASP.NET URL 授权的详细信息,请参阅 MSDN Library 中的 Building Secure ASP.NET Applications(英文)。
  • 在加载窗体或页面时对用户授权,以防止用户在进程或任务出现错误时访问用户界面元素。例如,要求用户在 ASP.NET 应用程序中填写多个网页之后才能完成操作。如果允许用户在浏览器中直接输入 URL,可能导致用户能够访问那些在特定阶段无权访问的页面。因此,加载页面时应执行应用程序授权检查。
  • 请勿只将系统的安全设置建立在用户界面中的应用程序授权之上。在业务层和数据层要执行其他的访问检查,因为有可能会以多种方式或从不同的应用程序中调用这些组件。
  • 如果用户界面限制用户可以查看的数据,则请勿从数据源读取不必要的数据,这样可以避免受保护的数据在网络传输中被泄露。
注意:以下主题的所有代码示例均以 Web 窗体为例。可以通过将 User.IsInRole 更改为 Thread.CurrentPrincipal.IsInRole,使代码适用于 Windows 窗体。有关 IsInRole 方法和执行授权检查的详细信息,请参阅本指南后面的执行授权检查

改变窗体中的控件

在 Windows 窗体和 ASP.NET 应用程序中,都有可能需要根据授权改变控件在用户界面中的显示方式,包括改变某些控件的可见性或者禁用它们。

例如,在雇员费用申请的应用程序中,CanApproveClaims 角色的成员需要在窗体中使用一个按钮批准申请,而所有不属于该角色的用户都看不到这个 Approve 按钮。以下代码说明实现该目的的方法:

如果用户界面比较复杂或者有很多自定义的角色,那么在创建授权代码时,以上这种过于简单的方法会导致“if-then-else”语句链过长。

您可以使用以下技术来降低这种根据授权检查显示控件的复杂性:

  • 合并逻辑 - 将改变控件的逻辑放到从窗体的 Load 事件中调用的私有方法内。这样,就只用一个位置保存有关显示控件的窗体授权逻辑。

    以下代码显示了如何对 Web 窗体应用这种方法:

  • 必要时使用中继器控件 - 如果控件与多行数据相对应,可以使用中继器控件,从而能够同时修改所有的控件设置。

    例如,如果您创建了一个使用 DataGrid 的 Web 窗体,则可以使用一个复选框模板列,允许已授权的用户同时批准多项申请。

    以下代码说明了如何使用该技术在 DataGrid 中显示 Approve 列。

  • 使用模型视图控制器模式 - 模型视图控制器模式可以帮助您从控件激发的事件中分离控制用户界面元素行为的逻辑。在费用申请示例中,窗体可以包含一个员工列表框和一个 Show Claims 按钮。在按下 ShowClaims 时,通过将所有应执行的代码放到另一个函数中(叫做控制器函数),甚至是另一个类中,可以强制实现清楚的逻辑分离。列表框充当员工数据的视图,Show Claims 按钮处理用户操作(如“单击”)并调用相关的控制器函数。控制器函数是您的 Web 或 Windows 窗体上的一种方法,包含改变业务数据状态和用户界面状态的逻辑。

    将加载窗体之后改变控件状态的授权代码放在这些控制器函数中,而不要放在事件处理程序中。

  • 为每个角色使用单独的窗体 - 如果角色的数量比较少,而且每个角色需要完全不同的窗体,则可以为每个角色创建单独的窗体。测试各个窗体中的角色成员关系,并重定向到正确的窗体中。这也许会增加大量的测试过程,但您可以使用前面介绍的模型视图控制器模式降低由此带来的工作量,并且还能重复使用显示代码。

改变页面流

有时(比如典型向导的用户界面)会根据用户的角色成员关系,要求用户完成多个窗体(或页面)以执行业务流程。这种方法可以改变页面的顺序或“流”。

在上面的申请示例中,GeneralManager 成员可以查看并批准所有部门的员工申请,而 StoreManager 成员只能查看自己部门的员工申请。为了使 GeneralManager 成员可以选择部门,需要另外创建一个步骤来检索该信息,然后再显示员工选择窗体。

以下代码说明了创建这种页面流的方法。这段代码使用 ASP.NET Server.Transfer 方法向不同的页面传递控制。对 Server.Transfer 的调用包含在名为 DisplayNextPage 的私有方法中:

在业务层执行授权

业务过程可以是很简单的业务逻辑,也可以是大型的、运行时间很长的、包含许多信任和非信任伙伴的工作流程。业务层代码必须保证所有的客户请求都能满足组织规定的授权规则和逻辑规则。

您有如下需求时,请在业务层中创建访问检查:

  • 对执行业务流程的操作进行授权。
  • 在调用内部业务组件之前,对来自外部的、非信任源的调用进行授权。为此,需要创建一个 IPrincipal 对象,供授权过程中的业务流程使用。在标识进入业务流程时使用服务接口管理标识,或者对内部业务组件的调用进行授权,或者同时使用这两种方法。
  • 对分布式系统的其他部分或不属于应用程序的外部服务的调用进行授权。为此,需要从组件之外管理标识流。使用服务代理来确保当前用户可以执行外部调用和/或在标识溢出过程之外时可以处理标识。

在业务层创建授权代码时,请使用以下最佳方案:

  • 将授权能力分解到框架和实用程序组件,如自定义的 IPrincipal 对象。这将允许您创建独立于业务逻辑的授权能力。
  • 在高级别过程的开始处执行授权检查。这样可以在较少的位置配置授权,减少维护的工作量。

    但要使解决方案更加安全,需要在每个可以公开访问的方法中执行授权,因为对这些方法的调用可能未通过授权检查。

在 .NET 和 COM+ 中使用基于角色的安全设置对比

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),以及应用程序中自定义的实体类,具体使用哪一类取决于应用程序的物理和逻辑设计限制。应用程序既生成实体又使用实体。

实体经常在应用程序层之间移动,包括从数据源检索数据、发送到用户界面再返回到数据源进行更新的整个过程。

您可以创建类来代表执行授权检查的实体。例如,只有属于某一角色的用户才能创建和初始化实体对象。

但是,大多数情况下,不应在实体中执行应用程序授权,因为:

  • 纯数据实现(如 DataSet、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 对象,从而将标识流传递到组件之外。有关管理标识流的详细信息,请参阅本指南前面的设计用于授权的标识流

在数据层执行授权

数据层是最后一道防线,用于抵御来自用户界面、业务组件以及试图直接访问数据的攻击者的非法请求。从授权的角度看,数据层确保只有经过批准的用户才可以访问和修改数据,而不管其访问方式如何。

需要执行以下操作时,请在数据层中创建访问检查:

  • 防止敏感数据泄露到数据层之外。
  • 防止未经授权修改现有数据和插入新数据。
  • 使用存储过程或视图限制对数据的访问,而不是修改对数据表的直接访问权限。

要在数据层执行授权,可以使用多种技术和对象,这取决于您的安全要求。可以在以下数据层组件中执行授权:

  • 数据访问逻辑组件 - 数据访问逻辑组件负责从数据库检索数据、将数据保存回数据库,还负责处理完成这些数据操作所需的逻辑请求。所有应用程序数据都是通过数据访问逻辑组件在数据库和应用程序的其他部分之间传递的。
  • 存储过程 - 存储过程可以在数据库中执行授权逻辑并检索和更新数据。
  • 数据库安全功能 - 使用 SQL Server 内置的安全功能保护数据库对象,例如表、列、视图和存储过程。

有关使用数据访问逻辑组件和实体的详细信息,请参阅 MSDN Library 中的 Designing Data Tier Components and Passing Data Through Tiers(英文)。

以下这些问题能帮助您选择数据层的授权策略:

  • 授权逻辑有多复杂?
    • 简单 - 在存储过程中执行授权检查。
    • 复杂 - 在数据访问逻辑组件中执行授权,并调用不同的存储过程。
  • 存储过程的复杂程度如何?
    • 简单 - 在数据访问逻辑组件中执行授权检查,并调用不同的存储过程(如果创建多个执行相似操作的存储过程不会增加维护难度)。
    • 复杂 - 在存储过程中执行授权检查,该存储过程具有向授权检查提供信息的参数。

      如果创建执行相似操作的多个存储过程会增加维护的难度,则可以向存储过程传递一个值以减少存储过程的数量。

  • 有多少存储过程会涉及授权检查?
    • 较少 - 创建多个涉及授权检查的存储过程版本。创建明显地链接存储过程的命名标准,例如 GetClaimsMGR 和 GetClaimsEMP。但这种方法在应用程序的开发过程中对设计是不利的,因此要慎用。
    • 较多 - 创建多个单独的存储过程,根据连接的用户的不同检索不同的数据。使用表示应用程序角色的服务帐户连接到数据库,允许存储过程执行授权检查。这种方法最大限度地减少了存储过程的数量,因而也减少了维护的工作量。

在数据层创建授权代码时,请使用以下最佳方案:

  • 连接到服务帐户较少的数据库,以使连接池的性能达到最优。当连接信息(如用户信息)与现有连接匹配时,连接池允许应用程序重复使用连接。因此,不必为每个用户创建连接。

    有关连接管理的详细信息,请参阅 MSDN Library 的 .NET Data Access Architecture Guide(英文)中的“Managing Database Connections”。

  • GRANTDENY 权限保护数据库对象。

    拒绝对数据表的直接访问可以使用应用程序(如查询分析器)防止连接到数据库的用户,而不会强制对数据的业务逻辑进行更新或查询。只有允许的用户/服务帐户可以访问存储过程和视图。

  • 在使用包括 WHERE 子句的应用程序授权技术时,始终使用存储过程。这样有助于防止 SQL 夹带攻击,实施这类攻击的攻击者会将其他的(通常是恶意的)SQL 语句附加到合法的 WHERE 子句中。有关 SQL 夹带攻击的详细信息,请参阅 MSDN Library 中的 Building Secure ASP.NET Applications(英文)。

在数据访问逻辑组件中执行授权

数据访问逻辑组件是在访问数据库之前最后一个提供功能的组件。因此,您可以利用它们来确保只有获得授权的用户才可以访问或修改数据。

出现以下情形时,请在数据访问逻辑组件中执行授权:

  • 多个应用程序使用相同的数据访问逻辑组件。
  • 需要对数据访问逻辑组件或数据存储库提供的强大功能的访问进行保护。
  • 需要限制用户对敏感数据的访问。

在数据访问逻辑组件中使用基于角色的安全设置的例子包括:在数据传递到其他应用程序层之前对数据进行过滤、控制代码执行的存储过程。

有关如何执行授权检查的详细信息,请参阅本指南后面的执行授权检查

在数据访问逻辑组件中过滤数据

为了保证只向其他应用程序层返回适当的信息,应该在数据访问逻辑组件代码中有选择地删除敏感数据。

下面的代码示例在用户不属于 Manager 角色时,删除数据的 ConfidentialNotes 列。

从数据库中检索不需要的列是对资源的一种浪费,尤其是不需要的列很多时。相反,使用存储过程只选择必要的数据,可以改善数据库引擎的性能。

从数据访问逻辑组件调用不同的存储过程

您可以创建仅为一个角色返回正确数据的多个存储过程。然后,数据访问逻辑组件代码执行授权检查,根据用户角色确定要使用哪些存储过程。

例如,可以为 Manager 创建一个存储过程版本,为 Employee 创建另一个不同的版本,两者只是检索的数据不同。数据访问逻辑组件代码将检查用户的角色成员关系,并调用正确的存储过程。

以下代码显示了数据访问逻辑组件方法如何根据用户的角色成员关系选择要调用的存储过程。这些代码使用了一个辅助类,以避免显示过多的 ADO.NET 代码。Microsoft Application Blocks for .NET(英文)提供了 SqlHelper 类:

在存储过程中执行授权

在存储过程中执行授权的方法主要有两种:

  • 传递角色标志 - 在数据访问逻辑组件中执行授权检查,将授权结果标志传递到存储过程。这样将允许存储过程根据授权标志过滤数据,如以下代码所示。
  • 传递用户标识或其他角色信息 - 数据访问逻辑组件将用户标识作为参数传递到存储过程。然后,存储过程使用该参数来查找用户的角色成员关系并选择数据,如以下代码所示。

在数据库中执行授权

以数据库安全功能作为安全解决方案的基础。然后在数据库安全功能之上使用存储过程设计应用程序授权,如本指南前面所述。

使用数据库安全功能执行授权:

  • 使用 SQL 数据定义语言 (DDL) 安全语句,如 GRANTDENYREVOKE,控制对表、列、视图、存储过程和其他数据对象的访问。
  • 以可能的最低级别(只有数据)限制对敏感数据的访问。SQL Server 本身不支持低级别授权,但是,您可以通过创建视图和存储过程来模拟低级别授权,本主题的后面将介绍有关内容。
  • 尽可能地加强安全性。注意,较高的安全性会降低可伸缩性。

在数据库中执行授权之前,必须选择应用程序连接到数据库的方式。

使用 SQL 角色简化管理

设计系统时,必须考虑使用 SQL Server 角色简化对 SQL Server 的维护。SQL Server 提供了多种角色,包括:

  • 用户定义的数据库角色 - 由数据库唯一的 sp_addrole 存储过程创建的角色。然后可以使用 sp_addrolemember 存储过程向这些角色添加 SQL 用户。
  • 应用程序角色 - 为应用程序而不是为 Windows 用户或组创建的角色。
  • 固定的数据库角色 - 链接到通用数据库功能的固定角色,例如 SQL 管理员和数据库创建者:sysadmindbcreator

有关 SQL Server 安全性、授权和授权角色的详细信息,请参阅 MSDN Library 中的 Building Secure ASP.NET Applications(英文)。

考虑将应用程序角色链接到 SQL Server 角色,使应用程序和 SQL Server 的管理一致。将直接映射到 SQL 角色的服务帐户连接到数据库,以实现这种链接。下面一节介绍了这种技术。

连接到数据库

为了控制对表、列、视图、存储过程和其他数据对象的访问,SQL Server 必许能够识别需要授权的用户,这种识别可以通过以下连接方法实现:

  • Windows 身份验证 - 如果将 SQL Server 配置为只使用 Windows 身份验证,则必须在用户上下文中执行建立连接的过程。SQL Server 使用该 Windows 用户帐户进行所有形式的授权。

    这种方法提供的连接最安全,因为它不通过线路传递凭据。

  • SQL Server 身份验证(混合安全设置)- 如果将 SQL Server 配置为使用 SQL Server 和 Windows 身份验证,则既可以使用 Windows 身份验证,也可以使用用户名和密码在连接字符串中指定用户来进行身份验证。

无论以何种方式连接到数据库,都要先创建几个代表应用程序角色的服务帐户,然后根据角色要求,为各个服务帐户授予权限。

例如,创建 USR_MANAGER 和 USR_APPROVER 等服务帐户以实现最少特权的原则,即只允许访问一部分应用程序功能。

这种方法会带来某些不便,因为当您使用 Windows 身份验证连接到数据库时,需要从数据访问逻辑组件模拟服务帐户。

有关管理数据库连接的详细信息,请参阅 MSDN Library 中的 .NET Data Access Architecture Guide(英文)。

控制对敏感列的访问

使用 T-SQL 语句或 SQL Server 企业管理器实用程序,在列级别应用 GRANTDENYREVOKE 权限,从而实现对列的访问控制。请尽量少用这种授权技术,因为如果数据库很大,要维护的列权限列表会很长,在实际操作中非常耗时。如果您要使用这种技术,请在连接到数据库时使用服务帐户,以使维护工作易于管理。

控制对敏感行的访问

SQL Server 没有隐含地让您控制对各行的访问。但是,您可以向数据表添加安全列,创建控制行的功能。安全列可以保存单独的用户名或访问数据需要的最低角色(假设角色的组织形式是分层的)。安全列允许您限制对各行的访问,但是如果用户可以直接访问数据表,该技术将没有效果。

可以结合使用视图或存储过程中数据表的安全列功能和 SUSER_SNAME 函数,控制对各行的访问。SUSER_SNAME 函数将检索连接的用户名(很可能是服务帐户名)。

以下代码显示如何创建一个视图,该视图只返回当前用户的行。

注意:该方法要求您在连接数据库时使用单独的用户标识,以便 SUSER_SNAME 函数能够检索到正确的信息

延伸阅读

文章来源于领测软件测试网 https://www.ltesting.net/

TAG: 程序 管理 设计 应用 授权


关于领测软件测试网 | 领测软件测试网合作伙伴 | 广告服务 | 投稿指南 | 联系我们 | 网站地图 | 友情链接
版权所有(C) 2003-2010 TestAge(领测软件测试网)|领测国际科技(北京)有限公司|软件测试工程师培训网 All Rights Reserved
北京市海淀区中关村南大街9号北京理工科技大厦1402室 京ICP备10010545号-5
技术支持和业务联系:info@testage.com.cn 电话:010-51297073

软件测试 | 领测国际ISTQBISTQB官网TMMiTMMi认证国际软件测试工程师认证领测软件测试网