在 WCF 分散式專案中,<identity> 元素常被視為可有可無的「盲腸」。許多開發者在安全模式為 None 時隨意刪除或修改,卻在啟用 Transport 或 Message 安全後遭遇詭異的 SecurityNegotiationException: 伺服器已拒絕客戶端憑證,錯誤訊息極不具體,耗時極久。本文深入剖析 IdentityElement(組態)與 EndpointIdentity(程式碼)的運作機制,並示範動態建立身分解決多伺服器/路由器場景。
預設組態:
<endpoint address="UserData" binding="netTcpBinding" contract="UserService.IUserData"
bindingConfiguration="EndpointBinding">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
若 <security mode="None">,客戶端與服務端的 identity 即使不符、甚至刪除,通訊依然正常。
當 <security mode="Transport"> 且 clientCredentialType="Windows"(無憑證)時,若服務端 identity 為 dns="localhost",客戶端刪除一個字元(如 localhos),即拋出 SecurityNegotiationException,錯誤訊息僅有「伺服器已拒絕客戶端憑證」,完全無法判斷根因。
若服務端啟用 mode="Message" 並配置 X.509 憑證,服務端預設 DNS 身分會被憑證主體名稱取代。例如憑證主體為 192168168151service,客戶端仍送 dns="localhost" 將拋出:
傳出訊息身分檢查失敗。預期遠端端點 DNS 身分為 "localhost",但遠端端點提供的 DNS 要求為 "192168168151service"。若遠端端點合法,可在建立通道代理時明確指定 DNS 身分 "192168168151service" 解決。
解決方式:在服務端組態明確指定 <dns value="localhost" /> 覆蓋預設行為,或客戶端動態指定正確身分。
EndpointIdentity 支援六種身分:DNS、Certificate、RSA、SPN、UPN、CertificateReference。最常用為 DNS。多伺服器/路由器環境下,靜態組態無法因應,需程式碼動態建立:
UserDataClient client = new UserDataClient();
EndpointIdentity identity = EndpointIdentity.CreateDnsIdentity("192168168151service");
AddressHeaderCollection headers = client.Endpoint.Address.Headers;
Uri uri = client.Endpoint.Address.Uri;
EndpointAddress remoteAddress = new EndpointAddress(uri, identity, headers.ToArray());
UserDataClient newClient = new UserDataClient(client.Endpoint.Binding, remoteAddress);
newClient.ClientCredentials.ClientCertificate.Certificate = client.ClientCredentials.ClientCertificate.Certificate;
client.Abort(); // 關閉舊通道
string msg = newClient.GetData(50);
<identity>。兩者功能等同,前者用於組態,後者用於程式碼。驗證流程:客戶端發起呼叫 → 服務端回傳身分 → 客戶端比對預期身分 → 不符則拋出例外。
<dns value="實際主機名/FQDN" />,勿依賴預設 localhost。EndpointAddress 建構策略。IdentityVerificationFailure 事件快速定位。