COM+ 和 .NET - 实用的方法 - 第二部分






4.50/5 (12投票s)
2004 年 3 月 3 日
13分钟阅读

73888
审视 COM+ 和 .NET
(c) 应用安全性
COM+ 和基于角色的安全是完全集成的。这两项功能都允许管理员创建用户组,这些组即为角色,并为这些角色分配关于 COM+ 组件和方法的安全权限。所有这些过程都可以在 COM+ MMC 中轻松完成。
基于角色的安全可用于强制执行数据库操作(插入、更新、删除和选择)的安全检查和授权。为了使用基于角色的安全,您需要分层构建应用程序。在创建数据层时,您需要创建实现包含插入、更新、删除和选择功能的接口的类。这样做可以使管理员创建诸如经理、用户、销售或其他角色,并将角色分配给这些类的数据库接口函数。管理员可以添加或删除用户到组,并对数据库操作施加授权。
可以从基于 Windows 身份验证、模拟和设置为交互式用户身份的 Web 应用程序调用的库应用程序和服务器应用程序,可以使用 ContextUtil.IsUserInRole()
函数来检查当前用户是否存在于给定的角色中。确定用户角色后,我们可以使用该数据对数据库进行操作,以强制执行数据隔离。每个数据库和组织都有自己实现数据隔离的方法。一种常见且易于使用的方法是为每一行添加隔离字母或使用位字段,并在选择数据时使用它。或者,可以调用不同的存储过程,以便每个组的成员都能从数据库中获取不同的数据集。
正如您可能已经发现的,您可以使用属性来为应用程序、组件和方法设置 COM+ 基于角色的安全和角色(ApplicationAccessControlAttribute
、ComponentAccessControlAttribute
、SecureMethodAttribute
)。您可以使用 SecurityRoleAttribute
属性来设置应用程序、组件或方法的角色或角色(您需要为每个角色使用该属性)。使用此属性将添加角色,但不会将用户添加到角色。将用户添加到角色必须由管理员执行,除非 SecurityRoleAttribute
使用其第二个构造函数参数设置为 true。使用该构造函数会将所有人分组为角色用户。
让我们将所有这些信息汇总到我们应用程序的一个示例页面中。我们的页面(ContractsLogic.aspx)需要显示一个包含公司合同列表的 ArrayList 中的数据。合同数据应在经理(可以看到所有列表)和工人(只能看到特定合同)之间进行隔离。该应用程序是利用分层应用程序范例构建的。页面类调用 ContractsLogic 类,该类调用 ContractsData 以从数据库获取数据,并将 ArrayList 返回给 ContractsLogic。ContractsLogic 实现安全要求,并将包含每个用户数据的 ArrayList 返回给绑定 ArrayList 到 Grid 的页面。
图 2.0
页面类调用设置为 COM+ 库应用程序的 ContractsLogic
类,以获取数据并将其绑定到 DataGrid
。
private void Page_Load(object sender, System.EventArgs e)
{
try
{
IContractsLogic oLogic = new Contracts.ContractsLogic();
this.DataGrid1.DataSource = oLogic.GetByUser ();
DataGrid1.DataBind();
}
catch (Exception err)
{
string s = err.Message ;
}
}
为了在程序集类中使用安全性,我们必须使用
ApplicationAccessControl
属性。using System;
using System.EnterpriseServices;
using DemoInterfaces;
//[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl]
我们的下一步是使用 ComponentAccessControlAttribute
来设置类的安全访问,并使用 SecurityRoleAttribute
来添加用户和经理角色。我们使用 SecurityRoleAttribute
构造函数,它允许我们将所有人分组为用户角色。
[ComponentAccessControlAttribute(true), SecurityRoleAttribute ("users",true),
SecurityRoleAttribute ("managers")]
public class ContractsLogic : ServicedComponent ,IContractsLogic {
这里有趣的部分是 IContractsLogic
接口的 GetByUser
实现。为了启用方法接口的安全设置,应使用接口。尽管我们不打算实现方法安全,但我们使用它们是为了让管理员可以选择授权数据库操作。接口应位于 GAC 中的单独程序集中,以便 Web 应用程序和 COM+ 服务器应用程序可以共享接口。GetByUser 调用 ContractsData 来获取合同数据。对于此演示,ContractsData 仅创建一个 ArrayList,但其原始目的是从数据库检索数据并应用隔离。在此演示中,通过检查用户是否在角色中并使用按位运算获取正确数据来完成隔离。
System.Collections.ArrayList IContractsLogic.GetByUser()
{
IHandleData oData = new ContractsData();
System.Collections.ArrayList oArr = oData.Get();
System.Collections.ArrayList oRetArr =
new System.Collections.ArrayList();
if (ContextUtil.IsCallerInRole("users") )
{
System.Collections.IEnumerator oEnum = oArr.GetEnumerator ();
while (oEnum.MoveNext ())
{
if ((((Contracts)oEnum.Current).Comp & 2) == 2 )
oRetArr.Add(((Contracts)oEnum.Current).ContractName );
}
}
if (ContextUtil.IsCallerInRole("managers") )
{
System.Collections.IEnumerator oEnum = oArr.GetEnumerator ();
while (oEnum.MoveNext ())
{
if ((((Contracts)oEnum.Current).Comp & 1) == 1 )
oRetArr.Add(((Contracts)oEnum.Current).ContractName );
}
}
return oRetArr;
}
首次运行演示将创建带有角色的 COM+ 应用程序。您将只能看到部分数据,因为我们将所有人分组为用户角色。现在,使用 COM+ MMC 将所有人添加到经理角色,然后刷新页面。这次您将看到所有合同,因为您的用户现在是经理的一部分。虽然它与 COM+ 没有直接关联,但我只想提一下,使用基于角色的安全可以通过操作视觉控件来实现数据隔离。ASP.NET 函数 IsUserInRole 可用于确定用户角色,并且可以根据该确定使控件可见或不可见。ASP.NET 通过 .Net 对基于角色的安全的实现来完成这项工作。.Net 机制更具灵活性,因为您可以使用它来处理非 Windows 帐户的用户,它更集成到 .Net 组件(函数、接口、属性、类)中,并且不仅可以在运行时强制执行。请注意,.NET Framework 和 COM+ 基于角色的安全机制是独立的,您只能在单个应用程序中使用一种机制。
(d) 提高服务器输出和应用程序收入。
.Net 的异步机制要求调用者和被调用对象在异步调用发生时都处于活动状态。有时,如果被调用对象因某种原因不可用,这种行为可能导致我们的应用程序运行不稳定。即使我们捕获了这种情况并通知用户服务不可用,仍然会造成经济损失。为了克服这种情况,我们可以保存请求详细信息并稍后处理。但我们需要找到一种机制来确保缓存请求的可用性,直到应用程序成功处理用户请求。
您可以使用 COM+ 排队组件 (QC) 而不是实现此类机制。QC 代表您使用组件名称、要激活的方法和参数调用 MSMQ 队列。结果,COM+ 将在请求服务器上激活请求组件。将消息写入 MSMQ 使我们能够异步调用组件,因为调用者和被调用对象之间没有连接。激活对象数据的请求存在于 MSMQ 队列中,并且只要 COM+ 未激活请求的 COM+ 组件,它就会一直保留在那里。将消息写入 MSMQ 还确保消息在从队列中检索消息之前是持久的。
让我们看看 COM+ QC 如何在实际场景中帮助我们。假设您正在构建一个允许用户购买商品的电子商务应用程序。用户选择商品,然后将其提交到服务器进行处理,以及信用卡详细信息。服务器开始一个长流程,从信用卡验证开始,到通知客户商品已发货结束。订单处理过程当然包含几个组件:信用卡验证、商品存在性验证、信用卡扣款、将订单请求发送到仓库并告知客户商品发货。这是一个漫长的过程,有几个潜在的故障点。这个过程可以分为两部分:前端和后端。前端负责接收用户请求、确保商品存在、验证信用卡和扣款。后端负责其他订单处理任务。分离后端任务组件的共存是显而易见的。每个任务都需要自己的时间,导致在给定任务结束时需要调用下一个处理组件。将后端组件注册为 QC 可确保即使其中一个任务组件暂时不可用,该过程也能继续工作。
前端组件可以通过两种方式处理。它们可以按顺序处理,直到信用卡扣款,或者也可以分成不相关的组件。选择第二种方法具有优势,正如您将看到的。扣款组件需要调用信用卡公司来处理信用卡扣款。这个过程通常涉及与信用卡公司的网络调用,这可能导致很长的时间和信用卡公司服务的不可用。长时间的响应时间可能导致服务器能够处理的请求减少。此外,信用卡公司不可用将使服务器无法满足客户请求。因此,分离信用卡扣款组件并将其注册为 COM+ QC 可以解决许多问题并提高服务器的生产力。我们的 ASP.NET 应用程序将接收请求并验证信用卡。然后,将通过 MSMQ 将请求继续处理的请求发送到 QC 扣款组件,并且客户端将收到通知,告知其请求已被接收并正在处理。当扣款组件完成工作后,将向客户发送电子邮件通知,并将请求发送到下一个 QC 组件以继续处理。为了看到该理论的实际效果,我们将构建一个新的页面和类来演示这样的电子商务应用程序。我们将仅构建前端组件,并运行与将扣款组件作为 QC 组件和普通组件的示例,收集 RPS 并观察结果。图 3.0 显示了我们电子商务应用程序的总体设计。请注意,选项“A”将扣款组件注册为 QC。
图 3.0
我们将向 AvailabilityCheck Web 项目添加两个页面(BuyQC.aspx、BuyNoQC.aspx)来模拟有或没有排队组件的扣款操作。BuyNoQC.aspx 将调用我添加到 AvailabilityCheckDll 库的新类(MyClass),其中有一个函数通过挂起线程 1 秒来模拟扣款操作。更有趣的部分是 BuyQC.aspx,它调用新的程序集(QCClass)和类(MyClass),其中包含与我添加到 AvailabilityCheckDll 库中的函数相同的函数。MyClass 将被注册为排队组件,因为在 AssemblyInfo 文件中使用了 ApplicationQueuing 属性,该属性设置为启用排队和侦听。
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationQueuing(Enabled = true,QueueListenerEnabled = true)]
[assembly: ApplicationName("QCDemoSrv")]
[assembly: AssemblyTitle("")]
第二步是创建一个接口并用 InterfaceQueuing
属性标记它以启用排队支持。排队组件和调用页面都将使用该接口作为它们的调用契约。在设计将用于排队组件的接口时,请记住不要使用 out 或 ref 参数,因为 QC 的异步工作模式。
[InterfaceQueuing]
public interface IMyInterface
{
void CreditDebit(string CardNumber, double amount);
};
最后,我们构建一个将继承自 ComponentServices
并实现 IMyInterface
接口的类。请注意,这是一个常规类,无需执行任何操作即可将其用作排队组件。我只是为该类添加了安全属性,以使其能够与 CLR 1.1 注册的 COM+ 服务器应用程序的默认安全启用一起工作。
[ComponentAccessControl(true), SecurityRoleAttribute("users",true)]
public class MyClass : ServicedComponent,IMyInterface
{
public MyClass()
{}
public void CreditDebit(string CardNumber, double amount)
{
System.Threading.Thread.Sleep (1000);
// Optional – write to eventLog
//System.Diagnostics.EventLog.WriteEntry ("QC check","Debit finished");
MessageBox.Show("Debit finished","Queued Component");
return;
}
}
为了使用我们的 COM+ 应用程序的延迟注册,我们在 Application_OnStart
事件中实例化了该类。这些代码行使我们无需手动从 COM+ MMC 启动 COM+ 应用程序。
protected void Application_Start(Object sender, EventArgs e)
{
QCAssembly.MyClass o = new QCAssembly .MyClass ();
System .EnterpriseServices .ServicedComponent .DisposeObject (o);
}
BuyQC.aspx 通过使用 Marshal 类的 BindToMoniker 函数来调用排队组件类。Moniker 充当唯一标识 COM 对象的名称。您可以在 MSDN 上找到关于排队组件 Moniker 语法的良好解释(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cossdk/htm/pgservices_queuedcomponents_786q.asp)。我使用该语法从本地队列创建对象,但您可以使用该语法调用远程队列。在获取对象并将其转换为 IMyInterface
接口后,我们可以使用接口方法并从 COM+ 中释放对象。
private void Page_Load(object sender, System.EventArgs e)
{
IMyInterface myObj;
myObj=(IMyInterface)System.Runtime.InteropServices.Marshal.
BindToMoniker("queue:/new:QCAssembly.MyClass");
myObj.CreditDebit ("423423423423",10.0);
System.Runtime.InteropServices.Marshal.ReleaseComObject(myObj);
Response.Write("Order received and processed");
}
此页面仅使用排队组件在组件队列中生成新消息,并返回已完成处理的指示。当排队组件侦听器在组件队列中找到新消息时,COM+ 将激活并使用给定参数运行给定函数,最终弹出消息框。
下方(表 2.0)可以看到使用 ACT 对 5、10、50 个并发用户运行 BuyQC.aspx 和 BuyNoQC.aspx 的结果。正如您所见,当执行的任务需要 1 秒时,排队组件场景至少快 17 倍。
表 2.0 | ||
模拟用户数量 |
BuyNoQC.aspx |
BuyQC.aspx |
5 |
5 (RPS) |
212 (RPS) |
10 |
10 (RPS) |
216 (RPS) |
50 |
12 (RPS) |
211 (RPS) |
MQ 组件可以标记为事务性组件,以确保组件请求在函数完成其工作并成功提高事务标志之前不会从队列中移除。
(e) 监控和优化许可证使用
一个常见的问题,尤其是在企业内部,是许可证的使用。IT 部门使用的大多数第三方组件都基于许可协议,通常对并发许可证使用有限制(例如 ESRI 产品)。我们的 IT 部门需要确保许可证策略得到执行,并且执行许可证策略不会导致任何使用该产品的用户因许可证策略验证而收到错误。本节将向您展示如何实现这些目标,甚至通过使用 COM+ 功能使更多用户使用少量许可证。
为了顺利处理许可证问题,我们可以使用 COM+ 对象池服务。创建使用许可证产品的组件并将该组件注册为使用对象池的 COM+ 服务器应用程序,可以让管理员控制许可证的使用。将最大池大小设置为特定数字,COM+ 就可以创建该数量的对象,其中每个对象都使用一个第三方应用程序许可证。此外,COM+ 会排队请求,这些请求会超出管理员设置的特定对象的数量,直到一个对象完成其服务。这种能力为我们节省了编写代码以防止许可证不可用时出错、处理客户端请求的代码。
使用 COM+ 不仅可以让管理员动态控制最大池对象数量。通过使用即时激活 (JITA),COM+ 可以用少量组件对象服务许多用户,从而使许多用户可以使用少量许可证。JITA 实际上在客户端调用对象的一个方法或属性时才将客户端“连接”到对象。在第一次调用对象时,COM+ 为客户端创建上下文对象,但实际对象仅在客户端首次请求对象属性或方法时创建。在 COM+ 为客户端请求创建对象之前,COM+ 会检查是否存在这样的对象并且它没有服务任何调用。如果找到这样的对象,COM+ 将使用该对象而不是创建新对象。如您所见,JITA 的行为使 COM+ 能够使用更少的组件来服务更多的客户端。
COM+ 对象池允许程序员通过重写 CanBePooled
受保护的方法来以编程方式决定对象是否返回池中。这个机会允许程序员将恶意或行为不当的对象从池中移除。
在这个示例中,我创建了一个简单的 COM EXE C++ 应用程序。我在 CComModule
类中添加了一个成员数据用于许可证使用,并重写了 FinalConstruct
和 FinalRelease
COM 类方法来模拟一个限制为两个许可证的许可证应用程序。
图 4.0
STDMETHODIMP CLicensesApplicationCls::FinalConstruct ()
{
if (_Module.UsageCount == 2)
{
Error(L"No more licenses available for usage!",
IID_ILicensesApplicationCls,(HRESULT)-111111);
return (HRESULT)-111111;
}
_Module.UsageCount += 1;
return S_OK;
}
void CLicensesApplicationCls::FinalRelease ()
{
_Module.UsageCount -= 1;
}
为了顺利使用 EXE 应用程序,我使用 tlbimp.exe 和 sn.exe 工具为 COM EXE 应用程序创建了托管包装器。下一步是创建两个新的 COM+ 类 LicensesUsageCls
和 LicensesUsagePoolJitaCls
。一个用于使用最大池为两个对象并启用 JITA 选项的 COM 对象,另一个不带。
[ObjectPooling(true,2,2), JustInTimeActivation (true),
EventTrackingEnabled (true)]
public class LicensesUsagePoolJitaCls : ServicedComponent
{
public LicensesUsagePoolJitaCls()
{
}
public void UseOneLicense()
{
licensesapplicationNet.LicensesApplicationClsClass oLA = new
licensesapplicationNet.LicensesApplicationClsClass();
oLA.GetName();
}
}
public class LicensesUsageCls : ServicedComponent
{
public LicensesUsageCls()
{
}
public void UseOneLicense()
{
licensesapplicationNet.LicensesApplicationClsClass oLA = new
licensesapplicationNet.LicensesApplicationClsClass();
oLA.GetName();
}
}
剩下的就是两个页面,每个页面都将调用其对应的类。
public class LicensesUsage : System.Web.UI.Page
{
private void Page_Load(object sender, System.EventArgs e)
{
try
{
LicensesUsageClass.LicensesUsageCls o =
new LicensesUsageClass.LicensesUsageCls();
o.UseOneLicense ();
System.EnterpriseServices.ServicedComponent.DisposeObject (o);
}
catch(Exception Err)
{
throw Err;
}
}
}
public class LicensesUsagePoolJita : System.Web.UI.Page { private void Page_Load(object sender, System.EventArgs e) { LicensesUsageClass.LicensesUsagePoolJitaCls o = new LicensesUsageClass.LicensesUsagePoolJitaCls (); o.UseOneLicense (); System.EnterpriseServices.ServicedComponent.DisposeObject (o); } }
为了检查页面的行为,我们将使用 ACT 对至少 5 个并发用户进行压力测试。您会发现 LicensesUsagePoolJita 页面运行没有任何错误,而 LicensesUsage 由于许可证过度使用而返回大约 10% 的错误。如果您检查 RPC,您会看到使用这种技术,我们使大约 26 个并发用户能够使用我们的 2 个许可证应用程序。