将我的asp.net核心Web API项目从2.2更新到3.1后,当尝试通过System.Net.Mail.SmtpClient
发送电子邮件时,出现以下异常:
- 2020-02-09 14:40:26.8163 15 ( SmtpClient.OnSendCompleted => <>c__DisplayClass78_0.<SendMailAsync>b__0 => SmtpClient.HandleCompletion ) - Error Failed to send E-Mail. -- ( Exception: System.Net.Mail.SmtpException: Failure sending mail.
---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
at System.Net.Security.SslStream.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
--- End of stack trace from previous location where exception was thrown ---
at System.Net.Security.SslStream.ThrowIfExceptional()
at System.Net.Security.SslStream.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
at System.Net.Security.SslStream.EndProcessAuthentication(IAsyncResult result)
at System.Net.Security.SslStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
at System.Net.Mail.SmtpConnection.ConnectAndHandshakeAsyncResult.TlsStreamAuthenticateCallback(IAsyncResult result)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source)
at System.Net.Mail.SmtpConnection.ConnectAndHandshakeAsyncResult.End(IAsyncResult result)
at System.Net.Mail.SmtpTransport.EndGetConnection(IAsyncResult result)
at System.Net.Mail.SmtpClient.ConnectCallback(IAsyncResult result)
--- End of inner exception stack trace ---
[API的端点是通过appsettings.json中的Kestrel部分配置的
"Kestrel": {
"EndPoints": {
"Http": {
"Url": "http://myapidomain:80"
},
"Https": {
"Url": "https://www.myapidomain:443",
"Certificate": {
"Path": <path to pfx file>,
"Password": <password>
}
}
}
},
这是我使用SmtpClient
的方式:
using (var client = new SmtpClient(_options.MailServiceHost, _options.MailServicePort))
{
client.EnableSsl = true;
client.ClientCertificates.Add(_certificate);
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = false;
client.Credentials = new NetworkCredential(_options.MailAccountName, _options.MailAccountPassword);
var mail = new MailMessage
{
From = new MailAddress(author),
Body = body,
Subject = subject,
Sender = new MailAddress(author),
SubjectEncoding = Encoding.UTF8,
BodyEncoding = Encoding.UTF8,
HeadersEncoding = Encoding.UTF8
};
mail.Headers.Add("Content-Type", "content=text/html; charset=\"UTF-8\"");
mail.To.Add(receiver);
mail.ReplyToList.Add(author);
_logger.LogInformation($"Sending mail to: {receiver}");
await client.SendMailAsync(mail);
return true;
}
以上代码在我切换回aspnetcore2.2
版本时有效,因此我怀疑这是证书本身存在的问题(我对两者都使用完全相同的.pfx文件)。另一个重要提示可能是,当使用Windows计算机上的自签名开发证书在本地调试时,它实际上可以在aspnetcore3.1
上运行。在生产中,上述代码失败,我在Ubuntu上运行API。
我的猜测是,在asp.net-core 3.x中处理证书的方式有所改变。或Windows库的行为不同。如果需要,我也可以发布两个Startup.cs文件。
任何建议都可以获取有关实际原因或解决方法的更多详细信息?
更新
正如亚当建议的那样,我已切换到MailKit的SmtpClient
实现。该错误仍然存在于生产中,但是我设法使用client.ServerCertificateValidationCallback
来获取有关该错误的更多详细信息。检查回调的ChainStatus
属性时,我得到以下日志打印:
X509ChainStatusFlags: RevocationStatusUnknown
StatusInformation: unable to get certificate CRL
有人可以解释为什么无法获得证书CRL导致我的连接失败吗?我该如何解决?
更新2
而且,当我设置client.CheckCertificateRevocation = false
时,出现以下错误:
X509ChainStatusFlags: PartialChain
StatusInformation: unable to get local issuer certificate
更新3
似乎netcore框架找不到所需的ca证书,因为当我在Ubuntu计算机上运行以下命令时,我得到与Update 2完全相同的错误消息:
openssl s_client -connect smtp.gmail.com:465
--> Verify return code: 20 (unable to get local issuer certificate)
但是,当我明确指定ca证书的存储路径验证通过时。
openssl s_client -CApath /etc/ssl/certs/ -connect smtp.gmail.com:465
--> Verify return code: 0 (ok)
所以我猜最后一个问题是:如何获取我的aspnet核心Web API来找到Ubuntu的ca证书存储?
因此,如果查看SmtpClient()的文档,您会发现它现在已标记为过时。实际上我很惊讶您没有遇到编译器错误,但是应该有,但这可能是明天的问题。
Microsoft建议使用MailKit库。链接:Nuget-GitHub
我鼓励您使用Mailkit库而不是使用System.Net.Mail.SmtpClient()
重新实现代码。除了不被淘汰之外,MailKit是一个非常易于使用的库,开发人员和贡献者投入了大量时间和精力。
这是使该库过时并推荐MailKit的原因是
SmtpClient及其类型的网络设计不良
有趣的事实:.Net Core 1.0无法访问System.Net.Mail
命名空间,并且在之后添加了支持,但是,到.NET Core中System.Net.Mail
可用时,MailKit
是早期的首选库。 NET核心采用者。