关于WEB Service&WCF&WebApi实现身份验证之WCF篇(1)

简介:

WCF身份验证一般常见的方式有:自定义用户名及密码验证、X509证书验证、ASP.NET成员资格(membership)验证、SOAP Header验证、Windows集成验证、WCF身份验证服务(AuthenticationService),这些验证方式其实网上都有相关的介绍文章,我这里算是一个总结吧,顺便对于一些注意细节进行说明,以便大家能更好的掌握这些知识。

第一种:自定义用户名及密码验证(需要借助X509证书)

由于该验证需要借助于X509证书,所以我们需要先创建一个证书,可以利用MS自带的makecert.exe程序来制作测试用证书,使用步骤:请依次打开开始->Microsoft Visual Studio 2010(VS菜单,版本不同,名称有所不同)->Visual Studio Tools->Visual Studio 命令提示,然后执行以下命令:

makecert -r -pe -n "CN=ZwjCert" -ss TrustedPeople -sr LocalMachine -sky exchange

上述命令中除了我标粗的部份可改成你实际的请求外(为证书名称),其余的均可以保持不变,命令的意思是:创建一个名为ZwjCert的证书将将其加入到本地计算机的受信任人区域中。

如果需要查看该证书,那么可以通过MMC控制台查询证书,具体操作步骤如下:

运行->MMC,第一次打开Windows没有给我们准备好直接的管理证书的入口,需要自行添加,添加方法如下:

1. 在控制台菜单,文件→添加/删除管理单元→添加按钮→选”证书”→添加→选”我的用户账户”→关闭→确定
2. 在控制台菜单,文件→添加/删除管理单元→添加按钮→选”证书”→添加→选”计算机账户”→关闭→确定

这样MMC中左边就有菜单了,然后依次展开:证书(本地计算机)->受信任人->证书,最后就可以在右边的证书列表中看到自己的证书了,如下图示:

 

证书创建好,我们就可以开始编码了,本文主要讲的就是WCF,所以我们首先定义一个WCF服务契约及服务实现类(后面的各种验证均采用该WCF服务),我这里直接采用默认的代码,如下:

要实现用户名及密码验证,就需要定义一个继承自UserNamePasswordValidator的用户名及密码验证器类CustomUserNameValidator,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace  WcfAuthentications
{
     public  class  CustomUserNameValidator : UserNamePasswordValidator
     {
 
         public  override  void  Validate( string  userName,  string  password)
         {
             if  ( null  == userName ||  null  == password)
             {
                 throw  new  ArgumentNullException();
             }
             if  (userName !=  "admin"  && password !=  "wcf.admin" //这里可依实际情况下实现用户名及密码判断
             {
                 throw  new  System.IdentityModel.Tokens.SecurityTokenException( "Unknown Username or Password" );
             }
 
         }
     }
}

代码很简单,只是重写其Validate方法,下面就是将创建WCF宿主,我这里采用控制台程序

代码部份:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace  WcfHost
{
     class  Program
     {
         static  void  Main( string [] args)
         {
             using  ( var  host =  new  ServiceHost( typeof (Service1)))
             {
                 host.Opened +=  delegate
                 {
                     Console.WriteLine( "Service1 Host已开启!" );
                 };
                 host.Open();
                 Console.ReadKey();
             }
         }
     }
}

APP.CONFIG部份(这是重点,可以使用WCF配置工具来进行可视化操作配置,参见:http://www.cnblogs.com/Moosdau/archive/2011/04/17/2019002.html):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
< system.serviceModel >
   < bindings >
     < wsHttpBinding >
       < binding  name="Service1Binding">
         < security  mode="Message">
           < message  clientCredentialType="UserName" />
         </ security >
       </ binding >
     </ wsHttpBinding >
   </ bindings >
   < services >
     < service  behaviorConfiguration="Service1Behavior" name="WcfAuthentications.Service1">
       < endpoint  address="" binding="wsHttpBinding" bindingConfiguration="Service1Binding"
         contract="WcfAuthentications.IService1">
         < identity >
           < dns  value="ZwjCert" />
         </ identity >
       </ endpoint >
       < endpoint  address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
       < host >
         < baseAddresses >
           < add  baseAddress="http://localhost:8732/WcfAuthentications/Service1/" />
         </ baseAddresses >
       </ host >
     </ service >
   </ services >
   < behaviors >
     < serviceBehaviors >
       < behavior  name="Service1Behavior">
         < serviceMetadata  httpGetEnabled="true" />
         < serviceDebug  includeExceptionDetailInFaults="false" />
         < serviceCredentials >
           < serviceCertificate  findValue="ZwjCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="TrustedPeople" />
           < userNameAuthentication  userNamePasswordValidationMode="Custom"
             customUserNamePasswordValidatorType="WcfAuthentications.CustomUserNameValidator,WcfAuthentications" />
         </ serviceCredentials >
       </ behavior >
     </ serviceBehaviors >
   </ behaviors >
</ system.serviceModel >

这里面有几个需要注意的点:

1.<dns value="ZwjCert" />与<serviceCertificate findValue="ZwjCert" ..>中的value必需都为证书的名称,即:ZwjCert;

2.Binding节点中需配置security节点,message子节点中的clientCredentialType必需设为:UserName;

3.serviceBehavior节点中,需配置serviceCredentials子节点,其中serviceCertificate 中各属性均需与证书相匹配,userNameAuthentication的userNamePasswordValidationMode必需为Custom,customUserNamePasswordValidatorType为上面自定义的用户名及密码验证器类的类型及其程序集

最后就是在客户端使用了,先引用服务,然后看下App.Config,并进行适当的修改,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
< system.serviceModel >
     < bindings >
         < wsHttpBinding >
             < binding  name="WSHttpBinding_IService1" >
                 < security  mode="Message">
                     < transport  clientCredentialType="Windows" proxyCredentialType="None"
                         realm="" />
                     < message  clientCredentialType="UserName" negotiateServiceCredential="true"
                         algorithmSuite="Default" />
                 </ security >
             </ binding >
         </ wsHttpBinding >
     </ bindings >
     < client >
         < endpoint  address="http://localhost:8732/WcfAuthentications/Service1/"
             binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1"
             contract="ServiceReference1.IService1" name="WSHttpBinding_IService1">
             < identity >
                 < dns  value="ZwjCert" />
             </ identity >
         </ endpoint >
     </ client >
</ system.serviceModel >

为了突出重点,我这里对Binding节点进行了精简,去掉了许多的属性配置,仅保留重要的部份,如:security节点,修改其endpoint下面的identity中<dns value="ZwjCert" />,这里的value与服务中所说的相同节点相同,就是证书名称,如果不相同,那么就会报错,具体的错误消息大家可以自行试下,我这里限于篇幅内容就不贴出来了。

客户端使用服务代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
namespace  WCFClient
{
     class  Program
     {
         static  void  Main( string [] args)
         {
             using  ( var  proxy =  new  ServiceReference1.Service1Client())
             {
                 proxy.ClientCredentials.UserName.UserName =  "admin" ;
                 proxy.ClientCredentials.UserName.Password =  "wcf.admin" ;
                 string  result = proxy.GetData(1);
                 Console.WriteLine(result);
                 var  compositeObj = proxy.GetDataUsingDataContract( new  CompositeType() { BoolValue =  true , StringValue =  "test"  });
                 Console.WriteLine(SerializerToJson(compositeObj));
             }
             Console.ReadKey();
         }
 
         /// <summary>
         /// 序列化成JSON字符串
         /// </summary>
         static  string  SerializerToJson<T>(T obj)  where  T: class
         {
             var  serializer =  new  DataContractJsonSerializer( typeof (T));
             var  stream =  new  MemoryStream();
             serializer.WriteObject(stream,obj);
 
             byte [] dataBytes =  new  byte [stream.Length];
 
             stream.Position = 0;
             stream.Read(dataBytes, 0, ( int )stream.Length);
             string  dataString = Encoding.UTF8.GetString(dataBytes);
             return  dataString;
         }
     }
}

运行结果如下图示:

  

如果不传入用户名及密码或传入不正确的用户名及密码,均会报错:

第二种:X509证书验证


首先创建一个证书,我这里就用上面创建的一个证书:ZwjCert;由于服务器端及客户端均需要用到该证书,所以需要导出证书,在客户端的电脑上导入该证书,以便WCF可进行验证。

WCF服务契约及服务实现类与第一种方法相同,不再重贴代码。

WCF服务器配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
< system.serviceModel >
   < bindings >
     < wsHttpBinding >
       < binding  name="Service1Binding">
         < security  mode="Message">
           < message  clientCredentialType="Certificate" />
         </ security >
       </ binding >
     </ wsHttpBinding >
   </ bindings >
   < services >
     < service  behaviorConfiguration="Service1Behavior" name="WcfAuthentications.Service1">
       < endpoint  address="" binding="wsHttpBinding" bindingConfiguration="Service1Binding"
         contract="WcfAuthentications.IService1">
       </ endpoint >
       < endpoint  address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
       < host >
         < baseAddresses >
           < add  baseAddress="http://127.0.0.1:8732/WcfAuthentications/Service1/" />
         </ baseAddresses >
       </ host >
     </ service >
   </ services >
   < behaviors >
     < serviceBehaviors >
       < behavior  name="Service1Behavior">
         < serviceMetadata  httpGetEnabled="true" />
         < serviceDebug  includeExceptionDetailInFaults="false" />
         < serviceCredentials >
           < serviceCertificate  findValue="ZwjCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="TrustedPeople" />
           < clientCertificate >
             < authentication  certificateValidationMode="None"/>
           </ clientCertificate >
         </ serviceCredentials >
       </ behavior >
     </ serviceBehaviors >
   </ behaviors >
</ system.serviceModel >

这里需注意如下几点:

1.<message clientCredentialType="Certificate" />clientCredentialType设为:Certificate;

2.需配置serviceCredentials节点,其中serviceCertificate 中各属性均需与证书相匹配,clientCertificate里面我将authentication.certificateValidationMode="None",不设置采用默认值其实也可以;

客户端引用服务,自动生成如下配置信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
< system.serviceModel >
     < bindings >
         < wsHttpBinding >
             < binding  name="WSHttpBinding_IService1">
                 < security  mode="Message">
                     < transport  clientCredentialType="Windows" proxyCredentialType="None"
                         realm="" />
                     < message  clientCredentialType="Certificate" negotiateServiceCredential="true"
                         algorithmSuite="Default" />
                 </ security >
             </ binding >
         </ wsHttpBinding >
     </ bindings >
     < client >
         < endpoint  address="http://127.0.0.1:8732/WcfAuthentications/Service1/"
             binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1"
             contract="ServiceReference1.IService1" name="WSHttpBinding_IService1" behaviorConfiguration="Service1Nehavior">
             < identity >
                 < certificate  encodedValue="AwAAAAEAAAAUAAAAkk2avjNCItzUlS2+Xj66ZA2HBZYgAAAAAQAAAOwBAAAwggHoMIIBVaADAgECAhAIAOzFvLxLuUhHJRwHUUh9MAkGBSsOAwIdBQAwEjEQMA4GA1UEAxMHWndqQ2VydDAeFw0xNTEyMDUwMjUyMTRaFw0zOTEyMzEyMzU5NTlaMBIxEDAOBgNVBAMTB1p3akNlcnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALfGfsiYpIVKu3gPJl790L13+CZWt6doePZHmcjMl+xPQKIR2fDvsCq9ZxzapDgiG4T3mgcVKUv55DBiuHcpXDvXt28m49AjdKwp924bOGKPM56eweKDCzYfLxy5SxaZfA9qjUhnPq3kGu1lfWjXbsp1rKI1UhKJg5b2j0V7AOC3AgMBAAGjRzBFMEMGA1UdAQQ8MDqAEH/MEXV8FHNLtxvllQ5SMbihFDASMRAwDgYDVQQDEwdad2pDZXJ0ghAIAOzFvLxLuUhHJRwHUUh9MAkGBSsOAwIdBQADgYEAdBtBNTK/Aj3woH2ts6FIU3nh7FB2tKQ9L3k6QVL+kCR9mHuqWtYFJTBKxzESN2t0If6muiktcO+C8iNwYpJpPzLAOMFMrTQhkO82gcdr9brQzMWPTraK1IS+GGH8QBIOTLx9zfV/iCIXxRub+Sq9dmRSQjKDeLeHWoE5I6FkQJg=" />
             </ identity >
         </ endpoint >
     </ client >
   < behaviors >
     < endpointBehaviors >
       < behavior  name="Service1Nehavior">
         < clientCredentials >
           < clientCertificate  findValue="ZwjCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="TrustedPeople" />
         </ clientCredentials >
       </ behavior >
     </ endpointBehaviors >
   </ behaviors >
</ system.serviceModel >

可以看出endpoint节点下的identity.certificate的encodedValue包含了加密的数据,另外需要手动增加clientCertificate配置信息,该信息表示证书在本地电脑存放的位置,当然也可以通过代码来动态指定,如:proxy.ClientCredentials.ClientCertificate.SetCertificate("ZwjCert", StoreLocation.LocalMachine, StoreName.My);

客户端使用服务代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
static  void  Main( string [] args)
{
     using  ( var  proxy =  new  ServiceReference1.Service1Client())
     {
 
         //proxy.ClientCredentials.ClientCertificate.SetCertificate("ZwjCert", StoreLocation.LocalMachine, StoreName.My); //直接动态指定证书存储位置
         string  result = proxy.GetData(1);
         Console.WriteLine(result);
         var  compositeObj = proxy.GetDataUsingDataContract( new  CompositeType() { BoolValue =  true , StringValue =  "test"  });
         Console.WriteLine(SerializerToJson(compositeObj));
     }
     Console.ReadKey();
}

网上还有另类的针对X509证书验证,主要是采用了自定义的证书验证器类,有兴趣的可以参见这篇文章:http://www.cnblogs.com/ejiyuan/archive/2010/05/31/1748363.html

第三种:ASP.NET成员资格(membership)验证

 由于该验证需要借助于X509证书,所以仍然需要创建一个证书(方法如第一种中创建证书方法相同):ZwjCert;

由于该种验证方法是基于ASP.NET的membership,所以需要创建相应的数据库及创建账号,创建数据库,请通过运行aspnet_regsql.exe向导来创建数据库及其相关的表,通过打开ASP.NET 网站管理工具(是一个自带的管理网站),并在上面创建角色及用户,用于后续的验证;

这里特别说明一下,若采用VS2013,VS上是没有自带的GUI按钮来启动该管理工具网站,需要通过如下命令来动态编译该网站:

1
2
cd  C:\Program Files\IIS Express
iisexpress.exe  /path :C:\Windows\Microsoft.NET\Framework\v4.0.30319\ASP.NETWebAdminFiles  /vpath : /WebAdmin  /port :12345  /clr :4.0  /ntlm

编译时若出现报错:“System.Configuration.StringUtil”不可访问,因为它受保护级别限制,请将WebAdminPage.cs中代码作如下修改:

1
2
3
4
5
6
7
8
//取消部份:
string  appId = StringUtil.GetNonRandomizedHashCode(String.Concat(appPath, appPhysPath)).ToString( "x" , CultureInfo.InvariantCulture);
 
 
//新增加部份:
Assembly sysConfig = Assembly.LoadFile( @"C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Configuration.dll" );
Type sysConfigType = sysConfig.GetType( "System.Configuration.StringUtil" );
string  appId = (( int )sysConfigType.GetMethod( "GetNonRandomizedHashCode" ).Invoke( null new  object [] { String.Concat(appPath, appPhysPath),  true  })).ToString( "x" , CultureInfo.InvariantCulture);

这样就可以按照命令生生成的网址进行访问就可以了。如果像我一样,操作系统为:WINDOWS 10,那么不好意思,生成的网站虽然能够打开,但仍会报错:

遇到错误。请返回上一页并重试。

目前没有找到解决方案,网上有说ASP.NET网站管理工具在WIN10下不被支持,到底为何暂时无解,若大家有知道的还请分享一下(CSDN有别人的求问贴:http://bbs.csdn.net/topics/391819719),非常感谢,我这里就只好换台电脑来运行ASP.NET管理工具网站了。

 WCF服务端配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
< connectionStrings >
   < add  name="SqlConn" connectionString="Server=.;Database=aspnetdb;Uid=sa;Pwd=www.zuowenjun.cn;"/>
</ connectionStrings >
< system.web >
   < compilation  debug="true" targetFramework="4.5" />
   < httpRuntime  targetFramework="4.5"/>
   < membership  defaultProvider="SqlMembershipProvider">
     < providers >
       < clear />
       < add  name="SqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="SqlConn" applicationName="/" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" passwordFormat="Hashed"/>
     </ providers >
   </ membership >
</ system.web >
< system.serviceModel >
   < behaviors >
     < serviceBehaviors >
       < behavior  name="Service1Behavior">
         < serviceCredentials >
           < serviceCertificate  findValue="ZwjCert" storeLocation="LocalMachine"
             storeName="TrustedPeople" x509FindType="FindBySubjectName" />
           < userNameAuthentication  userNamePasswordValidationMode="MembershipProvider"
             membershipProviderName="SqlMembershipProvider" />
         </ serviceCredentials >
          < serviceMetadata  httpGetEnabled="true" httpsGetEnabled="true" />
         < serviceDebug  includeExceptionDetailInFaults="false" />
       </ behavior >
     </ serviceBehaviors >
   </ behaviors >
   < bindings >
     < wsHttpBinding >
       < binding  name="Service1Binding">
         < security  mode="Message">
           < message  clientCredentialType="UserName"/>
         </ security >
       </ binding >
     </ wsHttpBinding >
   </ bindings >
   < serviceHostingEnvironment  aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
< services >
   < service  name="WcfService1.Service1" behaviorConfiguration="Service1Behavior">
     < endpoint  address="" binding="wsHttpBinding" contract="WcfService1.IService1" bindingConfiguration="Service1Binding">
     </ endpoint >
   </ service >
</ services >
</ system.serviceModel >

这里需注意几点:

1.配置connectionString,连接到membership所需的数据库; 

2.配置membership,增加SqlMembershipProvider属性配置;

3.配置serviceCredential,与第一种基本相同,不同的是userNameAuthentication的配置:userNamePasswordValidationMode="MembershipProvider",membershipProviderName="SqlMembershipProvider";

4.配置Binding节点<message clientCredentialType="UserName"/>,这与第一种相同; 

客户端引用WCF服务,查看生成的配置文件内容,需确保Binding节点有以下配置信息:

1
2
3
< security  mode="Message">
         < message  clientCredentialType="UserName" />
  </ security >

最后使用WCF服务,使用代码与第一种相同,唯一需要注意的是,传入的UserName和Password均为ASP.NET网站管理工具中创建的用户信息。

另外我们也可以采用membership+Form验证,利用ASP.NET的身份验证机制,要实现这种模式,是需要采用svc文件,并寄宿在IIS上,具体实现方法,参见:http://www.cnblogs.com/danielWise/archive/2011/01/30/1947912.html

由于WCF的验证方法很多,本文无法一次性全部写完,敬请期待续篇!

本文转自 梦在旅途 博客园博客,原文链接:http://www.cnblogs.com/zuowj/p/5021270.html  ,如需转载请自行联系原作者

相关文章
【Azure 应用服务】Web App Service 中的 应用程序配置(Application Setting) 怎么获取key vault中的值
【Azure 应用服务】Web App Service 中的 应用程序配置(Application Setting) 怎么获取key vault中的值
247 0
【Azure App Service】PowerShell脚本批量添加IP地址到Web App允许访问IP列表中
Web App取消公网访问后,只允许特定IP能访问Web App。需要写一下段PowerShell脚本,批量添加IP到Web App的允许访问IP列表里!
328 2
|
关系型数据库 MySQL Linux
【Azure 应用服务】在创建Web App Service的时候,选Linux系统后无法使用Mysql in App
【Azure 应用服务】在创建Web App Service的时候,选Linux系统后无法使用Mysql in App
174 0
【Azure 应用服务】在创建Web App Service的时候,选Linux系统后无法使用Mysql in App
|
缓存 JavaScript 前端开发
Web Workers与Service Workers:后台处理与离线缓存
Web Workers 和 Service Workers 是两种在Web开发中处理后台任务和离线缓存的重要技术。它们在工作原理和用途上有显著区别。
403 1
|
Shell PHP Windows
【Azure App Service】Web Job 报错 UNC paths are not supported. Defaulting to Windows directory.
【Azure App Service】Web Job 报错 UNC paths are not supported. Defaulting to Windows directory.
294 0
|
Linux 应用服务中间件 网络安全
【Azure 应用服务】查看App Service for Linux上部署PHP 7.4 和 8.0时,所使用的WEB服务器是什么?
【Azure 应用服务】查看App Service for Linux上部署PHP 7.4 和 8.0时,所使用的WEB服务器是什么?
193 0
【Azure 应用服务】通过 Web.config 开启 dotnet 应用的 stdoutLog 日志,查看App Service 产生500错误的原因
【Azure 应用服务】通过 Web.config 开启 dotnet 应用的 stdoutLog 日志,查看App Service 产生500错误的原因
412 0
|
Linux Python
【Azure 应用服务】Azure App Service For Linux 上实现 Python Flask Web Socket 项目 Http/Https
【Azure 应用服务】Azure App Service For Linux 上实现 Python Flask Web Socket 项目 Http/Https
343 0
|
存储 安全 网络安全
【Azure 环境】使用Azure中的App Service部署Web应用,以Windows为主机系统是否可以启动防病毒,防恶意软件服务呢(Microsoft Antimalware)?
【Azure 环境】使用Azure中的App Service部署Web应用,以Windows为主机系统是否可以启动防病毒,防恶意软件服务呢(Microsoft Antimalware)?
279 0
|
存储 Linux 网络安全
【Azure 应用服务】App Service For Linux 如何在 Web 应用实例上住抓取网络日志
【Azure 应用服务】App Service For Linux 如何在 Web 应用实例上住抓取网络日志
207 0