使用证书进行 WCF 安全性的简便方法






4.69/5 (37投票s)
一种使用证书在 Windows Communication Foundation 中加载从文件加载证书的简便解决方案
引言
安全性在任何分布式应用中都扮演着重要角色,而 Windows Communication Foundation(简称 WCF 或 Indigo)这个新的 Microsoft 通信框架实现了许多安全标准,并提供了广泛的功能。
安全最重要的方面之一是身份验证。WCF 可以配置为使用多种身份验证方法:
- 匿名调用者
- 用户名和密码
- 证书
- Windows
- CardSpace
在本文中,我将向您展示如何配置 WCF 使用证书通过另一种方法来验证服务客户端和服务器。
如果您想完全理解我的实现,请继续阅读下一节。如果您只想了解如何使用证书配置 WCF,请直接跳转到“快速入门教程”部分。
背景概念
接下来的几节假设您熟悉许多 WCF 和安全概念。如果您想回顾其中一些概念或获取更多信息,请参阅“外部资源”部分。
问题所在
使用证书进行身份验证并非新方法,但它仍然是验证主体的最常用方法之一。WCF 内置支持符合 Web Services Security (WS-Security) 标准的证书。
默认配置和可用示例存在的问题是,所有证书都必须安装在证书存储中,证书存储基本上是 Windows 保存所有证书(也用于其他应用程序:Internet Explorer 等)的中心位置。
为什么此解决方案会导致一些问题?简单来说,是因为正确配置所有证书并不容易。有关更多详细信息
- 当您将服务部署到服务器时,必须在证书存储中安装所有使用的证书(根据证书的用途安装在不同的位置)。
此操作必须使用安装程序、脚本文件或批处理文件执行。因此,使用 xcopy/ClickOnce 安装部署应用程序很困难。 - 每个客户端也必须安装用于自身身份验证的证书,同样要安装在证书存储中。如果您客户端数量很少,这很简单,但如果您必须手动配置每台计算机,则非常困难(此外,对于客户端,您不能使用 xcopy/ClickOnce 安装)。
- 您必须授予正在运行的进程(如 ASP.NET)读取证书私钥的权限。此步骤通常需要更改文件系统权限。这再次需要脚本文件或安装,这并不总是很容易。
- 如果您使用的是共享主机,您可能无法安装证书或更改证书权限。
- 作为开发人员,我希望每个项目都能与其他项目隔离。我希望能够轻松测试不同的配置或应用程序,我希望能够直接从代码存储库下载最新版本并运行它,而无需任何特殊配置。使用证书存储,我每次都必须记住安装或卸载证书。
在以下 MSDN 页面上,您可以看到一个使用证书的配置示例,以及关于如何使用经典解决方案安装证书的说明:MSDN:消息安全证书。
这些是我决定尝试不同方法的原因,我将在接下来的几节中进行描述。
解决方案
我的目标是找到一种不使用证书存储的简便方法来使用证书。WCF 可以轻松扩展;在本文中,我将向您展示如何扩展 WCF 以从文件中加载证书。
我知道将证书存储在文件系统中安全性较低,但我认为通过一些注意,这可能是一个有用的替代方案。有关我方法的潜在问题讨论,请参阅“缺点”部分。
请注意,通过我的解决方案,我仅改变了证书的加载方式,WCF 的所有优势(标准、经过验证的代码等)仍然有效。最重要的是,您仍然必须使用大多数证书所需的设置。有关完整且可正常工作的示例,我建议查看 zip 文件中的示例项目,或遵循“快速入门教程”部分自行在项目中实现此解决方案。
要理解此解决方案的工作原理,请继续阅读下一节。
实现细节
从文件加载证书非常简单,您只需使用 System.Security.Cryptography.X509Certificates.X509Certificate2
类。
//Load the certificate from a file
X509Certificate2 certificate =
new X509Certificate2(fullpath, password);
第一个参数是证书文件的路径,第二个参数是用于加密私钥的密码(如果存在)。
使用 X509Certificate2
类,您可以加载两种文件:
- .cer 文件 - 用于存储公钥
- .pfx 文件 - 用于存储公钥+私钥(可选,使用密码加密)
您可以从公共证书颁发机构获取这些文件,或使用 makecert.exe
和 pvk2pfx.exe
创建自己的自签名证书,两者都可作为 Visual Studio 工具获得。以下是如何创建证书的示例:
makecert -r -pe -n "CN=CompanyXYZ Server" -b 01/01/2007 -e 01/01/2010
-sky exchange Server.cer -sv Server.pvk
pvk2pfx.exe -pvk Server.pvk -spc Server.cer -pfx Server.pfx
第一个命令(makecert
)生成公钥(在此示例中为 Server.cer)和私钥(在此示例中为 Server.pvk)。第二个命令(pvk2pfx
)将这两个文件合并到一个 .pfx 文件中(在此示例中为 Server.pfx)。
执行 makecert
和 pvk2pfx
时,您可以输入用于加密私钥的密码。
服务身份验证
本节介绍如何配置服务器以使用用于验证服务的证书。
首先,您必须添加对 DevAge.ServiceModel.dll 的引用。此程序集(包含在带有完整代码的示例项目中)包含一些将在稍后介绍的用于从文件加载证书的类。
然后,您必须生成服务证书并将其放在安全目录中。对于 ASP.NET 网站,我认为一个好的解决方案是使用 App_Data 目录,该目录已自动配置为不允许公开访问。我在 App_Data 目录中生成了两个文件:Server.cer 和 Server.pfx。
通常,您可以在 .config 文件的 system.serviceModel
部分中像这样指定服务证书:
<behaviors>
<serviceBehaviors>
<behavior name="serviceCredentialBehavior">
<serviceCredentials>
<serviceCertificate findValue="Contoso.com"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
此配置基本上告诉 WCF 在证书存储中查找证书的位置。
要从文件加载证书,很遗憾,您不能简单地更改配置,而必须设置 ServiceHost.Credentials.ServiceCertificate.Certificate
属性。
我创建了一个新的配置节,您可以在其中指定服务器证书的位置:
<devage.serviceModel>
<services>
<add name="MathService"
serverCertificate="App_Data\Server.pfx"
/>
</services>
</devage.serviceModel>
然后,我创建了一个新的 ServiceHost
派生类 DevAge.ComponentModel.CertificateServiceHost
,它会自动读取新的配置节并设置 ServiceHost.Credentials.ServiceCertificate.Certificate
属性。
如果您直接创建 ServiceHost
类,您可以简单地更改代码,改为创建 DevAge.ComponentModel.CertificateServiceHost
。
如果您使用 ASP.NET(它会自动创建 ServiceHost
类),您必须像这样配置 .svc 文件:
<% @ServiceHost Language=C# Debug="true"
Service="MathService"
CodeBehind="~/App_Code/MathService.cs"
Factory="DevAge.ServiceModel.CertificateServiceHostFactory" %>
注意 Factory
属性,它使用一个特定的工厂类来创建我的 DevAge.ComponentModel.CertificateServiceHost
。
此配置替换了标准的 serviceCertificate
部分。
注意:当您使用 svcutil
创建代理客户端时,您会在 endpoint
部分中看到一个 identity
部分。
<identity>
<certificate encodedValue="...." />
</identity>
每次更改服务证书时都必须重新创建此部分,因为它包含在设计时生成的证书的公共标识。客户端使用此值来确保与预期的服务通信。
客户端身份验证
本节介绍如何使用证书来验证每个客户端。
您必须为每个客户端创建一个证书(或为多个客户端共享同一个证书)。您可以使用相同的命令生成自签名证书,或从证书颁发机构获取。
在我的示例中,我在客户端目录中生成了两个文件:Client.cer 和 Client.pfx。
通常,每个客户端都可以使用以下配置来配置应用程序:
<behaviors>
<endpointBehaviors>
<behavior name="ClientCredentialsBehavior">
<clientCredentials>
<clientCertificate findValue="Cohowinery.com"
storeLocation="CurrentUser"
storeName="My"
x509FindType="FindBySubjectName" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
此配置始终使用证书存储来查找要使用的正确证书,并且与前一个示例一样,没有直接使用证书文件的途径。因此,我再次创建了一个新的配置节来指定证书文件名:
<devage.serviceModel>
<endPoints>
<add contract="Client.MathService.IMathService"
clientCertificate="Client.pfx" />
</endPoints>
</devage.serviceModel>
要手动设置客户端证书,您可以设置 ClientProxy.ClientCredentials.ClientCertificate.Certificate
属性。
不幸的是,在这种情况下,没有办法通过读取新节来自动设置此属性(至少我找不到方法...),因此您必须使用如下代码配置每个代理:
//Create the client proxy as usual
MathService.MathServiceClient service =
new Client.MathService.MathServiceClient();
//Configure the client reading the devage.serviceModel configuration section
DevAge.ServiceModel.Proxy<MathService.IMathService>.Configure(service);
DevAge.ServiceModel.Proxy
类读取配置节并设置 ClientProxy.ClientCredentials.ClientCertificate.Certificate
属性。
如果客户端使用的证书受服务器信任(通常必须将其添加到证书存储的“受信任人员”中),则您的配置就完成了。如果证书是自签名的或无论如何不受服务器信任,则必须配置服务器以接受该证书。
为了解决此问题,我实现了一个自定义的 X509CertificateValidator
类 DevAge.ServiceModel.CustomCertificateValidator
,该类可以为每个服务进行配置。您必须扩展服务(服务器)的 .config 文件的 devage.serviceModel
部分,如下所示:
<devage.serviceModel>
<services>
<add name="MathService"
serverCertificate="App_Data\Server.pfx"
clientCertificates="App_Data\Client1.cer,App_Data\Client2.cer"
/>
</services>
</devage.serviceModel>
注意新的 clientCertificates
属性,其中包含一个要视为受信任的客户端证书列表。
您必须将所有客户端公钥文件(在此示例中为 Client1.cer 和 Client2.cer 文件)复制到服务器的 App_Data 目录中。
CertificateServiceHost
类会自动读取此新节,并使用如下代码设置 X509CertificateValidator
:
X509ClientCertificateAuthentication authentication =
serviceHost.Credentials.ClientCertificate.Authentication;
authentication.CertificateValidationMode =
System.ServiceModel.Security.X509CertificateValidationMode.Custom;
authentication.CustomCertificateValidator =
new CustomCertificateValidator(clientCertificates);
如果证书文件有密码,您可以在文件名中使用此格式指定:filename|password。例如,您可以使用以下值指定 web.config 的 serverCertificate
属性:App_Data\Server.pfx|yourpassword
。
同时请记住,您可以为 *.pvk 和 *.pfx 文件指定两个不同的密码,使用的密码是为 *.pfx 文件指定的密码。有关如何设置密码的更多详细信息,请参阅 makecert
和 pvk2pfx
文档。
缺点
将证书存储在文件中更简单,但安全性较低,因为恶意用户可能会窃取私钥。公钥可以毫无问题地共享,但如果有人窃取了私钥,您的安全就会受到威胁。使用私钥,恶意用户可以解密消息,或者冒充您的服务或客户端。
但我认为,通常情况下,如果有人可以访问您的文件系统,您可能还有许多其他安全问题。还要考虑,如果有人访问您的文件系统,他们很可能(取决于攻击类型)也能访问证书存储。
我的建议是正确评估您的应用程序和安全需求。在许多情况下,我认为将证书保存在文件中是相当安全的,并且可能很有用。
最后,请考虑您可以使用混合配置,例如在证书存储中读取服务器证书,在文件系统中读取客户端证书。
快速入门教程
这是一个使用 IIS/ASP.NET 托管的服务和控制台客户端应用程序来使用证书的简单教程。我将创建一个简单的 Math 服务,其中包含一个 Sum
方法。
创建服务
创建一个新的网站项目;添加对 DevAge.ServiceModel.dll 程序集的引用(您可以在示例项目中找到它)。
添加这些文件:
- App_Code/MathService.cs
using System; using System.ServiceModel; using System.Runtime.Serialization; [ServiceContract()] public interface IMathService { [OperationContract] double Sum(double a, double b); } public class MathService : IMathService { public double Sum(double a, double b) { return a + b; } }
- MathService.svc
<% @ServiceHost Language=C# Debug="true" Service="MathService" CodeBehind="~/App_Code/MathService.cs" Factory="DevAge.ServiceModel.CertificateServiceHostFactory" %>
- Web.config
<?xml version="1.0"?> <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <configSections> <!-- Add devage.serviceModel section handler --> <section name="devage.serviceModel" type="DevAge.ServiceModel.Configuration.Section, DevAge.ServiceModel" /> </configSections> <system.serviceModel> <services> <service name="MathService" behaviorConfiguration="behavior1"> <endpoint contract="IMathService" binding="wsHttpBinding" bindingConfiguration="binding1"/> </service> </services> <behaviors> <serviceBehaviors> <behavior name="behavior1"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> <bindings> <wsHttpBinding> <binding name="binding1"> <security mode="Message"> <message clientCredentialType="Certificate" /> </security> </binding> </wsHttpBinding> </bindings> </system.serviceModel> <!-- Specific DevAge.ServiceModel configuration used to set the client and server certificate --> <devage.serviceModel> <!-- List of service type each with the relative server certificate and client certificate list --> <services> <add name="MathService" serverCertificate="App_Data\Server.pfx" clientCertificates="" /> </services> </devage.serviceModel> </configuration>
现在您必须获取一个服务器证书,您可以从证书颁发机构购买,或者按照以下过程创建一个自签名证书:
打开 Visual Studio 命令提示符,转到 App_Data 目录并执行以下命令(在提示输入密码时不要输入):
makecert -r -pe -n "CN=CompanyXYZ Server" -b 01/01/2007 -e 01/01/2010
-sky exchange Server.cer -sv Server.pvk
pvk2pfx.exe -pvk Server.pvk -spc Server.cer -pfx Server.pfx
在 Visual Studio 中运行项目以启动 ASP.NET 开发服务器,然后尝试访问 MathService.svc 页面。
创建客户端
使用“Client
”作为项目名称创建一个新的控制台应用程序项目;添加对 DevAge.ServiceModel.dll 程序集的引用(您可以在示例项目中找到它)。在 Visual Studio 中,您必须右键单击项目并选择“添加服务引用”。插入您的服务的 URL(可能类似于 https://:1281/Server/MathService.svc)和名称 MathService
。
创建一个新的 Program.cs 文件来像这样测试您的服务:
using System;
using System.Collections.Generic;
using System.Text;
namespace Client
{
class Program
{
static void Main(string[] args)
{
MathService.MathServiceClient service =
new MathService.MathServiceClient();
//Configure the client reading the devage.serviceModel
//configuration section
DevAge.ServiceModel.Proxy<MathService.IMathService>.Configure(service);
try
{
double val = service.Sum(10, 20);
Console.WriteLine("OK");
}
catch (Exception ex)
{
service.Abort();
Console.WriteLine(ex.ToString());
}
Console.WriteLine("Press Enter key to exit...");
Console.ReadLine();
}
}
}
打开 app.config 文件,将 certificate
标签中的 encodedValue
属性保存到临时文件中。用下面的配置替换 app.config 文件,但请记住恢复以前的 encodedValue
属性,并检查 address
属性以指向正确的服务 URL:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<!-- Add devage.serviceModel section handler -->
<section name="devage.serviceModel"
type="DevAge.ServiceModel.Configuration.Section, DevAge.ServiceModel" />
</configSections>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="binding1" >
<security mode="Message">
<!-- Set the client authentication mode -->
<message clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="behavior1">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="None" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="https://:1281/Server/MathService.svc"
binding="wsHttpBinding" bindingConfiguration="binding1"
contract="Client.MathService.IMathService"
behaviorConfiguration="behavior1"
name="endpoint1" >
<identity>
<!--This is the value used to check the server certificate identity,
this value is generated by svcutil -->
<certificate encodedValue="TODO Here use your specific encodedValue" />
</identity>
</endpoint>
</client>
</system.serviceModel>
<!-- Specific DevAge.ServiceModel configuration -->
<devage.serviceModel>
<!-- List of endPoints each with the relative client certificate
to use for authentication. -->
<endPoints>
<add contract="Client.MathService.IMathService"
clientCertificate="..\..\Client.pfx" />
</endPoints>
</devage.serviceModel>
</configuration>
现在我们必须生成客户端证书。同样,此证书可以从证书颁发机构获取,或通过以下过程创建:
打开 Visual Studio 命令提示符,转到客户端项目目录并执行以下命令(在提示输入密码时不要输入):
makecert -r -pe -n "CN=CompanyXYZ Client" -b 01/01/2007 -e 01/01/2010
-sky exchange Client.cer -sv Client.pvk
pvk2pfx.exe -pvk Client.pvk -spc Client.cer -pfx Client.pfx
最后,将 Client.cer 文件复制到服务器的 App_Data 目录,并修改服务器的 web.config 文件,如下所示添加客户端证书:
<!-- Specific DevAge.ServiceModel configuration used to set
the client and server certificate -->
<devage.serviceModel>
<!-- List of service type each with the relative server
certificate and client certificate list -->
<services>
<add name="MathService"
serverCertificate="App_Data\Server.pfx"
clientCertificates="App_Data\Client.cer"
/>
</services>
</devage.serviceModel>
注意 clientCertificates
属性,它现在包含 Client.cer 文件。
现在您可以测试您的客户端应用程序了。
示例项目
在示例项目中,我创建了一个完整的可运行示例,包括:
Server
- 一个由网站项目托管的服务Client
- 实现为控制台应用程序的服务客户端DevAge.ServiceModel
- 包含用于从文件加载证书的新实现的代码库
您只需使用 Visual Studio 2005 打开解决方案,然后运行 Client 项目即可测试代码。
结论
我认为本文展示了不使用证书存储从文件加载证书的优点和缺点。安全性很重要,我知道此解决方案并非完美无缺,但可能是一个有用的替代方案。但请记住,更改配置文件以使用经典方法始终是容易的。
如果您发现任何错误(代码或文档),或者有任何问题、疑问或建议,请写信给我。我将非常感谢您的反馈。
外部资源
- MSDN WCF 主页
- 一篇 MSDN 文章,对用于身份验证的证书进行了很好的概述
带客户端证书的消息安全性 - WCF 的良好介绍和概述
Code Magazine - WCF 安全基础知识,作者 Michele Leroux Bustamante - 一个提供良好 WCF 资源的网站
http://www.netfx3.com/ - 来自维基百科的一些重要定义和概念
历史
- 2007 年 4 月 30 日:首次发布