自托管WCF服务证书认证报错:HTTP请求因Anonymous方案被禁止
我有一个自托管WCF服务的Windows服务,当前使用不安全的HTTP连接。已从可信CA购买SSL证书,且该证书已成功用于保护我的Web应用。
希望为Windows服务配置安全机制,让仅安装了指定证书的服务器和客户端能通过认证连接WCF服务。作为测试,创建了简单的主机/客户端控制台应用来验证证书安全性。
目前访问服务基准地址显示已加密,但客户端尝试连接并调用服务方法时,出现错误:
The HTTP request was forbidden with client authentication scheme 'Anonymous'
已在客户端app.config中配置了向WCF服务出示证书,不应以匿名方式连接。尝试修改IIS认证设置,但因WCF并非托管在IIS(测试时为控制台应用,实际是Windows服务),所以无效。
证书已安装在服务器和客户端的本地计算机-受信任的根证书颁发机构、本地计算机-个人存储,甚至试过放入“受信任的人”存储。
主机代码与配置
主机Program.cs(实际URL已替换为testendpoint)
using System; using System.ServiceModel; namespace SimplifiedHostService { [ServiceContract] public interface ISimplifiedHostServiceContract { [OperationContract] string SimplifiedHostServiceMethod(string input); } public class SimplifiedHostService : ISimplifiedHostServiceContract { public string SimplifiedHostServiceMethod(string input) { return "You said: " + input; } } class Program { static void Main(string[] args) { Uri baseAddress = new Uri("https://testendpoint:8000/SimplifiedHostService"); // Create a ServiceHost instance using (ServiceHost host = new ServiceHost(typeof(SimplifiedHostService), baseAddress)) { try { host.Open(); Console.WriteLine("The service is ready."); Console.WriteLine("Press <Enter> to stop the service."); Console.ReadLine(); host.Close(); } catch (CommunicationException ex) { Console.WriteLine("An exception occurred: {0}", ex.Message); host.Abort(); } } } } }
主机app.config
<configuration> <system.serviceModel> <services> <service name="SimplifiedHostService.SimplifiedHostService"> <endpoint address="" binding="basicHttpBinding" bindingConfiguration="secureBinding" contract="SimplifiedHostService.ISimplifiedHostServiceContract" /> </service> </services> <bindings> <basicHttpBinding> <binding name="secureBinding"> <security mode="Transport"> <transport clientCredentialType="Certificate" /> </security> </binding> </basicHttpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpsGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="false" /> <serviceCredentials> <serviceCertificate storeLocation="LocalMachine" storeName="Root" x509FindType="FindBySubjectName" findValue="TestCertificateSubjectName" /> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
客户端代码与配置
客户端Program.cs
// Shared service contract interface (copied from server project) using System.ServiceModel; using System; namespace SimplifiedHostService { [ServiceContract] public interface ISimplifiedHostServiceContract { [OperationContract] string SimplifiedHostServiceMethod(string input); } } namespace WCFClient { class Program { static void Main(string[] args) { // Create a channel to the service using the shared service contract interface var factory = new ChannelFactory<SimplifiedHostService.ISimplifiedHostServiceContract>("SimplifiedHostService"); SimplifiedHostService.ISimplifiedHostServiceContract channel = null; try { // Create a channel to the service channel = factory.CreateChannel(); // Call the service method string result = channel.SimplifiedHostServiceMethod("Hello from client"); // Display the result Console.WriteLine("Result from service: " + result); } catch (Exception ex) { Console.WriteLine("An error occurred: " + ex.Message); } finally { // Close the channel factory and the channel if (channel != null && channel is ICommunicationObject) { ((ICommunicationObject)channel).Close(); } factory.Close(); } Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } } }
客户端app.config
<configuration> <system.serviceModel> <client> <endpoint name="SimplifiedHostService" address="https://testendpoint:8000/SimplifiedHostService" binding="basicHttpBinding" bindingConfiguration="secureBinding" behaviorConfiguration="test" contract="SimplifiedHostService.ISimplifiedHostServiceContract" /> </client> <bindings> <basicHttpBinding> <binding name="secureBinding"> <security mode="Transport"> <transport clientCredentialType="Certificate" /> </security> </binding> </basicHttpBinding> </bindings> <behaviors> <endpointBehaviors> <behavior name="test"> <clientCredentials> <!-- Specify the client certificate --> <clientCertificate storeLocation="LocalMachine" storeName="Root" findValue="TestCertificateSubjectName" x509FindType="FindBySubjectName"/> </clientCredentials> </behavior> </endpointBehaviors> </behaviors> </system.serviceModel> </configuration>
1. 修正证书存储位置配置
服务器端
服务证书应放在**LocalMachine\My(个人)**存储,Root存储仅用于CA证书,将服务证书放在此处会导致权限和识别问题。修改主机app.config中的serviceCertificate配置:
<serviceCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" findValue="TestCertificateSubjectName" />
客户端
客户端证书同样应放在LocalMachine\My存储,修改客户端app.config中的clientCertificate配置:
<clientCertificate storeLocation="LocalMachine" storeName="My" findValue="TestCertificateSubjectName" x509FindType="FindBySubjectName"/>
2. 配置服务器端证书验证规则
在服务器的serviceCredentials中添加clientCertificate配置,明确指定验证客户端证书的规则,确保仅信任指定CA颁发的证书或特定客户端证书:
<serviceCredentials> <serviceCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" findValue="TestCertificateSubjectName" /> <clientCertificate> <authentication certificateValidationMode="ChainTrust" revocationMode="NoCheck" /> <!-- 如需限制仅特定客户端证书,可添加allowedCertificates --> <!-- <allowedCertificates> <add findValue="ClientCertificateSubject" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="Root" /> </allowedCertificates> --> </clientCertificate> </serviceCredentials>
ChainTrust:验证客户端证书的信任链,确保由可信CA颁发- 若需更严格控制,可改为
PeerTrust或Custom,并实现自定义验证逻辑
3. 确保证书权限设置
服务器端
给运行WCF服务的账户(Windows服务账户或控制台运行账户)授予读取服务证书私钥的权限:
- 打开管理控制台 -> 添加证书管理单元,选择本地计算机
- 展开个人 -> 证书,找到服务证书
- 右键证书 -> 所有任务 -> 管理私钥
- 添加运行服务的账户,授予读取权限
客户端
运行客户端的账户也需要读取客户端证书私钥的权限,重复上述步骤配置客户端证书的私钥权限。
4. 绑定端口与SSL证书
自托管WCF服务使用HTTPS时,需确保端口已正确绑定SSL证书。使用以下命令操作:
# 检查当前SSL绑定 netsh http show sslcert # 绑定证书到指定端口(替换Thumbprint为证书指纹,Port为8000) netsh http add sslcert ipport=0.0.0.0:8000 certhash=你的证书指纹 appid={00000000-0000-0000-0000-000000000000}
- 证书指纹可在证书属性的详细信息选项卡中找到(去掉空格)
appid可以是任意GUID,用于标识应用
5. 调试与日志排查
若问题仍存在,开启WCF跟踪日志排查细节:
在主机app.config中添加跟踪配置:
<system.diagnostics> <sources> <source name="System.ServiceModel" switchValue="Information, ActivityTracing" propagateActivity="true"> <listeners> <add name="traceListener" type="System.Diagnostics.XmlWriterTraceListener" initializeData="c:\logs\WcfTrace.svclog" /> </listeners> </source> </sources> </system.diagnostics>
生成的日志文件可通过服务跟踪查看器(SvcTraceViewer.exe)分析,定位证书认证失败的具体原因。
内容的提问来源于stack exchange,提问作者Nav Pandher

