WCF作为.Net Framework 三大核心应用之一,其在安全性方面也拥有强大的功能,WCF不仅与现有的安全性基础结构集成,而且还通过使用安全SOAP消息将分布式安全性扩展到Windows域的范围之外。
WCF使用绑定的方式可以灵活的配置服务的通信方式、安全策略、交互方式等,系统提供多种绑定方式,比如BasicHttpBinding、WSHttpBinding、NetTcpBinding等。接下将是一个完整的项目实现WCF安全证书认证方式的实现。
一、生成客户端证书
使用makecert.exe工具生成一个测试用的客户端证书(这个项目暂不验证服务端证书,所以只用一个客户端证书即可),命令如下:
makecert -sr CurrentUser -ss My -n CN=HelloServiceClient -sky exchange -pe -r
生成之后导出为文件以备后面使用。
二、创建项目
先创建一个解决方案HelloService,添加一个新的WCF服务应用程序WCF_HelloService和一个WPF项目WPF_HelloServiceClient作为客户端。
WCF_HelloService中删除默认生成的服务,添加一个名为HelloService的WCF服务,服务契约定义如下:
namespace WCF_HelloService
{
// 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IHelloService”。
[ServiceContract]
public interface IHelloService
{
[OperationContract]
string GetHello();
}
}
契约的实现代码如下:
namespace WCF_HelloService
{
// 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码、svc 和配置文件中的类名“HelloService”。
public class HelloService : IHelloService
{
public string GetHello()
{
if (ServiceSecurityContext.Current != null)
{
if (!ServiceSecurityContext.Current.IsAnonymous)
{
return "Hello:" + ServiceSecurityContext.Current.PrimaryIdentity.Name + ";type=" + ServiceSecurityContext.Current.PrimaryIdentity.AuthenticationType;
}
return "Hello,Anonymous";
}
return "Hello";
}
}
}
三、部署并测试HelloService
打开IIS,添加一个网站HelloService,目录指向上一不创建的WCF服务应用程序,类型Http,端口8001,修改HelloService应用程序池的.Net Framework版本为4.0。
先在浏览器参看服务是否托管成功,输入地址 http://charley-pc:8001/HelloService.svc。
提示服务已经托管成功,接下来WPF_HelloServiceClient项目中引用HelloService服务
接下来输入调用服务的代码:
private void btn_test_Click(object sender, RoutedEventArgs e)
{
try
{
HelloService.HelloServiceClient client = new HelloService.HelloServiceClient();
string msg = client.GetHello();
SubShowMessage(msg);
}
catch (Exception ex)
{
SubShowMessage(ex.ToString());
}
}
启动测试程序,单击Test按钮,结果如下:
以上步骤就简单的演示了服务的创建,部署及测试,但由于WCF服务没有进行任何配置,所以采用的都是默认的配置,由客户端引用服务自动生成的配置文件app.config内容可以清楚的看到WCF服务默认绑定方式是basicHttpBinding,安全模式为None,以下是自动生成的配置文件内容:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IHelloService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://charley-pc:8001/HelloService.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IHelloService" contract="HelloService.IHelloService"
name="BasicHttpBinding_IHelloService" />
</client>
</system.serviceModel>
</configuration>
启动Fiddler2,截获通信的内容如下:
POST http://charley-pc:8001/HelloService.svc HTTP/1.1
Content-Type: text/xml; charset=utf-8
VsDebuggerCausalityData: uIDPo5Ci8jnFhwFCgKGKh9VCB/cAAAAAJyy1KlXTmUaqjLYQlNh3vE3+ycPqdBxLnxS5MqxaLCwACQAA
SOAPAction: "http://tempuri.org/IHelloService/GetHello"
Host: charley-pc:8001
Content-Length: 133
Expect: 100-continue
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><GetHello xmlns="http://tempuri.org/"/></s:Body></s:Envelope>
响应的数据:
HTTP/1.1 200 OK
Content-Length: 197
Content-Type: text/xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
Date: Thu, 19 Jul 2012 06:21:48 GMT
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><GetHelloResponse xmlns="http://tempuri.org/"><GetHelloResult>Hello</GetHelloResult></GetHelloResponse></s:Body></s:Envelope>
四、配置WCF服务
由以上数据可以清楚的看到,WCF消息在网络上都是明文传输的,接下来就通过配置WCF服务,使用证书对消息进行加密验证,WCF提供的安全模式(配置中的Security元素的Mode属性)除了None,还有Basic、Windows、Digest、Ntlm、Certificate,接下来使用Certificate模式,
1、修改HelloService服务的配置文件如下:
服务端:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="WCF_HelloService.ServiceBehavior">
<!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 -->
<serviceMetadata httpsGetEnabled="true"/>
<!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 -->
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="PeerTrust"/>
<certificate x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="TrustedPeople" findValue="HelloServiceClient"/>
</clientCertificate>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="basicBindingConfig">
<security mode="Transport">
<transport clientCredentialType="Certificate"></transport>
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="WCF_HelloService.ServiceBehavior" name="WCF_HelloService.HelloService">
<endpoint binding="basicHttpBinding" bindingConfiguration="basicBindingConfig" contract="WCF_HelloService.IHelloService"></endpoint>
</service>
</services>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
客户端:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IHelloService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="Transport">
<transport clientCredentialType="Certificate" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="clientBehavior">
<clientCredentials>
<clientCertificate x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="TrustedPeople" findValue="HelloServiceClient"/>
<serviceCertificate>
<authentication certificateValidationMode="None"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://charley-pc:8001/HelloService.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IHelloService" contract="HelloService.IHelloService"
name="BasicHttpBinding_IHelloService" behaviorConfiguration="clientBehavior" />
</client>
</system.serviceModel>
2、再生成一个证书名称与站点名称相同,本例为:charley-pc,用站点的认证,导出该证书为文件。
3、运行MMC打开管理控制台,分别添加一个当前用户证书管理控制台和一个本地计算机证书管理控制台。
在当前用户下个人证书可以看到刚才生成的证书 charley-pc ,把它拖到本地计算机个人证书下(当然也可以直接生成证书到本地计算机个人证书下)
4、打开IIS服务器管理,点击服务证书可以看到证书已经存在了。
5、由于证书验证需用https传输方式,所以需要添加一个https绑定。选择HelloService站点,点击“绑定”,然后添加,类型为https,ssl证书选择charley-pc。
再次打开浏览器输入地址会出现找不到证书的错误,这是由于没有安装客户端证书或证书存储的位置不正确,这里需要把客户端证书放在可信任的人下。
运行客户端程序,单击Test按钮参看结果如下:
由以上结果可以看到已经启用了安全证书,类型是x509.