在上一篇"x.509证书在WCF中的应用(CS篇)"里,我们知道了如何在应用程序中,利用x.509证书来验证WCF的消息安全(没看过的朋友建议先看下,地址http://www.cnblogs.com/yjmyzz/archive/2008/08/20/1272550.html),这一篇我们将尝试把x.509证书放到IIS里来验证WCF。
WCF宿主在IIS和普通应用程序里,原理虽然没什么不同,但在实际测试中发现,如果服务端与客户端都采用x.509证书来验证,服务端设置的自定义验证客户端证书的方法总是不起作用,无奈之下,只能在客户端采用了一种变相的方法来验证客户端证书。
废话不多说,还是来看具体步骤吧:
开发环境: Windows2003 + VS2008(SP1)
一.申请/颁发服务端证书和客户端证书
默认情况下,用makecert制作的证书,我经过多次尝试,在IE7里始终被认为不信任的证书(也许是我makecert的参数不对),导致在IE7里测试SSL时,总是显示"证书错误,导航已阻止"之类,所以在本例中,我们换一种方式,用windows2003自带的证书服务来申请/颁发服务端证书和客户端证书,对这一块不熟悉的朋友,请参见"[原创图解]Win2003证书服务配置/客户端(服务端)证书申请/IIS站点SSL设置"一文(地址:http://www.cnblogs.com/yjmyzz/archive/2008/08/21/1273201.html),这里要注意的是服务端证书的"颁发给"的对象一定要和最后运行的url里的计算机名(或域名)信息一致,如下图,否则IE7会认为该证书有问题
二.Wcf web服务端开发
1.vs2008启动后,新建一个web Application(本例命名为WebServer),添加一个wcf服务,命名为MyService,同样系统会自动增加一个IMyService的接口,这二个文件的内容如下:
MyService.svc.cs(代码很简单,返回服务端时间而已)
using
System;
using
System.ServiceModel;
namespace
WebServer
{
//
NOTE: If you change the class name "MyService" here, you must also update the reference to "MyService" in Web.config.
public
class
MyService : IMyService
{
public
string
Test()
{
return
"
服务端时间:<br/>
"
+
DateTime.Now.ToString();
}
}
}
IMyService.cs内容
using
System;
using
System.ServiceModel;
using
System.Text;
namespace
WebServer
{
//
NOTE: If you change the interface name "IMyService" here, you must also update the reference to "IMyService" in Web.config.
[ServiceContract]
public
interface
IMyService
{
[OperationContract]
string
Test();
}
}
配置文件Web.Config关键节点内容如下:
<
system.serviceModel
>
<
behaviors
>
<
serviceBehaviors
>
<
behavior name
=
"
WebServer.MyServiceBehavior
"
>
<
serviceMetadata httpGetEnabled
=
"
false
"
httpsGetEnabled
=
"
true
"
httpsGetUrl
=
"
https://jimmycntvs/MyService.svc
"
/>
<
serviceDebug includeExceptionDetailInFaults
=
"
false
"
/>
<
serviceCredentials
>
<!--
下面这一行,在测试过程中,发现始终不起作用,只能放弃,转而在客户端的配置中用findValue
=
"
ec0aa48043eab64714c92a0ff7fa0365e1b594af
"
x509FindType
=
"
FindByThumbprint
"
类似这样的方法来验证指定的客户端证书
-->
<!--<
clientCertificate
>
<
authentication certificateValidationMode
=
"
Custom
"
customCertificateValidatorType
=
"
WebServer.CustomX509CertificateValidator,WebServer
"
/>
</
clientCertificate
>-->
<
serviceCertificate findValue
=
"
JimmyCntvs
"
storeLocation
=
"
LocalMachine
"
x509FindType
=
"
FindBySubjectName
"
storeName
=
"
My
"
/>
</
serviceCredentials
>
</
behavior
>
</
serviceBehaviors
>
</
behaviors
>
<
bindings
>
<
wsHttpBinding
>
<
binding name
=
"
wsHttpBinding_MyService
"
>
<
security mode
=
"
Transport
"
>
<
transport clientCredentialType
=
"
None
"
/>
<!--
设置成Certificate后,启动WCF时,总是提示出错[ 服务“SslRequireCert”的 SSL 设置与 IIS“Ssl”的 SSL 设置不匹配。]无奈只能设置成None
-->
<!--<
transport clientCredentialType
=
"
Certificate
"
/>-->
<
message clientCredentialType
=
"
Certificate
"
/>
</
security
>
</
binding
>
</
wsHttpBinding
>
</
bindings
>
<
services
>
<
service behaviorConfiguration
=
"
WebServer.MyServiceBehavior
"
name
=
"
WebServer.MyService
"
>
<
endpoint address
=
"
https://jimmycntvs/MyService.svc
"
binding
=
"
wsHttpBinding
"
bindingConfiguration
=
"
wsHttpBinding_MyService
"
contract
=
"
WebServer.IMyService
"
>
<
identity
>
<
dns value
=
"
jimmycntvs
"
/>
</
identity
>
</
endpoint
>
</
service
>
</
services
>
</
system.serviceModel
>
解释一下:
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" httpsGetUrl="
https://jimmycntvs/MyService.svc%22/>这行表明,只能用https方式取得wcf的元数据,httpsGetUrl标明了取得WCF元数据的地址
<serviceCertificate findValue="JimmyCntvs" storeLocation="LocalMachine" x509FindType="FindBySubjectName" storeName="My"/>这一句指wcf服务启动时,先验证服务端是否在LocalMachine这个位置有一个SubjectName为JimmyCntvs的服务端证书
<security mode="Transport">
<transport clientCredentialType="None"/>
<message clientCredentialType="Certificate"/>
</security> 这里表示用Transport模式来进行安全验证,详细安全参数含义可参见
http://www.cnblogs.com/yjmyzz/archive/2008/08/19/1271133.html
2.IIS服务端配置
为方便测试,在IIS中直接把WebServer项目配置为一个站点(本例为http://localhost/),同时正确安装第一步颁发的服务端证书,同时把"要求安全通道(SSL)"选中,这样站点就必须用https://来访问了
这些都弄好以后,就可以测试了,浏览https://localhost/MyService.svc,如果是IE7,可能会报一个"证书错误:导航已阻止"的错误,没关系,把localhost换成计算机名(本例中为jimmycntvs)就正常了,如下图:
三.Web 客户端开发
1.先生成服务端WCF的代理
vs2008命令行下运行
svcutil.exe https://jimmycntvs/MyService.svc?wsdl /d:c:\123 即把代理文件和配置文件输出到c:\123下
2.安装客户端证书
把第一步颁发的客户端证书正确安装,同时查看该证书的详细信息,记下"微缩图"去掉空格后的值(本例为ec0aa48043eab64714c92a0ff7fa0365e1b594af,每个证书的这个值都是唯一的),后面会用到
3.vs.net2008新建一个WebClient的webApplication,把刚才的这二个文件加到WebClient中,同时output.config改名为Web.config,关键节点调整如下:
<
system.serviceModel
>
<
behaviors
>
<
endpointBehaviors
>
<
behavior name
=
"
NewBehavior
"
>
<
clientCredentials
>
<!--<
clientCertificate findValue
=
"
Jimmy.Yang
"
x509FindType
=
"
FindBySubjectName
"
storeLocation
=
"
CurrentUser
"
storeName
=
"
My
"
/>-->
<!--
由于服务端设置的
<
authentication certificateValidationMode
=
"
Custom
"
customCertificateValidatorType
=
"
WebServer.CustomX509CertificateValidator,WebServer
"
/>
在测试中发现总是不起作用,所以只能转而用下面的方式从客户端来验证特定的证书,理论上讲这样有安全隐患,建议实际操作时,可将本节加密后,再连同客户端证书一起分发给客户端,若用于安全性较高的环境,建议还是用UserName方式,到数据库里验证用户名和密码
-->
<
clientCertificate findValue
=
"
ec0aa48043eab64714c92a0ff7fa0365e1b594af
"
x509FindType
=
"
FindByThumbprint
"
storeLocation
=
"
CurrentUser
"
storeName
=
"
My
"
/>
<
serviceCertificate
>
<
authentication certificateValidationMode
=
"
None
"
/>
</
serviceCertificate
>
</
clientCredentials
>
</
behavior
>
</
endpointBehaviors
>
</
behaviors
>
<
bindings
>
<
wsHttpBinding
>
<
binding name
=
"
WSHttpBinding_IMyService
"
closeTimeout
=
"
00:01:00
"
openTimeout
=
"
00:01:00
"
receiveTimeout
=
"
00:10:00
"
sendTimeout
=
"
00:01:00
"
bypassProxyOnLocal
=
"
false
"
transactionFlow
=
"
false
"
hostNameComparisonMode
=
"
StrongWildcard
"
maxBufferPoolSize
=
"
524288
"
maxReceivedMessageSize
=
"
65536
"
messageEncoding
=
"
Text
"
textEncoding
=
"
utf-8
"
useDefaultWebProxy
=
"
true
"
allowCookies
=
"
false
"
>
<
readerQuotas maxDepth
=
"
32
"
maxStringContentLength
=
"
8192
"
maxArrayLength
=
"
16384
"
maxBytesPerRead
=
"
4096
"
maxNameTableCharCount
=
"
16384
"
/>
<
reliableSession ordered
=
"
true
"
inactivityTimeout
=
"
00:10:00
"
enabled
=
"
false
"
/>
<
security mode
=
"
Transport
"
>
<
transport clientCredentialType
=
"
Certificate
"
proxyCredentialType
=
"
None
"
realm
=
""
/>
<
message clientCredentialType
=
"
Certificate
"
negotiateServiceCredential
=
"
true
"
establishSecurityContext
=
"
true
"
/>
</
security
>
</
binding
>
</
wsHttpBinding
>
</
bindings
>
<
client
>
<
endpoint address
=
"
https://jimmycntvs/MyService.svc
"
binding
=
"
wsHttpBinding
"
bindingConfiguration
=
"
WSHttpBinding_IMyService
"
contract
=
"
IMyService
"
name
=
"
WSHttpBinding_IMyService
"
behaviorConfiguration
=
"
NewBehavior
"
>
<
identity
>
<
dns value
=
"
jimmycntvs
"
/>
</
identity
>
</
endpoint
>
</
client
>
</
system.serviceModel
>
主要是增加了behaviors这一节,用于查询客户端机器是否有微缩图为ec0aa48043eab64714c92a0ff7fa0365e1b594af的证书
<behaviors>
<endpointBehaviors>
<behavior name="NewBehavior">
<clientCredentials>
<clientCertificate findValue="ec0aa48043eab64714c92a0ff7fa0365e1b594af" x509FindType="FindByThumbprint" storeLocation="CurrentUser" storeName="My"/> <serviceCertificate>
<authentication certificateValidationMode="None" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
最后,我们在Default.aspx.cs中加几行代码,用来调用WebServer中的WCF
protected
void
Page_Load(
object
sender, EventArgs e)
{
using
(MyServiceClient _client
=
new
MyServiceClient())
{
string
_test
=
_client.Test();
Response.Write(_test);
}
}
运行一下,正常的话,应该会返回服务端的时间。 欢迎转载,转载请注明来自cnblogs"菩提树下的杨过"
编后语:
本文演示了如何将WCF Host在IIS中,并对服务端和客户端都采用x.509证书方式来验证,当然这种方式要求每个客户端机器上都必须安装服务端颁发的证书。在互联网环境下,这可能会给客户端的使用带来麻烦,这时可以采用服务端用x.509方式验证,客户端用经典的用户名/密码的方式来验证,详情可参见http://www.cnblogs.com/fineboy/archive/2008/03/26/1122650.html,写得很详细,一看就知.