WCF 安全之自定义的用户名/密码身份验证
本文将记述基于 "用户名/密码" 方式的身份验证开发步骤。
1. 服务器数字证书
2. 创建服务
我们通过继承 UserNamePasswordValidator 来创建一个自定义验证器。
3. 创建客户端
启动服务器后,创建客户端代理文件。注意自动生成的客户端配置文件中包含了服务器数字证书的相关信息。
开始调用服务,注意将 CertificateValidationMode 设置为 None,当然也可以写到配置文件中。
OK,测试通过。测试环境:Windows XP操作系统、VS2008 WCF、VS2008 Windows应用程序。
1. 服务器数字证书
(1) 安装证书管理中心(Certification Authority):控制面板——〉添加删除程序里安装
(2) 打开IIS,到默认站点(Default Web Site)——〉右键菜单,属性(Properties),打开属性管理窗口——〉(目录安全)Directory Security——〉Server Certificate,下一步一直到最后生成certreq.txt。
(3) 进入Certification Authority,右键根节点,选择All Tasks—Submit new question,选择生成的certreq.txt文件
(4) Click Pending requests, choose Request ID , 右键菜单所以任务---〉Issue。证书将出现在Issued Certificates,双击在detail中copy file生成证书
(5) 在IIS中安装证书
或者使用命令的方式准备证书:D:\>makecert -r -pe -n "CN=MyServer" -ss My -sky exchange2. 创建服务
1 [ServiceContract]
2 public interface IService1
3 {
4
5 [OperationContract]
6 string Test();
7
8 [OperationContract]
9 string GetUserName();
10 }
2 public interface IService1
3 {
4
5 [OperationContract]
6 string Test();
7
8 [OperationContract]
9 string GetUserName();
10 }
1 public class Service1 : IService1
2 {
3 public string Test()
4 {
5 return "Server:" + DateTime.Now.ToString();
6 }
7
8 public string GetUserName()
9 {
10 string userName = "";
11 if (ApplicationRequestContext.Current != null && ApplicationRequestContext.Current.AuthenticatedUsername != "")
12 {
13 userName = ApplicationRequestContext.Current.AuthenticatedUsername;
14 }
15 else
16 {
17 string[] tokenParts = OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name.Split('|');
18 userName = tokenParts[0];
19 }
20
21 string str = "Hello " + userName + ".";
22 //str += string.Format(" You entered: {0}", value);
23 return str;// "hello world";
24 }
25 }
2 {
3 public string Test()
4 {
5 return "Server:" + DateTime.Now.ToString();
6 }
7
8 public string GetUserName()
9 {
10 string userName = "";
11 if (ApplicationRequestContext.Current != null && ApplicationRequestContext.Current.AuthenticatedUsername != "")
12 {
13 userName = ApplicationRequestContext.Current.AuthenticatedUsername;
14 }
15 else
16 {
17 string[] tokenParts = OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name.Split('|');
18 userName = tokenParts[0];
19 }
20
21 string str = "Hello " + userName + ".";
22 //str += string.Format(" You entered: {0}", value);
23 return str;// "hello world";
24 }
25 }
我们通过继承 UserNamePasswordValidator 来创建一个自定义验证器。
1public class MyUserNamePasswordValidator : UserNamePasswordValidator
2{
3 public override void Validate(string userName, string password)
4 {
5 if (userName != "user" || password != "pwd")
6 {
7 throw new SecurityTokenException("Unknown Username or Password");
8 }
9 }
10}
11
接下来创建服务器配置文件,我们使用 WsHttpBinding,采取 TransportWithMessageCredential安全模式。其他的设置还包括 certificateValidationMode、userNamePasswordValidationMode、customUserNamePasswordValidatorType 等。2{
3 public override void Validate(string userName, string password)
4 {
5 if (userName != "user" || password != "pwd")
6 {
7 throw new SecurityTokenException("Unknown Username or Password");
8 }
9 }
10}
11
1
2 <system.serviceModel>
3
4 <services>
5 <service name="WcfServiceUserName.Service1" behaviorConfiguration="NewBehavior">
6 <!-- Service Endpoints -->
7
8 <endpoint address="https://servername/WCFUserName/Service1.svc/ws1" binding="wsHttpBinding" bindingConfiguration="Binding1" contract="WcfServiceUserName.IService1">
9 <identity>
10 <dns value="servername(要和证书中申请的一样)"/>
11 </identity>
12 </endpoint>
13 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
14 </service>
15 </services>
16 <bindings>
17 <wsHttpBinding>
18 <binding name="Binding1">
19 <security mode="TransportWithMessageCredential">
20 <message clientCredentialType="UserName"/>
21 </security>
22 </binding>
23 </wsHttpBinding>
24 </bindings>
25 <behaviors>
26 <serviceBehaviors>
27 <behavior name="NewBehavior">
28 <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
29 <serviceMetadata httpGetEnabled="true"/>
30 <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
31 <serviceDebug includeExceptionDetailInFaults="true"/>
32 <serviceCredentials>
33 <clientCertificate>
34 <authentication certificateValidationMode="None"/>
35 </clientCertificate>
36 <!--userNamePasswordValidationMode can not equal Window,must be Custom-->
37 <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="CustomUserNamePasswordValidator(类的名称),程序集名称"/>
38 </serviceCredentials>
39 </behavior>
40 </serviceBehaviors>
41 </behaviors>
42 </system.serviceModel>
2 <system.serviceModel>
3
4 <services>
5 <service name="WcfServiceUserName.Service1" behaviorConfiguration="NewBehavior">
6 <!-- Service Endpoints -->
7
8 <endpoint address="https://servername/WCFUserName/Service1.svc/ws1" binding="wsHttpBinding" bindingConfiguration="Binding1" contract="WcfServiceUserName.IService1">
9 <identity>
10 <dns value="servername(要和证书中申请的一样)"/>
11 </identity>
12 </endpoint>
13 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
14 </service>
15 </services>
16 <bindings>
17 <wsHttpBinding>
18 <binding name="Binding1">
19 <security mode="TransportWithMessageCredential">
20 <message clientCredentialType="UserName"/>
21 </security>
22 </binding>
23 </wsHttpBinding>
24 </bindings>
25 <behaviors>
26 <serviceBehaviors>
27 <behavior name="NewBehavior">
28 <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
29 <serviceMetadata httpGetEnabled="true"/>
30 <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
31 <serviceDebug includeExceptionDetailInFaults="true"/>
32 <serviceCredentials>
33 <clientCertificate>
34 <authentication certificateValidationMode="None"/>
35 </clientCertificate>
36 <!--userNamePasswordValidationMode can not equal Window,must be Custom-->
37 <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="CustomUserNamePasswordValidator(类的名称),程序集名称"/>
38 </serviceCredentials>
39 </behavior>
40 </serviceBehaviors>
41 </behaviors>
42 </system.serviceModel>
3. 创建客户端
启动服务器后,创建客户端代理文件。注意自动生成的客户端配置文件中包含了服务器数字证书的相关信息。
1 <system.serviceModel>
2 <bindings>
3 <wsHttpBinding>
4 <binding name="WSHttpBinding_IService1" closeTimeout="00:01:00"
5 openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
6 bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
7 maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
8 messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
9 allowCookies="false">
10 <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
11 maxBytesPerRead="4096" maxNameTableCharCount="16384" />
12 <reliableSession ordered="true" inactivityTimeout="00:10:00"
13 enabled="false" />
14 <security mode="TransportWithMessageCredential">
15 <transport clientCredentialType="None" proxyCredentialType="None"
16 realm="" />
17 <message clientCredentialType="UserName" negotiateServiceCredential="true"
18 algorithmSuite="Default" establishSecurityContext="true" />
19 </security>
20 </binding>
21 </wsHttpBinding>
22 </bindings>
23 <client>
24 <endpoint address="https://servername/WCFUserName/Service1.svc/ws1"
25 binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1"
26 contract="IService1" name="WSHttpBinding_IService1">
27 <identity>
28 <dns value="servername" />
29 </identity>
30 </endpoint>
31 </client>
32 </system.serviceModel>
2 <bindings>
3 <wsHttpBinding>
4 <binding name="WSHttpBinding_IService1" closeTimeout="00:01:00"
5 openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
6 bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
7 maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
8 messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
9 allowCookies="false">
10 <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
11 maxBytesPerRead="4096" maxNameTableCharCount="16384" />
12 <reliableSession ordered="true" inactivityTimeout="00:10:00"
13 enabled="false" />
14 <security mode="TransportWithMessageCredential">
15 <transport clientCredentialType="None" proxyCredentialType="None"
16 realm="" />
17 <message clientCredentialType="UserName" negotiateServiceCredential="true"
18 algorithmSuite="Default" establishSecurityContext="true" />
19 </security>
20 </binding>
21 </wsHttpBinding>
22 </bindings>
23 <client>
24 <endpoint address="https://servername/WCFUserName/Service1.svc/ws1"
25 binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1"
26 contract="IService1" name="WSHttpBinding_IService1">
27 <identity>
28 <dns value="servername" />
29 </identity>
30 </endpoint>
31 </client>
32 </system.serviceModel>
开始调用服务,注意将 CertificateValidationMode 设置为 None,当然也可以写到配置文件中。
1 try
2 {
3 WSHttpBinding mybinding = new WSHttpBinding();
4 mybinding.Security.Mode = SecurityMode.TransportWithMessageCredential;
5 mybinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
6
7 EndpointAddress ea = new EndpointAddress(new Uri("https://servername/WCFUserName/Service1.svc/ws1"));
8 Service1Client client = new Service1Client(mybinding, ea);
9
10 client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
11 client.ClientCredentials.UserName.UserName = "user";
12 client.ClientCredentials.UserName.Password = "pwd";
13 client.Open();
14 MessageBox.Show(client.GetUserName());
15 }
16 catch (SecurityTokenException ex)
17 {
18 MessageBox.Show(ex.Message.ToString());
19 }
20
2 {
3 WSHttpBinding mybinding = new WSHttpBinding();
4 mybinding.Security.Mode = SecurityMode.TransportWithMessageCredential;
5 mybinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
6
7 EndpointAddress ea = new EndpointAddress(new Uri("https://servername/WCFUserName/Service1.svc/ws1"));
8 Service1Client client = new Service1Client(mybinding, ea);
9
10 client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
11 client.ClientCredentials.UserName.UserName = "user";
12 client.ClientCredentials.UserName.Password = "pwd";
13 client.Open();
14 MessageBox.Show(client.GetUserName());
15 }
16 catch (SecurityTokenException ex)
17 {
18 MessageBox.Show(ex.Message.ToString());
19 }
20
OK,测试通过。测试环境:Windows XP操作系统、VS2008 WCF、VS2008 Windows应用程序。