C# VSTO Excel插件连接不受信任HTTPS服务器异常求助
问题描述
使用C#开发Excel VSTO插件,功能为从指定URL下载数据并填充工作表。连接HTTP或证书链受信任的HTTPS服务器时运行正常,但连接证书不受信任的HTTPS服务器时抛出异常,调试发现异常发生在request.GetRequestStream()调用处。已尝试HttpWebRequest和HttpClient两种方式均未解决,替换为可信HTTPS地址(如https://www.google.com)则可正常连接,不想安装目标服务器的证书。
HttpWebRequest代码:
private void BtnCreateDataSource_Click(object sender, EventArgs e) { ServicePointManager.ServerCertificateValidationCallback += (messageSender, cert, chain, sslPolicyErrors) => true; string data_str = @"{ ""grant_type"": ""password"", ""username"": ""xxx"", ""password"": ""xxx"", ""scope"": ""openid https://xxxx"" }"; var data = Encoding.ASCII.GetBytes(data_str); HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://xxx/oidc/grantToken"); //HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://www.google.com"); request.Method = "POST"; request.ContentType = "application/json"; request.ContentLength = data.Length; StreamWriter requestWriter = new StreamWriter(request.GetRequestStream(), System.Text.Encoding.ASCII); using (var stream = request.GetRequestStream()) { stream.Write(data, 0, data.Length); } var response = (HttpWebResponse)request.GetResponse(); var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); MessageBox.Show(responseString); }
异常信息(已翻译):
************** 异常文本 ************** System.Net.WebException: 基础连接已关闭: 发送时发生意外错误。 ---> System.IO.IOException: 验证失败,因为远程方关闭了传输流。 v System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest) v System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest) v System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest) v System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest, Boolean renegotiation) v System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult) v System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) v System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) v System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) v System.Net.TlsStream.ProcessAuthentication(LazyAsyncResult result) v System.Net.TlsStream.Write(Byte[] buffer, Int32 offset, Int32 size) v System.Net.PooledStream.Write(Byte[] buffer, Int32 offset, Int32 size) v System.Net.ConnectStream.WriteHeaders(Boolean async) --- 内部异常堆栈跟踪结束 --- v System.Net.HttpWebRequest.GetRequestStream(TransportContext& context) v System.Net.HttpWebRequest.GetRequestStream() v uuTS.FormCreateDataSource.BtnCreateDataSource_Click(Object sender, EventArgs e) v C:\Dropbox\projekty\uuTS\uuTS\FormCreateDataSource.cs:řádek 202 v System.Windows.Forms.Control.OnClick(EventArgs e) v System.Windows.Forms.Button.OnClick(EventArgs e) v System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent) v System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks) v System.Windows.Forms.Control.WndProc(Message& m) v System.Windows.Forms.ButtonBase.WndProc(Message& m) v System.Windows.Forms.Button.WndProc(Message& m) v System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
HttpClient尝试代码:
public bool LoadToken() { string data_str = @"{ ""grant_type"": ""password"", ""username"": """" + this.Login + @"""", ""password"": """" + this.Password + @"""", ""scope"": ""openid "" + this.ApiUrl + @"""" }"; ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; var handler = new HttpClientHandler(); handler.ClientCertificateOptions = ClientCertificateOption.Manual; handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => { return true; }; using (HttpClient client = new HttpClient(handler)) { ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls; ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; { client.BaseAddress = new Uri(this.OidcUrl); HttpResponseMessage response = client.PostAsync(this.OidcUrl, new StringContent(data_str, Encoding.UTF8, "application/json")).Result; if (response.IsSuccessStatusCode) { // Parse the response body. string responseStr = response.Content.ReadAsStringAsync().Result; this.Token = JsonConvert.DeserializeObject<TokenJson>(responseStr); this.TokenValidUntil = DateTime.Now.AddSeconds(this.Token.expires_in - 30); return true; } else { Log.Error("Error " + ((int)response.StatusCode).ToString() + " (" + response.ReasonPhrase + ")"); return false; } } } }
解决方案
全局提前设置证书验证回调
在VSTO插件的启动入口(如ThisAddIn_Startup方法)中设置证书验证回调,避免Excel安全上下文阻止临时设置生效,同时指定兼容的TLS版本:private void ThisAddIn_Startup(object sender, System.EventArgs e) { // 全局跳过证书验证 ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; // 启用主流TLS版本,避免协议不兼容 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13; }修正HttpWebRequest冗余操作
移除重复调用request.GetRequestStream()的代码,改用UTF-8编码避免字符乱码:private void BtnCreateDataSource_Click(object sender, EventArgs e) { string data_str = @"{ ""grant_type"": ""password"", ""username"": ""xxx"", ""password"": ""xxx"", ""scope"": ""openid https://xxxx"" }"; var data = Encoding.UTF8.GetBytes(data_str); HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://xxx/oidc/grantToken"); request.Method = "POST"; request.ContentType = "application/json"; request.ContentLength = data.Length; // 仅调用一次GetRequestStream using (var stream = request.GetRequestStream()) { stream.Write(data, 0, data.Length); } // 用using包裹资源,自动释放 using (var response = (HttpWebResponse)request.GetResponse()) using (var reader = new StreamReader(response.GetResponseStream())) { var responseString = reader.ReadToEnd(); MessageBox.Show(responseString); } }优化HttpClient代码逻辑
移除重复的ServicePointManager设置,修正BaseAddress与请求URL的冲突问题:public bool LoadToken() { // 用字符串插值简化JSON构造 string data_str = $@"{{ ""grant_type"": ""password"", ""username"": ""{this.Login}"", ""password"": ""{this.Password}"", ""scope"": ""openid {this.ApiUrl}"" }}"; var handler = new HttpClientHandler(); // 明确跳过证书验证 handler.ServerCertificateCustomValidationCallback = (_, cert, _, _) => true; using (HttpClient client = new HttpClient(handler)) { // 直接传入完整端点URL,无需设置BaseAddress HttpResponseMessage response = client.PostAsync(this.OidcUrl, new StringContent(data_str, Encoding.UTF8, "application/json")).Result; if (response.IsSuccessStatusCode) { string responseStr = response.Content.ReadAsStringAsync().Result; this.Token = JsonConvert.DeserializeObject<TokenJson>(responseStr); this.TokenValidUntil = DateTime.Now.AddSeconds(this.Token.expires_in - 30); return true; } else { Log.Error($"Error {(int)response.StatusCode} ({response.ReasonPhrase})"); return false; } } }检查Excel安全策略
确保Excel允许插件的网络访问权限:- 打开Excel,依次点击「文件」->「选项」->「信任中心」->「信任中心设置」
- 进入「宏设置」,确认插件处于信任状态(选择「启用所有宏」或「启用签署的宏」且插件已签名)
- 进入「外部内容」,设置允许来自所有位置的外部数据访问
内容的提问来源于stack exchange,提问作者PEV Prague
相关产品推荐
相关产品推荐

