上文我們演示了,客戶端對服務器端身份的驗證,這一篇來簡單演示一下對客戶端身份的驗證。比如我們發布的服務,只想讓若干客戶端調用和訪問。這種情況應該怎么做呢,這就是今天要演示的客戶端認證。
對客戶端的認證基本分為三種:
1、 windows 身份驗證
2、 用戶名密碼方式驗證
3、 證書認證
我們今天主要用第二種方式來對客戶端進行驗證。
WCF的傳輸安全中,HttpClientCredentialType 提供了6種客戶端憑證來體現服務端對客戶端的認證方式,(以下對6種認證方式的解釋,摘自蔣金楠《WCF技術剖析》):
None: 客戶端無須指定用戶憑證,即匿名認證。
Basic:采用Basic認證方式對客戶端進行認證,這種方式客戶端需要提供有效的用戶名和密碼,但僅采用較弱的方式對密碼進行加密。當且僅當確定客戶端和服務器端直接的連接絕對安全的情況下采用此方式。
Digest:Digest認證提供與Basic類似的認證功能,但是安全性有所提升,主要體現在並不是將用戶名和密碼直接進行網絡傳輸,而是對其進行MD5 運算得到一個哈希碼,最終傳輸的是該哈希碼。
Ntlm:表示基於NTLM方式的windows集成認證。
Windows:表示使用windows集成認證,如果能夠使用Kerberos,則直接采用Kerberos進行認證,否則才使用NTLM
Certificate:表示客戶端的身份通過一個X.509數字證書表示,服務端通過校驗證書的方式來確定客戶端的真實身份。
Windows 身份的驗證 基本上計算機都需要加入域的,需要域控制器來處理客戶端的身份。有點復雜,我們就實驗一簡單的用戶名密碼方式來對客戶端進行驗證。說白了,就是服務器端提供證書,客戶端通過該證書對服務器端進行身份的驗證,服務器端根據客戶端提供的用戶名密碼來判斷是否能允許調用服務。
我們還用我們上一篇的例子就可以,將代碼稍微改動一下,就可以實現。
首先,我們 將 Host_Server 中App.config 文件中 bindings 節點中
<transport clientCredentialType="None"/>
修改為:
<transport clientCredentialType="Basic"/>
這時候我們重新生成一下Host_Server,並使其運行后,更新一下Client1 的服務引用,再運行Client1,我們會發現服務調用失敗,我們拋出這個錯誤提示,如下圖:
由於我們修改basic方式對客戶端進行認證,這時候我們的客戶端就需要提供一個用戶名和密碼來訪問這個服務,這個訪問服務的密碼是通過生成的客戶端代理類 中的一個屬性 ClientCredentials.UserName.UserName 和 ClientCredentials.UserName.Password 來提供的。但是我們在WCF服務中什么地方來驗證客戶端提供的用戶名和密碼呢?比如能夠提供用戶名 admin 和密碼 123456 的客戶端,我們都認為是合法的客戶端,都允許和服務進行通信。我們在WCF服務端可以這樣進行判斷:
首先,在我們的服務類庫LxServices中添加引用:System.IdentityModel 和 System.IdentityModel.Selectors,並增加一個自定義身份驗證類 CustomIdentityVerification 使其繼承 UserNamePasswordValidator類,並重寫其中的 Validate 方法,代碼如下:
using System; using System.IdentityModel; using System.IdentityModel.Selectors; using System.IdentityModel.Tokens; namespace LxServices { public class CustomIdentityVerification : UserNamePasswordValidator { public override void Validate(string userName, string password) { //這里可以對通過輸入的用戶名密碼進行驗證,比如可以在數據庫中進行查詢 if (userName != "admin" || password != "123456") { throw new SecurityTokenException("用戶名密碼錯誤!"); } } } }
然后,我們需要修改WCF宿主程序的配置文件App.config,使我們的這個自定義的驗證類生效,因此在服務行為serviceBehaviors節點中增加:
<!--服務器端提供證書--> <serviceCredentials> <serviceCertificate storeName="My" x509FindType="FindBySubjectName" findValue="Lx-PC" storeLocation="LocalMachine"/> <clientCertificate > <authentication certificateValidationMode="None"/> </clientCertificate> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="LxServices.CustomIdentityVerification,LxServices" /> </serviceCredentials>
以下是宿主程序完整的App.config文件代碼:

<?xml version="1.0"?> <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="LxBehavior"> <!--不提供通過瀏覽器輸入https訪問元數據的方式--> <serviceMetadata httpsGetEnabled="false"/> <serviceDebug includeExceptionDetailInFaults="false"/> <!--服務器端提供證書--> <serviceCredentials> <serviceCertificate storeName="My" x509FindType="FindBySubjectName" findValue="Lx-PC" storeLocation="LocalMachine"/> <clientCertificate> <authentication certificateValidationMode="None"/> </clientCertificate> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="LxServices.CustomIdentityVerification,LxServices"/> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> <bindings> <wsHttpBinding> <binding name="LxWsHttpBinding"> <security mode="Transport"> <!--采用傳輸安全,客戶端憑證=Basic--> <transport clientCredentialType="Basic"/> <message clientCredentialType="None"/> </security> </binding> </wsHttpBinding> </bindings> <services> <service name="LxServices.BookService" behaviorConfiguration="LxBehavior"> <endpoint address="BookService" binding="wsHttpBinding" bindingConfiguration="LxWsHttpBinding" contract="LxContracts.IBookContract"/> <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/> <host> <baseAddresses> <!--基地址是https--> <add baseAddress="https://Lx-PC:9000/"/> </baseAddresses> </host> </service> </services> <serviceHostingEnvironment aspNetCompatibilityEnabled="false"/> </system.serviceModel> </configuration>
啟動服務器端宿主程序后,更新Client1 的服務引用,並修改調用代碼如下:

using System; using System.Collections.Generic; using System.ServiceModel; using System.ServiceModel.Security; using Client1.WCF.BookSrv; namespace Client1 { class Program { static void Main(string[] args) { Console.WriteLine("請輸入您的用戶名:"); string userName = Console.ReadLine(); Console.WriteLine("請輸入您的密碼:"); string passWord = Console.ReadLine(); WCF.BookSrv.BookContractClient proxyClient = new WCF.BookSrv.BookContractClient(); proxyClient.ClientCredentials.UserName.UserName = userName; proxyClient.ClientCredentials.UserName.Password = passWord; List<Books> listBook = new List<Books>(); try { listBook = proxyClient.GetAllBooks(); listBook.ForEach(b => { Console.WriteLine("服務調用成功:"); Console.WriteLine("---------------"); Console.WriteLine("圖書編號:{0}", b.BookId); Console.WriteLine("圖書名稱:《{0}》", b.BookName); Console.WriteLine("圖書價格:{0}¥", b.Price); Console.WriteLine("---------------"); }); } catch(Exception ex) { Console.WriteLine("服務調用失敗,原因如下:"); Console.WriteLine(ex.Message); } Console.ReadKey(); } } }
我們的代碼修改完成,我們運行一下客戶端Client1,並輸入用戶名 admin 和密碼 123456,可以成功調用服務:
我們輸入一次錯誤的用戶名和密碼,比如輸入 123,就不會成功調用服務,結果如下圖所示:
至此,我們利用用戶名密碼方式對客戶端進行驗證的這個Demo ,就結束了,下一篇我們來演示一下傳輸安全中對客戶端也使用證書方式的認證。