| 本章内容 | |
| 目标 | |
| 适用范围 | |
| 如何使用本章内容 | |
| 安全体系结构 | |
| 服务器应用程序和库应用程序的安全性 | |
| 代码访问安全性要求 | |
| 配置安全性 | |
| 配置 ASP.NET 客户端应用程序 | |
| 配置企业服务应用程序的模拟级别 | |
| 编程安全性 | |
| 选择进程标识 | |
| 访问网络资源 | |
| 传送原调用方 | |
| RPC 加密 | |
| 构建服务组件 | |
| DCOM 和防火墙 | |
| 从 ASP.NET 调用服务组件 | |
| 安全性概念 | |
| 总结 |
.NET 组件可以使用传统的 COM+ 服务,例如分布式事务、实时激活、对象池和并发管理等。在 .NET 中,这些服务通称为企业服务,并且运行在企业服务应用程序环境中的组件通称为服务组件。服务组件是使用标准 .NET 语言和开发工具生成的,是实现许多分布式 Web 应用程序所需功能的关键一环。
本章讨论服务组件作为分布式 Web 应用程序的一环所带来的好处。本章描述了企业服务所提供的整体安全框架以及如何使用此框架建立和实现安全服务组件。此外,本章通过向您展示如何从 ASP.NET 客户端应用程序调用服务组件,描述如何将服务组件集成到您的分布式 Web 应用程序中。
本章的目标是:
| • | 确保您的企业服务解决方案的安全。 |
| • | 建立安全的服务组件。 |
| • | 了解企业服务提供的安全框架并标识企业服务运行时提供的网关守卫。 |
| • | 通过在开发期间使用 .NET 属性以及在部署期间使用 Windows 管理工具,为服务组件配置安全性。 |
| • | 传递具有对服务组件的调用的客户端凭据,并且将原始调用方的标识传送到下游系统。 |
| • | 使用 RPC 加密确保服务组件通信的安全。 |
| • | 从 ASP.NET 客户端应用程序调用服务组件。 |
本章适用于以下产品和技术:
| • | Windows XP 或 Windows 2000 Server (service pack 3) 和更高版本的操作系统 |
| • | .NET Framework 版本 1.0 (service pack 2) 和更高版本 |
| • | Visual C# .NET |
若要学好本章内容:
| • | 您必须具有使用 Visual C# .NET 进行编程的经验。 |
| • | 您必须具有开发和配置 ASP.NET Web 应用程序的经验。 |
| • | 您必须具有使用 Windows 管理工具配置 Windows 安全性的经验。 |
| • | 您必须具有开发和配置企业服务 (COM+) 应用程序的经验。 |
| • | 阅读第 1 章简介。这一章说明了身份验证、授权和安全通信对于分布式 Web 应用程序的重要性。 |
| • | 阅读第 2 章 .NET 应用程序的安全模型。这一章简要介绍了创建分布式 ASP.NET Web 应用程序所采用的体系结构和技术,并重点说明了身份验证、授权和安全通信在此体系结构中的作用。 |
| • | 阅读第 3 章身份验证和授权。这一章详细介绍了可用于分布式 Web 应用程序的身份验证和授权机制。此章节还介绍了模拟、委派和流标识概念,它们是对安全企业服务解决方案的实施起到重要作用的技术。 |
| • | 阅读第 4 章安全通信。这一章介绍了 RPC 加密,这是一种安全通信协议,常用于客户端和服务组件之间的通信。 |
| • | 阅读如何将基于角色的安全性用于企业服务,这一章说明了如何使用 C# 在服务组件中实施基于角色的安全性。 |
图 9.1 显示了企业服务应用程序所支持的身份验证、授权和安全通信功能。图 9.1 中显示的客户端应用程序是一个 ASP.NET Web 应用程序。

图 9.1
基于企业服务角色的安全体系结构
请注意,身份验证和安全通信功能是由分布式 COM (DCOM) 使用的基本 RPC 传输提供的。授权则是由企业服务 (COM+) 角色提供的。
下面概述了企业服务安全体系结构的主要构成要素:
| • | 企业服务应用程序使用 RPC 身份验证对调用方进行身份验证。也就是说,除非您采取了特定的步骤禁用身份验证,否则就会使用 Kerberos 或 NTLM 身份验证对调用方进行身份验证。 |
| • | 授权是通过企业服务 (COM+) 角色提供的,这些角色可以包含 Microsoft® Windows® 操作系统组或用户帐户。角色成员身份在 COM+ 目录内定义并利用“组件服务”工具进行管理。 注意:如果企业服务应用程序使用模拟功能,则还可以使用受保护资源的 Windows ACL 对调用方进行授权。 |
| • | 如果客户端(例如,ASP.NET Web 应用程序)在服务组件上调用某个方法,则在身份验证进程结束后,企业服务侦听层就会访问 COM+ 目录以确定该客户端的角色成员身份。然后,它检查该角色的成员身份是否允许授权访问当前的应用程序、组件、接口和方法。 |
| • | 如果客户端的角色成员身份具有访问权限,则调用该方法。如果客户端不属于某个相应角色,则拒绝进行调用,并且有选择地生成一个安全事件以反映失败的访问尝试。 重要说明:若要在 ASP.NET Web 应用程序调用的企业服务应用程序中实现有意义的基于角色的授权,必须在 ASP.NET Web 应用程序中使用 Windows 身份验证和模拟功能,确保将原调用方的安全性上下文传递给服务组件。 |
| • | 若要确保客户端和服务器应用程序之间 DCOM 通信链路的安全,可以使用 RPC 数据包完整性身份验证级别(提供消息完整性),也可以使用 RPC 数据包保密性身份验证级别(提供消息机密性)。 |
企业服务运行库可以充当服务组件的网关守卫。图 9.2 显示了企业服务应用程序中的各个关口(授权点)。可通过使用企业服务角色来配置这些关口(必须使用相应的 Windows 组和用户帐户来填充这些角色)。
注意:还必须确保对企业服务应用程序启用访问检查(基于角色的安全性),并且使用相应的身份验证级别。有关如何配置安全性的详细信息,请参见本章后面的配置安全性。

图 9.2
企业服务应用程序中的网关守卫
当客户端在服务组件上执行方法调用时,可以对该客户端执行三个完全不同的访问检查。图 9.2 和本文后面介绍了这三个访问检查:
1. | 第一个访问检查是由负责激活企业服务应用程序的子系统执行的;当服务组件调用生成激活请求(并创建新的 COM+ 代理进程实例 Dllhost.exe)时,该子系统就会激活企业服务应用程序(即 COM 服务控制器,SCM)以执行检查。 若成功地通过此访问检查,调用方必须至少是应用程序中定义的某个角色的成员。 |
2. | 当客户端调用进入 Dllhost.exe 进程实例时,就会执行第二个访问检查。 |
3. | 当客户端调用进入服务器应用程序或库应用程序时,就会执行最后一个访问检查。 若要成功地通过此访问检查,调用方必须是以下角色的成员:该角色与作为客户端调用目标的接口、类或方法关联。 |
重要说明:在服务组件上调用某个方法后,如果该组件与同一应用程序中的其他组件进行通信,则不需要再执行访问检查。但是,如果组件调用其他应用程序(库或服务器)中的另一个组件,则还要执行访问检查。
如果应用程序需要实施某个身份验证级别(例如,因为在通过网络传输数据时,需要进行加密以确保向服务组件发送的数据的机密性和完整性),则应该使用服务器应用程序。
可以给服务器应用程序实施身份验证级别,而库应用程序从主机进程中继承它们的身份验证级别。
若要配置企业服务应用程序的激活类型,请使用如下所示的程序集级别的 ApplicationActivation 属性。
[assembly: ApplicationActivation(ActivationOption.Server)]
这相当于在组件服务内,在应用程序“属性”对话框中的“激活”页面上将“激活类型”设置为“服务器应用程序”。
对于进程内的库应用程序和进程外的服务器应用程序而言,基于角色的安全性具有许多相似的地方。
请注意库应用程序的以下差别:
| • | 权限。库应用程序的权限是由客户端(主机)进程的权限决定的。例如,如果客户端进程以管理员权限运行,则库应用程序也具有管理员权限。 |
| • | 模拟。库应用程序的模拟级别是从客户端进程继承的,而且不能明确地进行设置。 |
| • | 身份验证。库应用程序的身份验证级别是从客户端进程中继承的。在库应用程序中,您可以明确地启用或禁用身份验证。库应用程序“属性”对话框的“安全性”页面中提供了这个选项。 该选项通常用于支持来自其他进程外 COM 组件的未验证身份的回调。 |
在库应用程序中,您应该始终在类、接口或方法级别上分配角色。这也是服务器应用程序的最佳做法。
无法将库应用程序角色中定义的用户添加到客户端进程的安全性描述符中。这意味着,要使库应用程序能够执行基于角色的授权,您必须至少使用类级别的安全性。
代码访问安全性 (CAS) 要求代码必须具有特定的权限,才能执行某些操作以及访问受限制的资源。在从 Internet 下载代码的客户端环境中,CAS 是非常有用的。在这种情况下,代码不可能被完全信任。
一般而言,使用服务组件的应用程序是完全受信任的,因此 CAS 的作用很有限。但是,企业服务要求调用代码必须具有必要的权限以调用非托管代码。这意味着:
| • | 要在服务组件上激活和执行交叉上下文调用,必须具有非托管代码权限。 |
| • | 如果服务组件的客户端是一个 ASP.NET Web 应用程序,则该应用程序必须具有非托管代码权限。 |
| • | 如果将服务组件引用传递给不受信任的代码,则无法从该代码调用在服务组件中定义的方法。 |
本节说明如何配置以下各项的安全性:
| • | 在企业服务服务器(进程外)应用程序中运行的服务组件。 |
| • | ASP.NET Web 应用程序客户端。 |
图 9.3 说明了配置企业服务服务器应用程序所需的步骤。

图 9.3
配置企业服务安全性
在开发时,您可以使用包含服务组件的程序集中的 .NET 属性来配置 COM+ 目录中的大多数安全设置。当使用 Regsvcs.exe 工具在 COM+ 中注册服务组件时,可以使用这些属性来填充 COM+ 目录。
在部署时,必须使用“组件服务”管理工具(或者以编程方式使用脚本)来配置其他的配置步骤:例如,使用 Windows 组和用户帐户来填充角色,以及配置服务器应用程序(Dllhost.exe 实例)的运行方式标识。
若要以声明方式设置应用程序身份验证级别,请使用以下所示的 ApplicationAccessControl 程序集级别的属性。
[assembly: ApplicationAccessControl(
Authentication = AuthenticationOption.Call)]
这相当于在组件服务内,在应用程序“属性”对话框中的“安全性”页面上设置“调用的身份验证级别”值。
注意:客户端的身份验证级别还会影响企业服务应用程序使用的身份验证级别。原因是,如果采用高水印协商的进程,则始终使用两个设置中较高的那一个设置。
有关配置 ASP.NET 客户端应用程序使用的 DCOM 身份验证级别的详细信息,请参见本节后面的配置 ASP.NET 客户端应用程序。
有关 DCOM 身份验证级别和身份验证级别协商的详细信息,请参见本章的安全概念一节。
若在组件、接口或方法级别上启用细分的授权,您必须:
| • | 在应用程序级启用访问检查。 使用下面的 .NET 属性启用应用程序范围内的访问检查。 [assembly: ApplicationAccessControl(true)] 这相当于在组件服务内,在应用程序“属性”对话框中的“安全性”页面上选取了“强制为该应用程序进行访问检查”复选框。 重要说明:如果未设定这个属性,将不会执行任何访问检查。 |
| • | 在进程和组件级别配置应用程序的安全级别。 [assembly: ApplicationAccessControl(AccessChecksLevel=
AccessChecksLevelOption. ApplicationComponent)]
这相当于在组件服务内,在应用程序“属性”对话框中的“安全性”页面上选取了“在进程和组件级别执行访问检查”复选框。 注意:在库应用程序的进程和组件级别上,应启用访问检查。 |
| • | 启用组件级访问检查。 若要启用组件级访问检查,请使用如下所示的 ComponentAccessControl 类级别属性。 [ComponentAccessControl(true)]
public class MyServicedComponent : ServicedComponent
{
}
这相当于在组件服务内,在应用程序“属性”对话框中的“安全性”页面上选取了“强制组件级访问检查”复选框。 注意:仅当按上述说明启用了应用程序级访问检查并配置了进程和组件级别访问检查后,该设置才生效。 |
可以在应用程序、组件(类)、接口和方法级别上创建和分配角色。
给应用程序添加角色
若要给应用程序添加角色,请使用如下所示的 SecurityRole 程序集级别属性。
[assembly:SecurityRole("Employee")]
[assembly:SecurityRole("Manager")]
这相当于使用“组件服务”工具在应用程序中添加角色。
注意:在程序集级别使用 SecurityRole 属性相当于在应用程序中添加角色,而不是给各个组件、接口或方法分配角色。结果,这些角色的成员决定了附加到应用程序的安全描述符的构成。这种方法仅用于确定允许哪些用户访问(和启动)应用程序。
要进行更有效的基于角色的授权,请按如下所述在组件、接口和方法中应用角色。
给组件(类)添加角色
要给组件添加角色,请按如下所示在类定义上面应用 SecurityRole 属性
[SecurityRole("Manager")]
public class Transfer : ServicedComponent
{
}
将角色添加到接口
若要在接口级别上应用角色,您必须创建一个接口定义,并在服务组件类中实现该定义。然后可以使用 SecurityRole 属性将角色与接口相关联。
重要说明: 在开发时,您还必须使用 SecureMethod 属性对该类进行注释。这种方法通知企业服务可以使用方法级别的安全服务。在部署时,管理员还必须将用户添加到系统定义的 Marshaler 角色中;在使用 SecureMethod 标记的类注册组件服务时,就会在 COM+ 目录中自动创建该角色。
下面一节将详细讨论 Marshaler 角色的用途。
以下示例说明如何向特定接口添加 Manager 角色。
[SecurityRole("Manager")]
public interface ISomeInterface
{
void Method1( string message );
void Method2( int parm1, int parm2 );
}
[ComponentAccessControl]
[SecureMethod]
public class MyServicedComponent : ServicedComponent, ISomeInterface
{
public void Method1( string message )
{
// 实现
}
public void Method2( int parm1, int parm2 )
{
// 实现
}
}
给方法添加角色
若确保类的公用方法出现在 COM+ 目录中,您必须明确地实现定义这些方法的接口。然后,为保护这些方法的安全,您必须在该类中使用 SecureMethod 属性,或者在方法级别上使用 SecureMethod 或 SecurityRole 属性。
注意:SecureMethod 和 SecurityRole 属性必须出现在方法实现方式之上,而不是在接口定义之中。
若要启用方法级别的安全性,请执行以下步骤:
a. 定义包含要保护的方法的接口。例如:
public interface ISomeInterface
{
void Method1( string message );
void Method2( int parm1, int parm2 );
}
b. 在服务组件类上实现该接口:
[ComponentAccessControl]
public class MyServicedComponent : ServicedComponent, ISomeInterface
{
public void Method1( string message )
{
// 实现
}
public void Method2( int parm1, int parm2 )
{
// 实现
}
}
c. 若要使用“组件服务”工具以管理方式配置角色,必须使用 SecureMethod 属性对类进行注释(如下所示)。
[ComponentAccessControl]
[SecureMethod]
public class MyServicedComponent : ServicedComponent, ISomeInterface
{
}
d. 另外,若要在开发时使用 .NET 属性给方法添加角色,请在方法级别上应用 SecurityRole 属性。在这种情况下,您无需在类级别上应用 SecureMethod 属性(但仍需使用 ComponentAccessControl 属性来配置组件级别访问检查)。
在下面的示例中,只有 Manager 角色的成员可以调用 Method1;而 Manager 和 Employee 角色的成员可以调用 Method2。
[ComponentAccessControl]
public class MyServicedComponent : ServicedComponent, ISomeInterface
{
[SecurityRole("Manager")]
public void Method1( string message )
{
// 实现
}
[SecurityRole("Manager")]
[SecurityRole("Employee")]
public void Method2( int parm1, int parm2 )
{
// 实现
}
}
e. 在部署时,管理员必须将要求访问该类的方法或接口的任何用户添加到预定义的 Marshaler 角色中。
注意:企业服务基础结构使用了许多由所有服务组件公开的系统级接口。它们包括 IManagedObject、 IDisposable 和 IServiceComponentInfo。如果在接口级或方法级启用了访问权限检查,那么企业服务基础结构将无法访问这些接口。
因此,企业服务会创建一个名为 Marshaler 的特殊角色,并将该角色与这些接口相关联。可以使用“组件服务”工具来查看该角色(和上述接口)。
在部署时,应用程序管理员需要将所有用户添加到需要访问该类中的任何方法或接口的 Marshaler 角色中。可以使用两种不同的方法自动完成此操作:
| • | 编写一个脚本,使用组件服务对象模型将其他角色中的所有用户都复制到 Marshaler 角色中。 |
| • | 编写一个脚本,将所有其他角色都分配给这三个特殊的接口并删除 Marshaler 角色。 |
在以下位置注册服务组件:
| • | 全局程序集缓存。COM+ 服务器应用程序中承载的服务组件要求使用全局程序集缓存中的安装,而库应用程序则不然。 gacutil -i MyServicedComponent.dll 注意:也可以使用“管理工具”程序组中的“Microsoft .NET Framework 配置工具”来查看和操作全局程序集缓存的内容。 |
| • | COM+ 目录。若要在 COM+ 目录中注册名为 MyServicedComponent.dll 的程序集,请运行以下命令。 regsvcs.exe MyServicedComponent.dll 该命令的执行结果是创建 COM+ 应用程序。该程序集中提供的 .NET 属性用于填充 COM+ 目录。 |
可以使用两种方法来填充角色:一是使用“组件服务”工具,二是使用脚本对使用 COM+ 管理对象的 COM+ 目录进行编程。
使用 Windows 组
将 Windows 2000 组帐户添加到企业服务角色中可以获得最大的灵活性。通过使用 Windows 组,您可以有效地使用一种管理工具(“用户和计算机管理”工具)同时管理 Windows 和企业服务安全性。
| • | 在企业服务应用程序中,为每个角色创建一个 Windows 组。 |
| • | 将每个组分配给其各自的角色。 |
| • | 在将组分配给角色后,请使用“用户和计算机管理”工具在每个组中添加和删除用户。 |
| • | 使用组件服务将 Windows 组分配给企业服务角色
|
更多信息
有关使用 COM+ 管理对象对 COM+ 目录进行编程的详细信息,请参见 MSDN 库的“组件开发”部分中的 Automating COM+ Administration(自动完成 COM+ 管理)。
可以使用“组件服务”工具(或脚本)来配置企业服务应用程序的标识。标识属性决定了运行承载该应用程序的 Dllhost.exe 实例所使用的帐户。
| • | 配置标识
|
更多信息
有关选择运行企业服务应用程序所需的相应标识的详细信息,请参见本章后面的选择进程标识。
在使用 DCOM 与服务组件进行通信时,必须配置客户端应用程序使用的 DCOM 身份验证级别和模拟级别。
若要配置 ASP.NET Web 应用程序在与服务组件进行通信时所使用的默认身份验证级别,请编辑 Machine.config 中 <processModel> 元素上的 comAuthenticationLevel 属性。
Machine.config 位于以下文件夹中。
%windir%\Microsoft.NET\Framework\v1.0.3705\CONFIG
将 comAuthenticationLevel 属性设置为以下值之一。
comAuthenticationLevel=
"[Default|None|Connect|Call|Pkt|PktIntegrity|PktPrivacy]"
更多信息
有关 DCOM 身份验证级别的详细信息,请参见本章后面安全性概念一节中的身份验证。
由客户端设置的模拟级别决定了服务器的模拟级别功能。若要配置基于 Web 的应用程序在与服务组件进行通信时所使用的默认模拟级别,请编辑 Machine.config 中 <processModel> 元素上的 comImpersonationLevel 属性。
comImpersonationLevel="[默认|匿名|标识|模拟|委派]"
更多信息
有关 DCOM 模拟级别的详细信息,请参见本章后面安全性概念一节中的模拟。
如果一个应用程序中的服务组件需要调用另一个(服务器)应用程序中的服务组件,则可能需要配置客户端应用程序的模拟级别。
重要说明:为企业服务应用程序配置的模拟级别(在应用程序“属性”对话框的“安全性”页面上)是该应用程序中的组件进行传出调用所使用的模拟级别。它不会影响应用程序中的服务组件是否执行模拟。若要在服务组件中模拟客户端,必须使用编程模拟技术。有关说明,请参见本章后面的传送原调用方。
若要以声明方式设置应用程序模拟级别,请使用如下所示的 ApplicationAccessControl 程序集级别的属性。
[assembly: ApplicationAccessControl(
ImpersonationLevel=ImpersonationLevelOption.Identify)]
这相当于在组件服务内,在应用程序“属性”对话框中的“安全性”页面上设置“模拟级别”值。
可以使用 ContextUtil、SecurityCallContext 和 SecurityIdentity 类给 .NET 组件提供企业服务安全功能。
对于细分的授权决策,可以使用 ContextUtil 类的 IsCallerInRole 方法以编程方式测试角色成员身份。在调用该方法之前,应检查是否启用了组件级访问检查,如以下代码片段所示。如果禁用了安全性,则 IsCallerInRole 始终返回 true。
public void Transfer(string fromAccount, string toAccount, double amount)
{
// 确保启用了安全性
if (ContextUtil.IsSecurityEnabled)
{
// 只允许 Manager(经理)传输金额超过 $1000 的资金
if (amount > 1000)
{
if (ContextUtil.IsCallerInRole("Manager"))
{
// 调用方已授权
}
else
{
// 调用方未授权
}
}
}
下面的示例说明如何从服务组件中标识所有上游调用方。
[ComponentAccessControl]
public class MyServicedComponent : ServicedComponent
{
public void ShowCallers()
{
SecurityCallContext context = SecurityCallContext.CurrentCall;
SecurityCallers callers = context.Callers;
foreach(SecurityIdentity id in callers)
{
Console.WriteLine(id.AccountName);
}
}
}
注意:可通过 SecurityCallContext.OriginalCaller 属性提供原调用方的标识。
服务器激活的企业服务应用程序在 Dllhost.exe 进程的一个实例中运行。必须使用“组件服务”工具配置用于在 COM+ 目录中运行该进程的帐户。
注意:不能使用 .NET 属性来指定运行方式标识。
不要使用以交互方式登录的用户的标识来运行服务器应用程序(这是默认设置)。由于以下两个主要原因,需要避免出现这种情况:
| • | 取决于当前在服务器上以交互方式登录的用户身份,应用程序的特权和访问权限将会发生变化。如果是管理员进行登录,则该应用程序将具有管理员权限。 |
| • | 如果在某个用户以交互方式登录时启动应用程序,则在该用户注销后,将会关闭该服务器应用程序。在另一个用户以交互方式登录之前,无法重新启动该应用程序。 |
由于交互式用户设置供开发人员在开发时使用,因此不应将其作为部署设置。
创建权限最少的帐户可以减少与进程攻击相关的威胁。如果某个顽固的攻击者设法破坏了服务器进程,那么这个攻击者就可以轻而易举地继承向进程帐户授予的特权和访问权限。配置为最少权限的帐户限制了可能造成的潜在危害。
如果您需要使用进程帐户访问网络资源,那么远程计算机必须能够对该进程帐户进行身份验证。在这个情况下,您有两个选择:
| • | 如果两台计算机位于同一个域或者信任域中,则可以使用域帐户。 |
| • | 您可以使用本地帐户,然后在远程计算机上创建重复帐户(具有相同的用户名和密码)。在选择这种方法时,您必须确保将两个帐户的密码保持同步。 |
服务组件可能需要访问远程资源。一定要标识以下各项内容:
| • | 组件需要访问的资源。例如,文件共享上的文件、数据库、其他 DCOM 服务器和 Active Directory® 目录服务对象等等。 |
| • | 用来执行资源访问的标识。如果服务组件访问远程资源,那么远程计算机必须能够对所使用的标识(默认情况下为进程标识)进行身份验证。 |
注意:有关访问远程 SQL Server 数据库的信息,请参见第 12 章数据访问安全性。
您可以使用以下任何标识从企业服务应用程序的组件中访问远程资源:
| • | 原调用方(如果您正在使用 CoImpersonateClient 明确地进行模拟) |
| • | 当前进程标识(在服务器应用程序的 COM+ 目录中配置的) |
| • | 特定的服务帐户 |
若要使用原调用方的标识访问远程资源,您必须:
| • | 通过调用 CoImpersonateClient 以编程方式模拟原调用方。 |
| • | 可以将调用方的安全性上下文从驻留企业服务应用程序的应用程序服务器委派到远程计算机。这假设您正在企业服务应用程序和客户端应用程序之间使用 Kerberos 身份验证。 |
可伸缩性警告:如果您使用原调用方的模拟标识来访问应用程序的数据服务层,则会严重影响应用程序的可伸缩性,因为这会影响数据库连接池的工作效率。由于将每个数据库连接的安全性上下文绑定到多个单独的调用方,因此数据库连接池无法有效地进行工作。
更多信息
有关模拟调用方的详细信息,请参见本章后面的传送原调用方。
如果将应用程序配置为作为服务器应用程序运行,则可以使用已配置的进程标识来访问远程资源(这是默认方式)。
如果要使用服务器进程帐户来访问远程资源,则必须执行以下操作之一:
| • | 使用权限最少的域帐户运行服务器应用程序。这是假设客户端计算机和服务器计算机都位于同一个域或信任域内。 |
| • | 在远程计算机上使用相同的用户名和密码复制进程帐户。 |
如果您主要关心管理简便性问题,则应该使用权限最少的域帐户。
如果将应用程序配置为作为库应用程序运行,则从主机进程(它通常是基于 Web 的应用程序)继承进程标识。有关使用 ASP.NET 进程标识访问远程资源的详细信息,请参见第 8 章 ASP.NET 安全性。
企业服务应用程序可以使用专门配置的服务帐户(即,非用户 Windows 帐户)来访问远程资源。但是,由于这种方法依赖于调用 LogonUser API,因此在 Windows 2000 中不推荐使用这种方法。因为帐户凭据必须被安全存储,所以该方法还会造成应用程序的凭据管理问题。
应避免在 Windows 2000 上使用 LogonUser,因为它会给企业服务进程帐户授予“作为操作系统的一部分”这一强大权限。
注意:Microsoft Windows Server 2003 将取消这个限制。
默认情况下,由服务组件发出的传出调用(例如,为访问本地或远程资源)是使用从主机进程获得的安全性上下文实现的。对于服务器应用程序而言,这是配置的运行方式标识。对于库应用程序而言,这是(主机)客户端进程的标识(例如,当客户端是 ASP.NET Web 应用程序时,客户端进程为 Aspnet_wp.exe)。
| • | 通过企业服务应用程序传送原调用方的上下文
|
注意:原调用方的标识自动传递给企业服务应用程序,并且可通过使用 SecurityCallContext.OriginalCaller 获得该标识。该标识可用于审核目的。
CoImpersonateClient (和 CoRevertToSelf)位于 OLE32.dll 中。必须使用 DllImport 属性导入它们的定义,以便通过 P/Invoke 调用它们。下面的代码片段说明了这一点。
class COMSec
{
[DllImport("OLE32.DLL", CharSet=CharSet.Auto)]
public static extern uint CoImpersonateClient();
[DllImport("OLE32.DLL", CharSet=CharSet.Auto)]
public static extern uint CoRevertToSelf();
}
. . .
void SomeMethod()
{
// 为了传递原调用方的安全性上下文并将它用于访问本地
// 或远程资源,启动模拟
COMSec.CoImpersonateClient();
// 作为调用方执行操作
// 此处的代码使用调用方的上下文 - 而不是进程的上下文
. . .
COMSec.CoRevertToSelf();
// 此处的代码转为使用进程上下文
}
有关如何配置完整的 Kerberos 委派方案(说明如何通过 ASP.NET Web 应用程序、企业服务应用程序将原调用方的安全性上下文传送到数据库)的详细信息,请参见第 5 章 Intranet 安全性的将原调用方传递到数据库一节。
若要保护通过 DCOM 从客户端应用程序发送到远程服务组件的数据的安全,请在客户端和服务器之间使用 RPC 数据包保密性身份验证级别。这可以提供消息的机密性和完整性。
您必须在客户端和服务器配置身份验证级别。
若要配置 ASP.NET(其中,客户端是 ASP.NET Web 应用程序),请将 machine.config 中的 <processModel> 元素上的 comAuthenticationLevel 属性设置为 PktPrivacy。
若要配置企业服务服务器应用程序,请使用“组件服务”工具或服务组件程序集中的以下 .NET 属性来设置应用程序级身份验证级别。
[assembly: ApplicationAccessControl(
Authentication = AuthenticationOption.Privacy)]
| • | 有关配置安全性(包括身份验证级别)的详细信息,请参见本章前面的配置安全性。 |
| • | 有关 RPC/DCOM 身份验证级别的详细信息,请参见本章后面的身份验证。 |
| • | 有关身份验证级别协商的详细信息,请参见本章后面的身份验证级别协商。 |
有关说明如何构建服务组件的分步指导,请参见本指南的如何将基于角色的安全性用于企业服务。
在重新构建服务组件时,如果 DLL 被锁定,请执行以下操作:
| • | 使用组件服务关闭 COM+ 服务器应用程序。。 |
| • | 如果您正在开发库应用程序,则仍可以将该应用程序加载到 Aspnet_wp.exe 进程中。在命令提示符下运行 IISReset ,或者使用任务管理器停止 Aspnet_wp.exe 进程。 |
| • | 使用 www.sysinternals.com 中提供的 FileMon.exe 工具帮助解决文件锁定问题。 |
在创建新的项目时,Microsoft Visual Studio® .NET 开发系统生成默认的 AssemblyVersion 属性(如下所示)。
[assembly: AssemblyVersion("1.0.*")]
每次重建项目时,都会生成一个新的程序集版本。这还导致生成用于标识服务组件类的新类标识符 (CLSID)。如果使用 Regsvcs.exe 重复注册包含组件服务的程序集,就会看到在“组件”文件夹下面列出具有不同 CLSID 的重复组件(严格说来是类)。
尽管这符合严格的 COM 版本控制语义并且可防止中断现有的托管和未托管客户端,但它可能会给开发工作带来麻烦。
在测试和开发期间,请考虑使用如下所示的程序集级别的 AssemblyVersion 属性设置显式版本。
[assembly: AssemblyVersion("1.0.0.1")]
该设置可防止给每个后续项目版本生成新的 CLSID。您可能还需要固定接口标识符 (IID)。如果类实现显式的接口,则可以使用如下所示的 GUID 属性来固定给定接口的 IID。
[Guid("E1FBF27E-9F11-474d-8DF6-58916F798E9D")]
public interface IMyInterface
{
}
| • | 生成新的 GUID
|
重要说明:在部署用于测试和生产的服务组件程序集之前,请删除任何固定的 GUID 并恢复到自动程序集版本控制机制(例如,通过使用“1.0.*”)。如果无法完成上述操作,就会增加新的组件版本中断现有客户端的可能性。
更多信息
有关用于部署的版本控制的详细信息,请参见 MSDN 上的 Understanding Enterprise Services (COM+) in .NET(理解 .NET 中的企业服务 (COM+))。
如果发现 IRoleSecurity 接口的 QueryInterface 调用失败,这表明您已更新了程序集中的接口定义,但是尚未使用 Regsvcs.exe 重新在组件服务中注册程序集。
重要说明:每次运行 Regsvcs.exe 时,您都需要重新配置服务器应用程序的运行方式标识,并且还需要重新将用户添加到组中。您可以编写一个简单的脚本来自动执行此任务。
在 Windows 2000(SP3 或 QFE 18.1)或 Windows Server 2003 中,可以将企业服务应用程序配置为使用静态终结点。如果防火墙将客户端与服务器隔开,则只需要在防火墙中打开两个端口。具体而言,您必须为 RPC 打开端口 135,并为企业服务应用程序打开一个端口。
作为此方法的替代方法,可以考虑将企业服务应用程序作为 Web 服务进行公开。这样,您就可以在端口 80 上使用 SOAP 激活和调用服务组件。但是,这种方法的主要问题是不允许用户将事务上下文从客户端传递到服务器。您需要在远程服务组件中启动事务。
有关详细信息,请参见下列知识库文章:
| • | 文章 Q312960:Cannot Set a Fixed Endpoint for a COM+ Application(无法为 COM+ 应用程序设置固定的终结点) |
| • | 文章 Q259011:SAMPLE: A Simple DCOM Client Server Test Application(示例:一个简单的 DCOM 客户服务器测试应用程序) |
| • | 文章 Q248809:PRB: DCOM Does Not Work over Network Address Translation-Based Firewall(PRB:DCOM 无法在基于网络地址转换的防火墙上工作) |
| • | 文章 Q250367:INFO:Configuring Microsoft Distributed Transaction Coordinator (DTC) to Work Through a Firewall(INFO:将 Microsoft 分布式事务处理协调器 (DTC) 配置为通过防火墙工作) |
| • | 文章 Q154596:HOWTO: Configure RPC Dynamic Port Allocation to Work with Firewall(HOWTO:将 RPC 动态端口分配配置为用于防火墙) |
本节着重说明在 ASP.NET 应用程序调用服务组件时将遇到的主要问题。
在从 ASP.NET 应用程序调用服务组件时,就会从该应用程序的 Win32® 线程标识中获取调用的安全性标识。如果将 Web 应用程序配置为模拟调用方,则它就是调用方的标识。否则,它就是 ASP.NET 进程标识(默认情况下为 ASPNET)。
可通过调用 WindowsIdentity.GetCurrent(),从 ASP.NET 应用程序中检索当前的 Win32 线程标识。
可以使用 SecurityCallContext.OriginalCaller 从服务组件中检索原调用方的标识。
若要在企业服务应用程序中启用有意义的基于角色的安全性,您必须使用 Windows 身份验证并启用模拟功能。这样,就可以确保服务组件能够对原调用方进行身份验证,并根据原调用方的标识做出授权决定。
DCOM 身份验证级别是在客户端(例如,基于 Web 的应用程序)和服务器(企业服务应用程序)之间协商确定的。请使用两个安全设置中较高的一个。
可以使用 Machine.config 的 <processModel> 元素中的 comAuthenitcation 属性来配置 ASP.NET 身份验证级别。
模拟级别是由客户端(例如,基于 Web 的应用程序)控制的。客户端可以确定允许服务器使用的模拟级别。
可以使用 Machine.config 的 <processModel> 元素中的 comImpersonationLevel 属性来配置 ASP.NET 模拟级别(对于所有的传出 DCOM 调用)。
通常,应用于各个接口代理的安全设置是从默认的进程级安全设置中获取的。在 ASP.NET 中,默认的安全设置(例如,模拟级别和身份验证级别)是在 Machine.config 中配置的(如上所述)。
如有必要,可以修改各个接口代理所使用的安全设置。例如,如果 ASP.NET 应用程序与公开两个接口的服务组件进行通信,并且只通过一个接口传递机密数据,则可以选择只在机密接口上使用数据包保密性身份验证级别提供的加密支持,而在另一个接口上使用数据包身份验证。这样,您就不会在两个接口上都遇到与加密有关的性能下降问题。
总之,将应用于某个接口代理的一组安全设置称为“安全网”。COM 提供以下函数来查询和操作单个接口代理上的安全网设置:
| • | CoQueryProxyBlanket |
| • | CoSetProxyBlanket |
| • | CoCopyProxy |
必须使用 P/Invoke 从 ASP.NET Web 应用程序(DCOM 客户端)调用这些函数。下面的代码说明如何配置特定接口以使用数据包保密性身份验证级别(该级别提供加密功能)。可以在与远程服务组件通信的 ASP.NET Web 应用程序中使用该代码。
// 为针对 CoSetProxyBlanket 的 P/Invoke 调用定义一个包装类
class COMSec
{
// 调用 CoSetProxyBlanket 所需的常量
public const uint RPC_C_AUTHN_DEFAULT = 0xFFFFFFFF;
public const uint RPC_C_AUTHZ_DEFAULT = 0xFFFFFFFF;
public const uint RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6;
public const uint RPC_C_IMP_LEVEL_DEFAULT = 0;
public const uint COLE_DEFAULT_AUTHINFO = 0xFFFFFFFF;
public const uint COLE_DEFAULT_PRINCIPAL = 0;
public const uint EOAC_DEFAULT = 0x800;
// HRESULT CoSetProxyBlanket( IUnknown * pProxy,
// DWORD dwAuthnSvc,
// DWORD dwAuthzSvc,
// WCHAR * pServerPrincName,
// DWORD dwAuthnLevel,
// DWORD dwImpLevel,
// RPC_AUTH_IDENTITY_HANDLE pAuthInfo,
// DWORD dwCapabilities );
[DllImport("OLE32.DLL", CharSet=CharSet.Auto)]
public unsafe static extern uint CoSetProxyBlanket(
IntPtr pProxy,
uint dwAuthnSvc,
uint dwAuthzSvc,
IntPtr pServerPrincName,
uint dwAuthnLevel,
uint dwImpLevel,
IntPtr pAuthInfo,
uint dwCapababilities);
} // end class COMSec
// 调用 CoSetProxyBlanket 的代码
void CallComponent()
{
// 这是要配置的接口
Guid IID_ISecureInterface = new Guid("c720ff19-bec1-352c-bb4b-e2de10b858ba");
IntPtr pISecureInterface;
// 实例化服务组件
CreditCardComponent comp = new CreditCardComponent();
// 获取其 IUnknown 指针
IntPtr pIUnk = Marshal.GetIUnknownForObject(comp);
// 获取要配置的接口
Marshal.QueryInterface(pIUnk, ref IID_ISecureInterface,
out pISecureInterface);
try
{
// 配置接口代理并设置数据包保密性身份验证
uint hr = COMSec.CoSetProxyBlanket( pISecureInterface,
COMSec.RPC_C_AUTHN_DEFAULT,
COMSec.RPC_C_AUTHZ_DEFAULT,
IntPtr.Zero,
COMSec.RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
COMSec.RPC_C_IMP_LEVEL_DEFAULT,
IntPtr.Zero,
COMSec.EOAC_DEFAULT );
ISecureInterface secure = (ISecureInterface)comp;
// 由于 ISecureInterface 为数据包保密性身份验证进行了配置,将加密
// 以下调用。其他接口使用进程
// 级别默认值(通常为数据包身份验证)。
secure.ValidateCreditCard("123456789");
}
catch (Exception ex)
{
}
}
更多信息
| • | 有关配置 ASP.NET 客户端应用程序以调用服务组件的详细信息,请参见本章前面的配置 ASP.NET 客户端应用程序。 |
| • | 有关 DCOM 身份验证级别的详细信息,请参见本章后面的身份验证。 |
| • | 有关 DCOM 模拟级别的详细信息,请参见本章后面的模拟。 |
| • | 有关在基于 Web 的应用程序中使用 Windows 身份验证和启用模拟功能的详细信息,请参见第 8 章 ASP.NET 安全性。 |
本节概要介绍了企业服务安全性的概念。如果您具有使用 COM+ 方面的经验,那么您对许多概念都很熟悉。
有关企业服务的背景信息,请参见 MSDN 文章理解 .NET 中的企业服务 (COM+)。
以下概述了应该理解的重要安全性概念:
| • | 服务组件和企业服务应用程序的安全设置保存在 COM+ 目录中。大多数设置可以使用 .NET 属性来进行配置。所有设置都可以使用“组件服务”管理工具或 Microsoft Visual Basic® Scripting Edition 开发系统脚本来进行配置。 | ||||||
| • | 授权是由企业服务 (COM+) 角色提供的,这些角色可以包含 Windows 组或用户帐户。它们与 .NET 角色不同。
| ||||||
| • | 企业服务应用程序中基于角色的有效授权依赖于调用服务组件使用的 Windows 标识。
| ||||||
| • | 服务组件可以在服务器或库应用程序中运行。
| ||||||
| • | 身份验证是由 DCOM 和 RPC 的基本服务提供的。客户端和服务器的身份验证级别共同确定了与服务组件通信使用的身份验证级别。 | ||||||
| • | 模拟是在客户端应用程序中配置的。它决定了服务器的模拟性能。 |
企业服务 (COM+) 角色用于表示在应用程序中共享相同安全性权限的常用用户类别。虽然在概念上与 .NET 角色相似,但它们是完全独立的。
企业服务 (COM+) 角色包含 Windows 用户和组帐户(与包含任意非 Windows 用户标识的 .NET 角色不同)。因此,对于使用 Windows 身份验证和模拟的应用程序而言,企业服务 (COM+) 角色是唯一有效的授权机制(以便将调用方的安全性上下文传递到企业服务应用程序)。
表 9.1:比较企业服务 (COM+) 角色和 .NET 角色
| 功能 | 企业服务 (COM+) 角色 | .NET 角色 |
管理 | 组件服务 | 自定义 |
数据存储 | COM+ 目录 | 自定义数据存储(如 SQL Server 或 Active Directory) |
声明方式 | 是 | 是 |
强制方式 | 是 | 是 |
类、接口和方法级别的细化程度 | 是 | 是 |
可扩展 | 否 | 是 |
可供所有 .NET 组件使用 | 只供派生自 ServicedComponent 基类的组件使用 | 是 |
角色成员身份 | 角色包含 Windows 组或用户帐户 | 当使用 WindowsPrincipals 时,角色是 Windows 组 没有额外的抽象级别 |
需要显式接口实现 | 是 | 否 |
因为企业服务依赖于 COM+ 和 DCOM/RPC 提供的基本体系结构,所以企业服务应用程序使用的身份验证级别设置是由 RPC 定义的(并由 DCOM 使用的)那些设置。
表 9.2:企业服务应用程序身份验证设置
| 身份验证级别 | 说明 |
默认 | 利用常规的协商规则选择身份验证级别 |
无 | 无身份验证 |
连接 | 仅当客户端初次连接到服务器时对凭据进行身份验证 |
调用 | 在每个远程过程调用开始时进行身份验证 |
数据包 | 对从客户端收到的所有数据进行身份验证 |
数据包完整性 | 对所有数据进行身份验证,并确认没有对传送的数据进行修改 |
数据包保密性 | 对所有数据进行身份验证,并对每个远程过程调用的参数状态进行加密 |
您应该知道某些身份验证级别升级时并不给出提示。例如:
| • | 如果使用用户数据协议 (UDP) 数据报传输,则将“连接”和“调用”级别升级为“数据包”,因为前面提到的身份验证级别只适用于 TCP 等面向连接的传输。 注意:Windows 2000 默认在 DCOM 通信上使用 TCP 上的 RPC。 |
| • | 对于单个计算机上的进程间调用,始终将所有身份验证级别升级为“数据包保密性”。但是,在单个计算机方案中,不会对数据进行加密以获得机密性(因为不会通过网络传送数据)。 |
企业服务用来对客户端进行身份验证的身份验证级别是由两个设置确定的:
| • | 进程级身份验证级别。对于服务器激活的应用程序(在 Dllhost.exe 中运行),身份验证级别是在 COM+ 目录中配置的。 |
| • | 客户端身份验证级别。与服务组件通信的客户端进程的已配置身份验证级别还会影响所使用的身份验证级别。 ASP.NET Web 应用程序的默认身份验证级别是由 Machine.config 中 <processModel> 元素上的 comAuthenticationLevel 属性定义的。 |
始终选择两个(客户端和服务器)身份验证级别中较高的一个级别。图 9.4 中说明了这种情况。

图 9.4
身份验证级别协商
更多信息
有关如何为企业服务应用程序配置身份验证级别的详细信息,请参见本章前面的配置安全性。
为企业服务应用程序定义的模拟级别决定了应用程序中服务组件发出的所有传出 DCOM 调用所使用的模拟级别。
重要说明:它不能决定应用程序中的服务组件是否模拟它们的调用方。默认情况下,服务组件不模拟调用方。若要模拟调用方,服务组件必须调用 CoImpersonateClient。有关说明,请参见本章前面的传送原调用方。
模拟是客户端的设置。因为它允许客户端限制服务器的模拟功能,所以可以为客户端提供一定程度的保护。
表 9.3:可用的模拟级别
| 模拟级别 | 说明 |
标识 | 允许服务器标识客户端并使用客户端的访问标志执行访问检查 |
模拟 | 允许服务器使用客户端的凭据访问本地资源 |
委派 | 允许服务器使用客户端的凭据访问远程资源(这需要使用 Kerberos 和特定帐户配置) |
基于 Web 的应用程序在与服务组件(或任何使用 DCOM 的组件)通信时所使用的默认模拟级别是由 Machine.config 中 <processModel> 元素上的 comImpersonationLevel 属性确定的。
掩盖精确地确定在模拟过程中是如何将客户端标识通过 COM 对象代理映射到服务器上。可以使用两种类型的掩盖:
| • | 动态掩盖。企业服务服务器应用程序使用动态掩盖(这是不能进行配置的)。库应用程序的掩盖是由主机进程确定的,例如 ASP.NET 辅助进程 (Aspnet_wp.exe)。基于 Web 的应用程序也使用动态掩盖(这也是不能进行配置的)。 动态掩盖导致在模拟过程中使用线程模拟标志来代表客户端的标识。这意味着,如果在服务组件中调用 CoImpersonateClient ,那么同一方法后来发出的传出调用就会使用客户端的标识,直到调用 CoRevertToSelf 或该方法结束(隐式调用 CoRevertToSelf)时为止。 |
| • | 静态掩盖。通过使用静态掩盖,服务器可以了解从客户端到服务器的第一次调用中使用的凭据(与传出调用过程中线程是否使用模拟功能无关)。 |
更多信息
| • | 有关如何为企业服务应用程序配置模拟级别的详细信息,请参见本章前面的配置安全性。 |
| • | 有关掩盖的详细信息,请参见 MSDN 中关于 Cloaking(掩盖)的 Platform SDK 信息。 |
本章介绍如何在企业服务应用程序中构建安全的服务组件。您还了解到如何配置调用服务组件的 ASP.NET Web 客户端应用程序。概括起来,有以下几点:
| • | 使用服务器激活的企业服务应用程序来提高安全性。增加的进程跃点也提高了安全性。 |
| • | 使用权限最少的本地帐户来运行服务器应用程序。 |
| • | 如果需要确保在客户端应用程序和服务组件之间通过网络安全发送数据,请使用数据包保密性级别身份验证(必须在服务器和客户端对该身份验证进行配置)。 |
| • | 为有意义的基于角色的安全性实现启用组件级访问检查。 |
| • | 在调用企业服务应用程序中依赖基于角色的安全性的组件之前,在 ASP.NET Web 应用程序中使用 Windows 身份验证并启用模拟功能。 |
| • | 将受保护的网关类用作企业服务应用程序的入口点。 通过减少给客户端提供进入企业服务应用程序的入口点的网关类的数量,您可以减少需要分配角色的类的数量。应该给其他内部 helper 类启用基于角色的检查,但不要给它们分配角色。这意味着,外部客户端无法直接调用它们,而同一应用程序中的网关类则可以直接访问它们。 |
| • | 在以编程方式检查角色成员身份之前,请直接调用 IsSecurityEnabled。 |
| • | 应该避免在中间层使用模拟功能,因为这会影响数据库连接池的使用效率,并显著降低应用程序的可伸缩性。 |
| • | 将 Windows 组添加到企业服务 (COM+) 角色中以提高灵活性和简化管理。 |