VS2010 WCF用户名密码X.509验证

        WCF支持多种认证技术,例如Windowns认证、X509证书、Issued Tokens、用户名密码认证等,在跨Windows域分布的系统中,用户名密码认证还是比较常用的,要实现用户名密码认证,就必须需要X509证书,为什么呢?因为我们需要X509证书这种非对称密钥技术来实现WCF在Message传递过程中的加密和解密,要不然用户名和密码就得在网络上明文传递!详细说明就是客户端把用户名和密码用公钥加密后传递给服务器端,服务器端再用自己的私钥来解密,然后传递给相应的验证程序来实现身份验证。

       在网上搜了很多WCF认证相关文章,才最终把X.509用户名密码认证搞定,现在把自己的理解和经验总结如下:

       服务器环境:1、windows server 2003 sp2   2、IIS6.0

       客户端环境:1、winXP, win7

       开发环境:VS2010,HttpAnalyzer V5

 开发步骤:

1、用VS2010创建WCF服务应用程序,如下图所示。

WCF模板已经有默认的服务,在此不需另外编码就行。再次,首先我们要编写自己的用户名密码认证逻辑,先要在WCF项目上添加引用'System.IdentityModel'然后我们建立一个新的类文件并继承自'System.IdentityModel.Selectors.UserNamePasswordValidator',然后我们重写里面的'Validate'方法来实现用户名密码认证逻辑。代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IdentityModel;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;

namespace WCFServerOne
{
    public class MyCustomValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (userName != "lyx" || password != "123")
            {
                throw new SecurityTokenException("用户名或密码错误!");
            }
        }
    }
}
上面只是一个简单的验证,实际应用中用户名和密码一般都保存在数据库中,如果验证不通过就抛出一个'SecurityTokenException'类型的异常;下一步我们需要配置一下服务端的webConfig文件,在修改配置文件之前我们先在win2003 IIS6.0上生成x.509证书,命令是VS2010 Visual Studio Tools自带的makecert.exe,一般2003服务器上没有,你可直接拷贝过去。命令格式(在cmd窗口输入):

makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=MyServerCert -sky exchange –pe

1、需要注意的是需要查看win2003是否安装证书组件,如果没有安装,需要在控件面板添加删除程序中,安装win2003证书组件。在安装的过程中有可能需要安装CD。SP2的需要SP2 安装CD。

2、执行以上命令之后,在证书管理中就可以查看名为MyServerCert的证书了。接下证书就不需要去管它了(IIS也不需Https,我们只用X.509加密就行),我们来修改上面WCF服务的Web.Config文件,如下:

<?xml version="1.0" encoding="utf-8"?>
<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="mySecureBinding">
          <security mode="Message">
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </wsHttpBinding>  
    </bindings>
    <services>
      <service behaviorConfiguration="MySimpleServiceBehavior" name="WCFServerOne.MyServiceOne">
        <endpoint address="" binding="wsHttpBinding" bindingConfiguration="mySecureBinding"
          contract="WCFServerOne.IServiceOne">
          <identity>
            <dns value="MyServerCert" />
          </identity>
        </endpoint>
      </service>
    </services>
    
    <behaviors>
      <serviceBehaviors>
        <behavior name="MySimpleServiceBehavior">
          <!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 -->
          <serviceDebug includeExceptionDetailInFaults="false"/>

          <serviceCredentials>
            <serviceCertificate findValue="MyServerCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My"/>
            <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFServerOne.MyCustomValidator,WCFServerOne"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
  
</configuration>

security mode="Message"
这个需要配置成Message

endpoint 需要注意的上 contract 写的是接口,behaviorConfiguration="myClientBehavior"在下面定义,名字一定不能错,不然自定验证就不会生效,如上所示。

ServiceCertificate节中指定了我们的X509证书的位置,以用来加解密message,usernameAuthentication节中指定了我们自己的用户名密码验证逻辑。

1、用VS2010创建WCF服务应用程序,如下图所示。

在项目添加服务引用,服务引用是IIS发布的地址,如:http://118.186.240.182:8090/MyServiceOne.svc 就会像WebService一样生成一个代理类,调用形式也是一样如:

 private void button1_Click(object sender, EventArgs e)
        {
            MyWCFOne.ServiceOneClient client = new MyWCFOne.ServiceOneClient();
            client.ClientCredentials.UserName.UserName = "lyx";
            client.ClientCredentials.UserName.Password = "123";
            textBox1.Text = client.GetData(10);
        }

如果你有一个真正的X509证书,那么现在的代码就可以正常运行了。但是很不幸,我们的证书是测试用的,我们运行的时候出错:'X.509 certificate CN=MyServerCert 链生成失败。所使用的证书具有无法验证的信任链。请替换该证书或更改 certificateValidationMode。已处理证书链,但是在不受信任提供程序信任的根证书中终止',WCF无法验证测试证书的信任链,那我们要做的就是绕过这个信任验证,具体做法如下:

先要在Asp.net Web应用程序项目上添加引用'System.IdentityModel'然后我们建立一个新的类文件并继承自'System.IdentityModel.Selectors.X509CertificateValidator',然后我们重写里面的'Validate'方法来实现我们自己的X509认证逻辑,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Security.Cryptography.X509Certificates;

namespace WCFClient
{
    class MyX509Validator : X509CertificateValidator
    {
        public override void Validate(X509Certificate2 certificate)
        {
            if(certificate == null)
            throw new ArgumentNullException("X509认证证书为空");
        }
    }
}
你可以把Validate方法里面留空让所有的认证都通过,也可以自己定义认证逻辑,如果认证失败,就抛出' SecurityTokenValidationException'的异常,然后我们配置一下客户端的webconfig让它使用我们自己的X509认证,增加以下的配置节,并在' endpoint'节中指定 behaviorConfiguration="myClientBehavior"。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_IServiceOne" 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="Message">
                        <transport clientCredentialType="Windows" proxyCredentialType="None"
                            realm="" />
                        <message clientCredentialType="UserName" negotiateServiceCredential="true"
                            algorithmSuite="Default" />
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://118.186.240.182:8090/MyServiceOne.svc"
                binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IServiceOne"
                contract="MyWCFOne.IServiceOne" name="WSHttpBinding_IServiceOne" behaviorConfiguration="myClientBehavior">
                <identity>
                    <dns value="MyServerCert" />
                </identity>
            </endpoint>
        </client>
      <behaviors>
        <endpointBehaviors>
          <behavior name="myClientBehavior">
            <clientCredentials>
              <serviceCertificate>
                <authentication certificateValidationMode="Custom" customCertificateValidatorType="WCFClient.MyX509Validator,WCFClient"/>
              </serviceCertificate>
            </clientCredentials>
          </behavior>
        </endpointBehaviors>
      </behaviors>
    </system.serviceModel>
</configuration>
OK,客户端代码和配置完成,现在你可以运行自己的程序了,运行界面如下:

点击提交,会调用远程,GetData然后显示在文本框内,如下图所示:



猜你喜欢

转载自blog.csdn.net/seamonkey/article/details/8611204