创建带有 MSMQ 通信和证书安全的 WCF 服务






4.89/5 (26投票s)
使用 MSMQ 通信实现简单、安全的 WCF 服务
引言
可靠性和安全性是高度可用的业务关键型分布式应用程序开发人员和设计人员面临的两大挑战。Microsoft 消息队列 (MSMQ) 是 Microsoft 针对可靠、健壮的分布式应用程序设计提供的解决方案。
当我被要求开发一个安全、可靠的 WCF 服务时,我转向 Microsoft 消息队列寻求答案。最初我发现这是一项艰巨的任务,而且非常令人沮丧,主要是因为缺乏文档(说实话,是缺乏可用的示例而不是文档),而且概念令人困惑,最初难以理解。
一旦我的服务启动并运行,另一个挑战是使服务安全,这使我转向了安全证书,这本身可能非常令人困惑。
在这里,我将尝试通过提供一个简化、分步的教程来解释如何以最简单的形式实现 MSMQ 企业,该教程将创建一个使用 MSMQ 4.0 的客户端-服务器应用程序,我将在此过程中介绍所需的 WCF 基础知识。在文章的后半部分,我将提供一个指南,介绍如何使用基于消息的证书安全来保护我们的服务,这将使用我们的自签名密钥加密和签署我们的消息。我已尽量使本文尽可能实用,并且不会深入探讨理论(因为那也不是我的强项)。只假设您对 WCF 有一点先验知识。
使用代码
我随本文附带了总共 4 个解决方案。一个名为“MSMQNoSecurityService”的项目,它是我们没有实现安全性的服务项目,以及一个名为“MSMQNoSecurityClient”的项目,它是用于使用该服务的客户端。另外两个项目,“MSMQSecuredService”和“MSMQSecuredClient”与前两个项目几乎相同,但它们实现了我们将在文章后面讨论的安全功能。
“MSMQSecuredService”和“MSMQSecuredClient”本质上是我们创建的没有安全配置的相同服务和客户端,只是应用程序配置有所微小更改,但我将它们分开只是为了清晰和方便。当我们稍后在文章中讨论安全性时,您可以直接对旧项目进行相应的更改,而不是创建新项目。所以现在,只需浏览到您下载的文章源代码中的“MSMQ Service With No Security”文件夹。
什么是消息队列?
在设计分布式应用程序时,选择传输协议很重要,主要取决于您的优先级。如果可靠性和保证交付对您的业务需求至关重要,则最好选择更隔离的传输方案,而不是直接协议(例如 HTTP 或 TCP)。在直接传输协议中,如果网络中断,所有通信都将失败,因此难以解决可靠性问题。排队传输比直接传输提供更高的弹性。排队消息由存储库(队列)支持,因此如果交付失败,您的服务可以在指定时间段后再次尝试发送它们,以防连接已重新建立。
WCF 为我们提供了 MSMQ,这是一种存储转发机制,它在操作系统级别通过消息队列保证交付。在 MSMQ 中,我们不是直接向服务终结点发送消息,而是针对一个队列工作,该队列将充当未交付消息的存储库。如果发生故障,MSMQ 会定期尝试联系接收方,直到建立连接。简而言之,MSMQ 为我们提供了一个出色的基础设施,可以相对轻松地解决上述问题。
入门:使用 MSMQ 创建一个简单的 WCF 服务,不带安全性
安装 MSMQ 服务器
在使用消息队列之前,我们需要在操作系统中启用它。要在 Windows 上执行此操作,请转到“开始”->“打开或关闭 Windows 功能”,向下滚动列表并选择“Microsoft 消息队列 (MSMQ) 服务器”,展开该节点并选择所有 MSMQ 功能,我们不需要大部分功能(我们只需要 MSMQ 服务器和 MSMQ 服务器核心),但激活其他 MSMQ 功能也没有坏处。选择 MSMQ 功能并选择“确定”。这可能需要几分钟,因为它会在您的系统上激活 MSMQ 服务器。

此外,请确保稍后在运行示例时,在任何客户端计算机上激活 MSMQ。
创建服务项目
这里我们将创建 2 个项目,一个 WCF 服务库项目,它将托管我们的服务,以及一个小型 WPF 应用程序作为我们的客户端,我们将在本地网络上的另一台计算机上托管它。让我们开始吧。启动 Visual Studio 2010 并以管理员身份运行。创建一个新的 WCF 服务库项目并将其命名为“MSMQNoSecurityService”。我们在解决方案资源管理器中看到 Visual Studio 为我们创建了服务契约 (IService1.cs) 和服务实现 (Service1.cs)。根据 WCF 基础知识,接口类定义了我们的服务和操作契约,服务实现接口类中定义的操作契约。将这两个文件分别命名为 IMSMQService.cs 和 MSMQService.cs。我们将在服务契约中添加一个简单的方法(操作),名为 ShowMessage()
,它接受一个字符串参数,不返回任何内容。为了演示目的,我们将服务保持尽可能简单。在这里,我们的 ShowMessage()
方法将接受客户端发送给它的字符串参数,并将其打印到 Visual Studio 的输出控制台。请注意,当使用 MSMQ 时,我们**必须**使用 [OperationContract(IsOneWay=true)]
属性标记我们的操作契约,因为不保证可靠的通信通道,我们不能期望收到回复。所以我们的服务契约现在应该如下所示
namespace MSMQNoSecurityService
{
[ServiceContract]
public interface IMSMQService
{
[OperationContract(IsOneWay = true)]
void ShowMessage(string msg);
}
}
我们需要在我们的服务中实现我们的契约
namespace MSMQNoSecurityService
{
public class MSMQService : IMSMQService
{
public void ShowMessage(string msg)
{
Debug.WriteLine(msg+" Received at: "+System.DateTime.Now.ToString());
}
}
}
所以在这里我们定义了 `ShowMessage()` 方法的实际行为。它只是接收一条消息并将其连同接收时间一起打印到输出(调试)控制台。现在我们有了服务契约和实现,接下来我们需要配置它以使用 MSMQ。
创建队列
由于 MSMQ 通信是针对队列工作的,因此我们首先需要在将托管我们服务的机器上创建一个队列。我们可以通过编程方式或手动完成此操作,为了简单起见,我们将使用计算机管理管理单元手动完成。单击开始并搜索“计算机管理”。在计算机管理中导航到“服务和应用程序”节点并展开它,您现在应该会看到“消息队列”节点,展开它并选择“私有队列”。现在右键单击私有队列节点并选择“新建”->“私有队列”。我们将我们的队列命名为 **testqueue**,并确保它是一个事务性队列,这是 MSMQ 所要求的。

现在,如果我们展开私有队列节点,我们可以在那里看到我们新创建的 **testqueue**,右键单击并选择属性,我们可以配置其权限并查看其配置详细信息。
配置我们的服务以使用 MSMQ 传输
最后但并非最不重要的一点是,我们需要在服务的 *App.config* 文件中指定 netMsmqBinding
。我们主要关注服务配置文件中的 system.serviceModel
部分,默认情况下它使用 wsHttpBinding
,我们需要将其更改为 netMsmqBinding
。首先,我们将相应地更改服务名称。更改
<service name="MSMQNoSecurityService.Service1">
to<service name="MSMQNoSecurityService.MSMQService">
如果我们不在此处提供正确的服务名称,稍后在尝试托管服务时,我们将遇到“无元数据”错误。接下来,我们可以根据自己的喜好更改基址,这是服务在网络上宣传自己的地址,默认情况下您会看到类似 <add baseAddress = "https://:8732/Design_Time_Addresses/MSMQNoSecurityService/Service1/" />
这样就可以正常工作,为了清晰起见,我将其更改为包含服务命名空间和名称,但这并非强制性要求 <add baseAddress="http://mohammad-pc:8080/Design_Time_Addresses/MSMQNoSecurityService/MSMQService/"/>
毋庸置疑,您的地址的初始部分将根据您的计算机名称而与我的不同。请注意,如果您的服务主机计算机的 IP 预计不会更改,您也可以提供 IP 地址而不是计算机名称。我们 *App.config* 中需要修复的最后一个关键部分是我们的服务终结点。服务终结点之所以重要,是因为它们包含几个关键的配置信息。与服务的实际通信是通过服务的终结点进行的。终结点指定它们可以找到的地址、描述客户端如何与服务终结点通信的绑定协议(BasicHttp, netMsmqBinding, wsHttpBinding, NetTcpBinding
等),以及将显示哪些服务操作可用的契约。现在我们将终结点地址更改为我们计算机上队列的名称,因为无论客户端和服务之间是否建立连接,消息都将通过队列。自然,我们需要指定 netMsmqBinding
以及正确的服务契约名称。我们添加了另一个名为 bindingConfiguration
的属性,它允许我们微调绑定并提供更多配置选项。您的服务终结点现在应该如下所示 <endpoint address="net.msmq://mohammad-pc/private/testqueue"
binding="netMsmqBinding" bindingConfiguration="MyBinding"
contract="MSMQNoSecurityService.IMSMQService">
在 system.servicemodel
部分中,我们需要添加一个 bindings
部分,我们可以在其中提供 MyBinding
的详细信息
<bindings>
<netmsmqbinding>
<binding name="MyBinding">
<security mode="None">
</security></binding>
</netmsmqbinding>
目前,我们只是禁用服务的任何形式的安全性。我们的最终 *App.config* 应该如下所示<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<system.serviceModel>
<services>
<service name="MSMQNoSecurityService.MSMQService">
<host>
<baseAddresses>
<add baseAddress="http://mohammad-pc:8080/Design_Time_Addresses/MSMQNoSecurityService/MSMQService/"/>
</baseAddresses>
</host>
<endpoint address="net.msmq://mohammad-pc/private/testqueue"
binding="netMsmqBinding" bindingConfiguration="MyBinding"
contract="MSMQNoSecurityService.IMSMQService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netMsmqBinding>
<binding name="MyBinding" >
<security mode="None"/>
</binding>
</netMsmqBinding>
</bindings>
</system.serviceModel>
</configuration>
现在我们可以运行我们的项目,WcfSvcHost 将为我们托管我们的服务。如果出现错误,请确保您以管理员身份运行 Visual Studio,检查您的命名空间和您的服务配置 App.config 文件是否存在拼写错误或错误。如下图所示,我们的服务现在已启动并运行,我们网络上的客户端现在可以连接到它并发送消息。
创建客户端项目
现在我们已经完成了最难的部分,让我们创建一个小客户端向 MSMQ 服务发送消息。服务托管后,在网络上的另一台电脑上创建一个 WPF 应用程序(或控制台应用程序,这并不重要),命名为“MSMQNoSecurityClient”。显然,您还需要在客户端计算机上的“添加或删除 Windows 功能”中启用 MSMQ 服务器。右键单击“引用”并选择“添加服务引用...”
在地址栏中输入您作为服务基址的地址,在我的情况下是:
http://mohammad-pc:8080/Design_Time_Addresses/MSMQNoSecurityService/MSMQService/
,然后点击“Go”,如下图所示,Visual Studio 将为您找到该服务,在命名空间文本框中将服务引用命名为“MSMQServiceReference”。
如果您找不到该服务,请确保它在主机计算机上运行,并且您的客户端计算机已连接到网络。另外,尝试从服务引用地址栏的地址末尾删除“/mex”并禁用任何防火墙。单击“确定”并检查 WPF 应用程序的 *App.config*。您会注意到 Visual Studio 已方便地为您创建了绑定
<system.serviceModel> <bindings> <netMsmqBinding> <binding name="NetMsmqBinding_IMSMQService" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" deadLetterQueue="System" durable="true" exactlyOnce="true" maxReceivedMessageSize="65536" maxRetryCycles="2" receiveErrorHandling="Fault" receiveRetryCount="5" retryCycleDelay="00:30:00" timeToLive="1.00:00:00" useSourceJournal="false" useMsmqTracing="false" queueTransferProtocol="Native" maxBufferPoolSize="524288" useActiveDirectory="false"> <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" /> <security mode="None"> <transport msmqAuthenticationMode="WindowsDomain" msmqEncryptionAlgorithm="RC4Stream" msmqProtectionLevel="Sign" msmqSecureHashAlgorithm="Sha1" /> <message clientCredentialType="Windows" /> </security> </binding> </netMsmqBinding> </bindings> <client> <endpoint address="net.msmq://mohammad-pc/private/TestQueue" binding="netMsmqBinding" bindingConfiguration="NetMsmqBinding_IMSMQService" contract="MSMQServiceReference.IMSMQService" name="NetMsmqBinding_IMSMQService"> <identity> <dns value="localhost" /> </identity> </endpoint> </client> </system.serviceModel>您可以稍后修改客户端 *App.config* 的绑定部分中的各种设置以查看效果,其中包含客户端应等待多长时间才能重试向服务队列发送消息、要发送的最大消息大小等各种设置。请注意,您也需要在服务的 *App.config* 中更改这些设置,默认情况下,如果您在服务的 *App.config* 文件中指定自定义值,Visual Studio 将在您创建服务引用时在您的客户端配置文件中创建相同的设置。现在我们所需要做的就是创建一个客户端实例并使用它公开的操作将我们的消息发送到服务,请继续在 *MainWindow.xaml.cs* 文件中添加这些行
using MSMQNoSecurityClient.MSMQServiceReference;
.....
public partial class MainWindow : Window
{
private MSMQServiceReference.MSMQServiceClient client;
public MainWindow()
{
InitializeComponent();
client=new MSMQServiceClient();
}
private void buttonSendMsg_Click(object sender, RoutedEventArgs e)
{
client.ShowMessage(textBox1.Text.ToString());
}
}
我们在这里所做的只是创建一个名为“client”的服务引用实例,然后我们将主窗口文本框中的用户输入发送到服务的 `ShowMessage()` 方法。就这样!我们已经准备好向我们的服务计算机发送消息。如果您是一位敏锐的读者或事先了解消息队列,您可能会想“但我没有在客户端机器上创建队列”,确实您说对了。一旦您在客户端机器上启用了 MSMQ 服务器,客户端就会将消息发送到您客户端机器上的一个出站队列,因此我们无需在客户端机器上显式创建队列。如果您返回并检查计算机管理中“服务和应用程序”下的“消息队列”节点,您会看到一个出站队列文件夹,这就是当未指定本地队列时 MSMQ 使用的。返回并查看您的客户端 *App.config* 并查看终结点地址:`endpoint address="net.msmq://mohammad-pc/private/testqueue" ` 这告诉您的客户端将消息发送到 mohammad 计算机上名为 **testqueue** 的私有队列,如果 MSMQ 无法连接到该队列,它将把这些消息存储在自己的出站队列中,并每隔 x 分钟(如您的服务和客户端配置文件中指定)重试。因此,请继续运行客户端和服务器应用程序,输入要发送的消息,您将看到它们出现在 Visual Studio 调试输出窗口中(要打开此窗口,请在 Visual Studio 工具栏上选择“视图”->“输出”,然后选择“显示输出来源:调试”)。
消息队列实际操作
现在是酷炫的部分!禁用客户端工作站的网络适配器,但确保客户端应用程序仍在运行,发送更多消息,正如预期的那样,这些消息不会显示在 Visual Studio 输出控制台上。现在转到“计算机管理”管理单元,然后转到“服务和应用程序”->“消息队列”->“出站队列”,您会看到消息存储在一个名为 DIRECT=OS:[ServiceComputerName]\private$\testqueue 的出站队列中。

您可以看到自连接丢失以来您发送的消息数量,并且可以看到队列的状态是“等待连接”。打开客户端计算机的网络适配器并继续监视出站队列,过一段时间(如绑定中的 `retryCycleDelay` 所指定)消息将从出站队列消失并到达服务计算机。如果消息无法传递到服务主机,MSMQ 将尝试 `ReceiveRetryCount`(在我们的例子中为 5)次来传递消息。如果在此时间内传递失败,消息将移动到重试队列。在 `retryCycleDelay` 分钟(在我们的例子中为 30 分钟)之后,消息会转到终结点队列,并重新尝试传递消息。MSMQ 将重复此操作 `maxRetryCycle`(在我们的例子中为 2)次。如果消息仍然未传递,它将根据绑定配置中的 `receiveErrorHandling`(在我们的例子中为 fault)进行处理。理想情况下,您希望处理这种情况,因为留在客户端系统上的消息会迅速累积并占用系统资源,这些消息被称为“中毒”消息,因为它们被认为会“毒害”客户端计算机。您可以在此处阅读有关 `receiveErrorHandling` 选项的更多信息。所以我们有了一个简单的 WCF 服务,它使用可靠的 MSMQ 通信。现在我们已经解决了可靠性问题,在本文的下一部分中,我们将研究如何使用自签名证书保护我们的系统。
使用证书保护我们的 WCF 服务
现在我们已经成功设置并通过 MSMQ 传输运行了 WCF 服务,明智的做法是为其添加一些安全功能。我们将通过基于消息的证书安全来实现这一点。在此之前,让我们简要概述一下数字证书。
什么是证书?
数字证书负责验证网络世界中人员的身份,即此人是否是他们声称的那个人。它们还通过加密数据来保护客户端和服务器之间交换的数据免受篡改或窃听,这需要一个唯一的私钥才能解密。数字证书包含各种识别信息,例如序列号、指纹等。在我们的场景中,我们将创建两个证书,一个用于客户端,一个用于服务器。客户端和服务器将使用我们证书中的公钥来解码数字证书,并验证证书是由受信任的证书颁发机构颁发的,在本例中是我们的计算机的“根机构”。例如,一旦我们的客户端应用程序确定服务器是它所声称的服务器,它就会使用服务器的公钥和证书中的身份信息来加密消息。理想情况下,在生产环境(例如您希望用户访问并提供个人详细信息的网站)中,您不应使用自签名证书,主要原因是您的用户根本无法访问您的网站,因为他们的浏览器会告诉他们它不识别证书颁发机构 (CA),并向他们显示那个可怕的红色屏幕。在生产场景中,您应该考虑购买由知名 CA(如 Verisign、Equifax 等)颁发的证书。如果您有少量用户访问您的服务,并且您知道他们是谁,并且能够向他们颁发自签名证书,那么您可以使用自签名证书。
实现一个具有 MSMQ 通信和证书安全的简单 WCF 服务
现在我们的服务已经启动并运行,我们应该考虑添加一些安全功能。我们将通过使用 'makecert' 创建自签名证书来实现这一点,这些证书将用于签名和加密我们的消息。此工具生成 X.509 证书以及存储在证书 (.cer) 文件中的公钥和私钥对。公钥/私钥对也用于生成数字签名,以确保真实性。
创建我们的证书
以管理员身份打开 Visual Studio 2010 命令提示符(在 Visual Studio 2010 工具下),输入以下命令
makecert -pe –n "CN=CertServer" –sr localmachine –ss my –sky exchange -b 01/01/2012 -e 06/06/2012
和
makecert -pe –n "CN=CertClient" –sr localmachine –ss my –sky exchange -b 01/01/2012 -e 06/06/2012

请手动将上述命令输入到 Visual Studio 命令提示符中,如果您复制粘贴它们,可能会收到“参数过多”错误。`makecert` 命令会为我们生成一个自签名证书,您可以在此处找到有关 makecert 选项的更多信息。在这里,我们主要创建 2 个自签名证书,一个安装在客户端机器上,一个用于服务器,即托管我们服务的计算机。`CN=xxx` 指定证书的名称。`-sr` 告诉命令提示符将证书存储在当前机器上,`-ss` 指定证书将放置的存储位置,`my` 指向个人证书存储,我们稍后将解释如何访问此存储中的证书。其他命令:`-sky` 表示我们将密钥用于加密和密钥交换,如果省略此项,则在客户端尝试向服务发送消息时会出错,您会看到诸如“证书 'x' 可能没有能够进行密钥交换的私钥,或者进程可能没有私钥的访问权限”之类的错误,因此请务必包含此项。另外两个命令,`-b` 和 `-e` 分别指定证书的验证和到期日期。因此,我们的证书将从 2012 年 1 月 1 日到 2012 年 6 月 6 日有效。所以现在我们有了用于加密和签名的自签名证书。现在我们已经创建了证书,让我们使用它们来保护我们的 MSMQ 企业。要查看证书,请在运行中输入“mmc”并按 Enter。这是 Microsoft 管理控制台,它存储有关已安装证书和受信任的证书颁发机构 (CA) 的信息。现在选择“文件”->“添加/删除管理单元...”,您将看到此屏幕

我们从右侧的“可用管理单元”菜单中选择了“证书”,然后依次点击“添加>”、“计算机账户”、“下一步”、“完成”(本地计算机),最后点击“确定”。请尽快熟悉此操作,因为您将经常使用它来管理您的证书。在这里我们可以看到安装在我们系统上的证书和受信任的 CA。从现在开始,我们将使用此控制台来管理我们的证书。另外,您也可以将此控制台保存到一个方便的位置,这样可以省去每次管理证书时重复上述步骤的麻烦。为此,请从 mmc 控制台工具栏中选择“文件”->“另存为”,然后将控制台保存到一个位置,例如您的桌面,这样您就可以从现在开始重复使用该快捷方式。
让我们的计算机信任我们
在 MMC 控制台中,在左侧菜单中,转到“控制台根目录”->“证书(本地计算机)”->“个人”->“证书”,您可以看到我们刚刚创建的两个证书“CertClient”和“CertServer”。双击其中一个证书,在“认证路径”选项卡下,我们看到证书状态下方显示“此 CA 根证书不受信任,因为它不在受信任的根证书颁发机构存储中”。

为了纠正这个问题,我们需要告诉 MMC 根机构确实是一个受信任的 CA,所以我们将把根机构自己的证书添加到受信任的 CA 中。为此,选择根机构证书(记住我们现在正在查看我们刚刚创建的两个证书之一的认证路径选项卡,并且根机构是 CertClient 或 CertServer 证书的父级),点击如下图所示的“查看证书”按钮。

单击“详细信息”选项卡,然后选择“复制到文件”选项。连续单击两次“下一步”,并确保选择“DER 编码二进制 X.509(.CER)”->“下一步”。现在浏览到任意位置,我将把证书(.cer 文件)放在桌面上并命名为“Root Agency”。通过“下一步”->“完成”和“确定”确认步骤。现在,如果您查看输出位置,在我的情况下是我的桌面,您可以看到“Root Agency.cer”文件。现在我们可以将证书导入到我们的“受信任的根证书颁发机构”中,这样我们就可以开始使用它来保护我们的服务。右键单击“受信任的根证书颁发机构”下的“证书”文件夹,然后从上下文菜单中选择“所有任务”->“导入”。

在打开的“证书导入向导”中,单击“下一步”,然后浏览到您刚刚导出的 Root Agency.cer 文件(我的在桌面上),然后在下一个窗口中单击“下一步”,选择“将所有证书放入以下存储区”单选按钮,然后浏览到“受信任的根证书颁发机构”。通过选择“下一步”和“完成”确认,如果您收到任何安全警告,只需选择“是”。现在刷新“个人”->“证书”文件夹,如果我们在 mmc 中检查我们的个人证书,我们会看到它不再显示“未识别的 CA”警告,如果您双击“ClientCert”或“ServerCert”证书并选择“认证路径”选项卡,您将看到“Root Agency”和“ClientCert”证书都已被识别,并且在“证书状态”文本框下我们看到“此证书正常。”,如下图所示

现在我们已经创建并安装了证书,并将其注册到我们的服务计算机受信任的 CA,我们现在可以通过证书来保护我们的 MSMQ 通信。
创建安全的 WCF 服务项目
现在我们将重新创建一个项目,该项目将托管与我们之前项目相同的服务,但这次将实现安全性。由于我们的新项目与之前的项目几乎相同,我将不再赘述创建它的步骤,但我为了清晰起见,创建了一个名为“MSMQSecuredService”的单独项目,您可以与本文的源代码一起下载。要配置我们的服务以使用安全性,我们无需更改服务契约或实现中的任何内容。我们需要做的是配置我们的服务 *App.config* 文件以使用证书安全性。我们的新服务 *App.config* 在大多数方面与之前的相同,但我们需要定义服务行为并指定我们的绑定应该使用哪种安全性。首先,为您的服务添加一个服务行为:`service name="MSMQSecuredService.SecuredMSMQService" behaviorconfiguration="SecurityBehavior"`,并在我们的终结点部分中添加一个绑定配置,就像以前一样,但这次我们的绑定配置,名为 `SecuredBinding`,实际上会做一些事情。
我们的新终结点
<endpoint address="net.msmq://mohammad-pc/private/testqueue"
binding="netMsmqBinding" bindingConfiguration="SecuredBinding"
contract="MSMQSecuredService.ISecuredMSMQService">
`SecuredBinding` 的绑定配置
现在这是我们需要添加到新项目中的主要部分,以便将其配置为使用安全性<behaviors>
<serviceBehaviors>
<behavior name="SecurityBehavior">
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="True" />
<serviceCredentials>
<serviceCertificate findValue="CertServer" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
<clientCertificate>
<certificate findValue="CertClient" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
<authentication certificateValidationMode="None" />
</clientCertificate>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
因此,在 *App.config* 的 system.serviceModel
部分中,添加一个 serviceBehaviors
部分,它将描述我们在 *App.Config* 开头告诉服务使用的行为。行为配置非常直观,我们添加了一个 servicecredentials
部分,它将自然地告诉我们的服务客户端能够使用它所需的凭据。在 serviceCredentials
部分中,我们已将服务配置为使用计算机“My”存储中的名为“CertServer”的证书,这是我们在 mmc 中添加证书的 personal->certificates 存储(mmc 中的 Certificates(Local Computer)->Personal->Certificates),如果我们没有此证书,我们的服务将在我们尝试托管它时报错。在 serviceCertificate
部分下方,我们定义了 clientCertificate
凭据,在这里我们告诉服务要求客户端提供一个名为“ClientCert”的证书,该证书也安装在服务主机机器上。如果服务主机或客户端计算机上缺少任一证书,您将收到异常:System.InvalidOperationException: Cannot find the X.509 certificate using the following search criteria...
,并且我们将无法托管或使用服务。现在尝试运行具有安全配置的项目,它应该像以前一样启动并运行,接下来我们对客户端项目进行适当的更改。
创建安全的 WCF 客户端项目
首先,在创建客户端项目之前,我们需要在客户端机器上安装服务器和客户端证书“CertServer”和“CertClient”以及它们的私钥。为此,我们需要将证书及其私钥导出为 .PFX 文件,并将该 .PFX 文件安装在客户端机器上。
将证书导出到客户端计算机
转到 MMC 中的证书存储库:Certificates(Local Computer)->Personal->Certificates。右键单击“CertServer”证书文件,选择“所有任务”->“导出”,然后选择“是,导出私钥”选项,然后选择“个人信息交换 - PKCS #12 (.PFX)”选项,然后单击“下一步”。


请注意,由于我们正在导出私钥,并且由于它用于解密加密消息和签名消息,我们必须确保它不会落入坏人之手,因此证书导出向导会提示我们为 .PFX 文件设置密码,继续输入密码,请务必记住此密码,因为当您要在客户端计算机上安装证书及其私钥时,系统会再次提示您输入此密码。接下来为您的 .PFX 文件命名,我再次将其命名为“CertServer”,并将其导出到我的 U 盘,这样我就可以在客户端计算机上安装它了。通过单击完成确认,CertServer.PFX 文件现在位于您指定的输出位置。现在我们需要在客户端计算机上安装此 .PFX 文件。在客户端计算机上运行 mmc 控制台,导航到 Certificates(Local Computer)->Personal->Certificates,然后右键单击 Certificates 文件夹,选择“所有任务”->“导入...”

单击“下一步”,然后浏览到我们从服务主机计算机导出的 .PFX 文件,然后单击“打开”和“下一步”。系统会提示您输入之前设置的密码,因此请继续输入,并勾选“包括所有扩展属性”复选框。然后选择“将所有证书放入以下存储”->“证书存储:个人”。通过单击“下一步”和“完成”确认。这样我们就成功导入并安装了“CertServer”证书及其私钥到客户端计算机。**对“CertClient”执行完全相同的操作,并将其(以及其私钥)导入并安装到客户端计算机**。再次记住,您也需要导出私钥,否则在尝试使用服务时,您将收到一个漂亮的 `NotSupportedException: 私钥不在 X.509 证书中`。好的,我们快完成了,我们已经将两个证书复制并安装到客户端计算机上,现在我们只需要创建客户端应用程序并将其配置为使用证书,这非常简单。我为客户端创建了一个单独的 WPF 项目,名为“MSMQSecuredClient”,您可以与安全和不安全的服务和客户端项目一起下载。由于更改仅在 *App.Config* 文件中,我将不再重复如何创建客户端和服务引用。像以前一样创建客户端应用程序,或者对其 *App.Config* 进行以下更改。
<netMsmqBinding>
<binding name="NetMsmqBinding_ISecuredMSMQService" closeTimeout="00:01:00"
//Same as before....
..
//
/>
<security mode="Message">
<message clientCredentialType="Certificate" />
</security>
</binding>
</netMsmqBinding>
</bindings>
在绑定部分的安全节点中,将安全模式设置为 `Message`,之前我们将其设置为 `None`。在安全部分中,我们告诉客户端提供证书凭据才能使用服务,另一种凭据类型可以是用户名。与安全服务配置一样,我们还需要为客户端终结点配置一个行为 `behaviorConfiguration=endpointCredentialBehavior`,并将 DNS 设置为服务证书,之前这只是设置为“localhost”
<identity>
<dns value="CertServer" />
</identity>
同样,就像安全服务一样,最关键的配置更改是定义一个包含客户端安全功能的行为
<behaviors>
<endpointBehaviors>
<behavior name="endpointCredentialBehavior">
<clientCredentials>
<clientCertificate findValue="CertClient"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
<serviceCertificate>
<defaultCertificate findValue="CertServer" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
<authentication certificateValidationMode="None" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
我们正在配置客户端以在其本地证书存储“My”中查找名为“CertClient”的证书。`defaultCertificate` 提供服务器用于身份验证和加密的证书的名称和位置,我们需要客户端计算机上的“CertServer”证书才能向服务发送消息。如果我们不向客户端提供默认证书,我们将遇到 `InvalidOperationException: 未为目标“net.msmq://xxx-pc/private/queueName”提供服务证书`。所以我们做到了,我们已经成功实现了带有 MSMQ 通信的 WCF 服务,并且我们使用数字证书保护了我们的服务。最后需要注意的是,如果您在尝试使用服务时在客户端计算机上遇到 `CryptographicException: “密钥集不存在”`,请尝试以管理员身份在 Visual Studio 中运行项目,因为客户端应用程序可能没有访问证书的凭据。尝试运行服务和客户端,向服务发送一些消息,您应该像以前一样在 Visual Studio 的调试输出控制台中看到它们,只是现在我们的消息已签名并加密。

注意:当我第一次实现“SecuredMSMQService”项目时,WcfSvcHost 花了很长时间才托管它,如果您的也如此,请耐心等待,尝试停止并重新运行项目。
结论
在本文中,我们解释了如何使用 MSMQ 通信设置一个简单的 WCF 服务,并使用一个小型客户端应用程序来使用它。我们详细介绍了在 Windows 中启用消息队列所需的步骤,并解释了消息队列的概念。简要讨论了数字证书,并概述了如何创建自签名证书,我们还讨论了如何使用自签名证书来保护我们的服务。