在早期使用 Web Service 建構分散式系統時,身分驗證一直是個棘手的問題。傳統做法需繼承 SoapHeader,將帳號密碼放入其中,每次呼叫方法都得傳遞認證資訊給伺服器驗證,不僅效率低落、程式碼繁瑣,更存在明文傳輸的安全隱憂。
WCF 內建支援 Windows 驗證、X.509 憑證、Issued Tokens 及用戶名密碼等多種驗證技術。在跨 Windows 網域的分散式系統中,用戶名密碼認證仍是最常採用的方式。然而,實作此機制必須搭配 X.509 憑證,原因在於需要非對稱加密技術保護訊息傳遞過程中的用戶名密碼,避免在網路上明文傳輸。運作流程為:客戶端使用公開金鑰加密認證資訊傳送,伺服器端以私鑰解密後交由驗證邏輯處理。
開發測試階段無需申請正式數位憑證,微軟提供 makecert.exe 工具產生測試憑證。在命令提示字元執行:
makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=MyServerCert -sky exchange –pe
此命令會在「LocalMachine」存儲區的「My」資料夾建立主體名稱為「MyServerCert」的測試憑證。更多參數說明請參考 MSDN 文件。
在 WCF 專案加入 System.IdentityModel 參考,新增類別繼承 UserNamePasswordValidator 並覆寫 Validate 方法:
using System;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
namespace ServerWcfService.CustomValidators
{
public class MyCustomValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentNullException("userName");
if (string.IsNullOrEmpty(password))
throw new ArgumentNullException("password");
// 實際應用中應從資料庫驗證
if (userName != "xiaozhuang" || password != "123456")
throw new SecurityTokenException("用戶名或者密碼錯誤!");
}
}
}
驗證失敗時拋出 SecurityTokenException,實際專案中帳密通常存放於資料庫。
重點在於 <system.serviceModel> 區段設定:
wsHttpBinding 的安全模式為 Message,客戶端認證類型為 UserName<identity><dns value="MyServerCert"/></identity>,值需與憑證 CN 名稱一致<serviceCertificate>:指定憑證位置(LocalMachine/My/FindBySubjectName/MyServerCert),用於訊息加解密<userNameAuthentication>:設定 userNamePasswordValidationMode="Custom" 並指定自訂驗證器型別protected void btnPrint_Click(object sender, EventArgs e)
{
TestWCFService.MySimpleServiceClient client = new ClientWeb.TestWCFService.MySimpleServiceClient();
client.ClientCredentials.UserName.UserName = "xiaozhuang";
client.ClientCredentials.UserName.Password = "123456";
lbMessage.Text = client.PrintMessage(txtMessage.Text);
}
測試憑證非受信任根憑證機構簽發,直接執行會拋出「X.509 certificate CN=MyServerCert 鏈生成失敗」錯誤。解決方法是實作自訂 X509CertificateValidator:
using System.Configuration;
using System.IdentityModel.Selectors;
using System.Security.Cryptography.X509Certificates;
namespace ClientWeb.CustomX509Validator
{
public class MyX509Validator : X509CertificateValidator
{
public override void Validate(X509Certificate2 certificate)
{
if (certificate == null)
throw new ArgumentNullException("X509認證證書為空!");
// 僅驗證主體名稱,實際專案應加強驗證邏輯
if (certificate.SubjectName.Name != ConfigurationManager.AppSettings["CertName"])
throw new SecurityTokenValidationException("Certificated was not issued by thrusted issuer");
}
}
}
在客戶端 Web.config 加入 <endpointBehaviors>,設定 certificateValidationMode="Custom" 並指定自訂驗證器型別,於 <endpoint> 套用 behaviorConfiguration="myClientBehavior" 即可正常執行。
