SSL保护的RESTful WCF自托管服务通信失败问题求助
SSL保护的WCF REST服务通信失败问题排查
我正在实现一个带SSL加密的RESTful WCF服务,结果在客户端测试时遇到了通信失败的错误:
向‘https://123.123.123.123:5000/TestService/PostMsg’发送HTTP请求失败。该问题可能是由于HTTPS场景下服务器证书未在HTTP.SYS中正确配置,也可能是客户端与服务器的安全绑定不匹配。
目前已经确认无SSL保护的版本可以正常通信,服务器和客户端在同一台机器上运行,Windows Defender防火墙也开放了5000端口。下面是我实现的相关代码和证书生成脚本,希望能找到问题根源:
已尝试事项
- 完成无SSL保护的WCF服务通信,验证了服务核心逻辑正常
- 开放Windows Defender防火墙的5000端口
- 服务器与客户端在同一设备上测试
服务端实现代码
using System; using System.Security.Cryptography.X509Certificates; using System.ServiceModel; using System.ServiceModel.Description; using System.ServiceModel.Security; using System.ServiceModel.Web; namespace TestService { class Program { static WebServiceHost host; static void Main() { WebHttpBinding binding = new WebHttpBinding(); binding.Security.Mode = WebHttpSecurityMode.Transport; binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate; binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None; Uri uri = new Uri("https://localhost:5000/TestService"); host = new WebServiceHost(typeof(TestService)); ServiceEndpoint se = host.AddServiceEndpoint(typeof(ITestService), binding, uri); var behavior = new WebHttpBehavior(); behavior.FaultExceptionEnabled = false; behavior.HelpEnabled = true; behavior.DefaultOutgoingRequestFormat = WebMessageFormat.Json; behavior.DefaultOutgoingResponseFormat = WebMessageFormat.Json; se.EndpointBehaviors.Add(behavior); ServiceDebugBehavior debug = host.Description.Behaviors.Find<ServiceDebugBehavior>(); debug.IncludeExceptionDetailInFaults = true; ServiceMetadataBehavior metad = new ServiceMetadataBehavior(); metad.HttpGetEnabled = true; metad.HttpsGetEnabled = true; host.Description.Behaviors.Add(metad); var certificate = new X509Certificate2(@"D:\Work\TestService\ServerCert1.pfx", "paswd", X509KeyStorageFlags.UserKeySet); host.Credentials.ServiceCertificate.Certificate = certificate; host.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None; host.Open(); Console.WriteLine(string.Format(null, "URL : {0}", uri.ToString())); Console.WriteLine("Press <ENTER> to terminate"); Console.ReadLine(); host.Close(); } } }
接口定义
ITestService 契约
using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; namespace TestService { [ServiceContract] interface ITestService { [OperationContract] [WebInvoke(Method = "POST" , RequestFormat = WebMessageFormat.Json , UriTemplate = "/PostMsg" )] MessageData PostMsg(MessageData msg); } [DataContract] public class MessageData { [DataMember] public string Name { get; set; } [DataMember] public int Gender { get; set; } [DataMember] public int Age { get; set; } } }
服务实现类
using System; namespace TestService { class TestService : ITestService { public MessageData PostMsg(MessageData msg) { Console.WriteLine(string.Format(null, "Recieved Name: {0}, Gender:{1}, Age:{2}", msg.Name , msg.Gender , msg.Age)); return new MessageData() { Name = msg.Name, Gender = msg.Gender, Age = msg.Age + 1 }; } } }
客户端实现代码
using System; using System.Windows.Forms; using System.Security.Cryptography.X509Certificates; using System.ServiceModel; using System.ServiceModel.Description; using System.ServiceModel.Security; using System.ServiceModel.Web; using TestService; namespace TestClient { public partial class Form1 : Form { WebChannelFactory<ITestService> cf = null; ITestService channel = null; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { listBox1.HorizontalScrollbar = true; Uri uri = new Uri("https://123.123.123.123:5000/TestService"); EndpointAddress endpointAddress = new EndpointAddress(uri); cf = new WebChannelFactory<ITestService>(uri); WebHttpBinding binding = cf.Endpoint.Binding as WebHttpBinding; binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate; binding.Security.Mode = WebHttpSecurityMode.Transport; var behavior = new WebHttpBehavior(); behavior.FaultExceptionEnabled = false; behavior.HelpEnabled = true; behavior.DefaultOutgoingRequestFormat = WebMessageFormat.Json; behavior.DefaultOutgoingResponseFormat = WebMessageFormat.Json; cf.Endpoint.Behaviors.Add(behavior); var clientCertificate = new X509Certificate2(@"D:\Work\TestService\ServerCert1.pfx", "pswd", X509KeyStorageFlags.UserKeySet); cf.Credentials.ClientCertificate.Certificate = clientCertificate; cf.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None; channel = cf.CreateChannel(); } private void button1_Click(object sender, EventArgs e) { MessageData msg = new MessageData() { Name = "Taro", Gender = 1, Age = 3 }; MessageData rtn = channel.PostMsg(msg); listBox1.Items.Insert(0, string.Format("Name:{0}, Gender:{1}, Age{2} ", rtn.Name, rtn.Gender, rtn.Age)); } } }
PFX证书生成脚本
@echo ---------------------------------------------- @echo Script for creating self certificate @echo ---------------------------------------------- @set "TOOL_DIR=E:\Windows Kits\10\bin\10.0.18362.0\x86" @if not exist "%TOOL_DIR%" ( @echo Tools do not exists. %TOOL_DIR% @goto ERROR_EXIT ) @set "PATH=%TOOL_DIR%;%PATH%" @set "WORK_DIR=D:\Work\TestService" @if not exist %WORK_DIR% ( @echo Work folder does not exist. %WORK_DIR% @goto ERROR_EXIT ) cd /d %WORK_DIR% @openfiles > NUL 2>&1 @if NOT %ERRORLEVEL% EQU 0 ( @echo It is not being executed as an administor. goto ERROR_EXIT ) @SET /P ANS="Create a certificate file. Are you sure (Y / N)?" @if /i %ANS% NEQ y if /i %ANS% NEQ Y goto ERROR_EXIT del %WORK_DIR%\*.* @echo; @echo Create Self-Signed Certificate makecert -n "CN=ServerCN1" -a sha1 -eku 1.3.6.1.5.5.7.3.3 -r -sv ServerCert1.pvk ServerCert1.cer -cy authority -b 11/06/2019 -e 12/31/2019 @echo; @echo Create Software Publisher Certificate File cert2spc ServerCert1.cer ServerCert1.spc @echo; @echo Create Personal Information Exchange File pvk2pfx -pvk ServerCert1.pvk -spc ServerCert1.spc -po pswd -pfx ServerCert1.pfx -f @echo; :ERROR_EXIT @SET /P ANS="Finished."
排查与解决建议
结合你的代码和场景,我整理了几个最可能的问题点,你可以逐一排查:
1. 证书未绑定到HTTP.SYS端口(核心问题)
因为你用WebServiceHost托管而不是IIS,必须手动将SSL证书绑定到服务监听的5000端口,否则HTTP.SYS会拒绝HTTPS请求。执行以下步骤:
- 打开证书管理器,找到你的
ServerCN1证书,查看详细信息→指纹,复制指纹并去掉所有空格 - 以管理员身份运行命令提示符,执行:
(netsh http add sslcert ipport=0.0.0.0:5000 certhash=你的证书指纹 appid={12345678-1234-1234-1234-1234567890AB}appid可以是任意生成的GUID,用来标识你的应用)
2. 客户端证书配置错误
看你的代码,客户端加载的是服务器的PFX证书作为客户端凭证,这显然不合理:
- 如果你的场景不需要验证客户端证书,把服务端和客户端的
binding.Security.Transport.ClientCredentialType改成HttpClientCredentialType.None - 如果确实需要客户端证书验证,你需要单独生成客户端证书,并将客户端证书的公钥导入到服务器的受信任的根证书颁发机构存储中
3. 证书与访问地址不匹配
服务端证书的CN是ServerCN1,但客户端用123.123.123.123访问,即使你关闭了证书验证,也可能导致SSL握手失败:
- 测试时客户端改用
https://localhost:5000/TestService访问 - 或者重新生成证书,添加IP备用名称(SAN)包含
123.123.123.123
4. 证书存储权限问题
你加载证书时用了X509KeyStorageFlags.UserKeySet,如果服务是用本地系统账户运行,可能无法访问用户证书存储:
- 改用
X509KeyStorageFlags.MachineKeySet - 将证书导入到本地计算机的个人存储中,而不是当前用户的存储
内容的提问来源于stack exchange,提问作者user12345985
相关产品推荐
相关产品推荐

