使用Gmail的SmtpClient

34

我正在为一个学校项目开发邮件客户端。我已经使用C#中的SmtpClient成功发送电子邮件。这可以与任何服务器完美配合使用,但无法在Gmail上工作。我认为这是因为Google使用了TLS。我已尝试在SmtpClient上设置EnableSsltrue,但没有任何改变。

这是我用来创建SmtpClient并发送电子邮件的代码。

this.client = new SmtpClient("smtp.gmail.com", 587);
this.client.EnableSsl = true;
this.client.UseDefaultCredentials = false;
this.client.Credentials = new NetworkCredential("username", "password");

try
{
    // Create instance of message
    MailMessage message = new MailMessage();

    // Add receiver
    message.To.Add("myemail@mydomain.com");

    // Set sender
    // In this case the same as the username
    message.From = new MailAddress("username@gmail.com");

    // Set subject
    message.Subject = "Test";

    // Set body of message
    message.Body = "En test besked";

    // Send the message
    this.client.Send(message);

    // Clean up
    message = null;
}
catch (Exception e)
{
    Console.WriteLine("Could not send e-mail. Exception caught: " + e);
}

当我尝试发送电子邮件时,我遇到了这个错误。

Could not send e-mail. Exception caught: System.Net.Mail.SmtpException: Message could not be sent. ---> System.IO.IOException: The authentication or decryption has failed. ---> System.InvalidOperationException: SSL authentication error: RemoteCertificateNotAvailable, RemoteCertificateChainErrors
  at System.Net.Mail.SmtpClient.<callback>m__4 (System.Object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, SslPolicyErrors sslPolicyErrors) [0x00000] in <filename unknown>:0 
  at System.Net.Security.SslStream+<BeginAuthenticateAsClient>c__AnonStorey7.<>m__A (System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Int32[] certErrors) [0x00000] in <filename unknown>:0 
  at Mono.Security.Protocol.Tls.SslClientStream.OnRemoteCertificateValidation (System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Int32[] errors) [0x00000] in <filename unknown>:0 
  at Mono.Security.Protocol.Tls.SslStreamBase.RaiseRemoteCertificateValidation (System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Int32[] errors) [0x00000] in <filename unknown>:0 
  at Mono.Security.Protocol.Tls.SslClientStream.RaiseServerCertificateValidation (System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Int32[] certificateErrors) [0x00000] in <filename unknown>:0 
  at Mono.Security.Protocol.Tls.Handshake.Client.TlsServerCertificate.validateCertificates (Mono.Security.X509.X509CertificateCollection certificates) [0x00000] in <filename unknown>:0 
  at Mono.Security.Protocol.Tls.Handshake.Client.TlsServerCertificate.ProcessAsTls1 () [0x00000] in <filename unknown>:0 
  at Mono.Security.Protocol.Tls.Handshake.HandshakeMessage.Process () [0x00000] in <filename unknown>:0 
  at (wrapper remoting-invoke-with-check) Mono.Security.Protocol.Tls.Handshake.HandshakeMessage:Process ()
  at Mono.Security.Protocol.Tls.ClientRecordProtocol.ProcessHandshakeMessage (Mono.Security.Protocol.Tls.TlsStream handMsg) [0x00000] in <filename unknown>:0 
  at Mono.Security.Protocol.Tls.RecordProtocol.InternalReceiveRecordCallback (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0 
  --- End of inner exception stack trace ---
  at Mono.Security.Protocol.Tls.SslStreamBase.AsyncHandshakeCallback (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0 
  --- End of inner exception stack trace ---
  at System.Net.Mail.SmtpClient.Send (System.Net.Mail.MailMessage message) [0x00000] in <filename unknown>:0 
  at P2Mailclient.SMTPClient.send (P2Mailclient.Email email) [0x00089] in /path/to/my/project/SMTPClient.cs:57 

有人知道为什么我会收到这个错误吗?


在设置凭据之前,请尝试设置 client.UseDefaultCredentials = false; - Renatas M.
@Reniuz 那没有任何区别。 - simonbs
看起来是证书问题 - 请查看我的回答。 - konrad.kruczynski
我已经编辑了我的回答,并写下了下一个评论以通知您。请查看已编辑的答案。 - konrad.kruczynski
@konrad.kruczynski 我现在不在电脑旁,但我一定会尽快查看。谢谢你的帮助。 - simonbs
6个回答

25

Gmail的SMTP服务器要求您使用有效的Gmail电子邮件/密码组合对请求进行身份验证。您需要启用SSL。在没有实际查看传递的所有变量的转储的情况下,我能够做出的最好猜测是您的凭据无效,请确保您正在使用有效的GMAIL电子邮件/密码组合。

您可能想阅读这里获取一个可行的示例。

编辑:好的,这是我刚写并测试过的内容,对我来说运行良好:

public static bool SendGmail(string subject, string content, string[] recipients, string from) {
    if (recipients == null || recipients.Length == 0)
        throw new ArgumentException("recipients");

    var gmailClient = new System.Net.Mail.SmtpClient {
        Host = "smtp.gmail.com",
        Port = 587,
        EnableSsl = true,
        UseDefaultCredentials = false,
        Credentials = new System.Net.NetworkCredential("******", "*****")
    };

    using (var msg = new System.Net.Mail.MailMessage(from, recipients[0], subject, content)) {
        for (int i = 1; i < recipients.Length; i++)
            msg.To.Add(recipients[i]);

        try {
            gmailClient.Send(msg);
            return true;
        }
        catch (Exception) {
            // TODO: Handle the exception
            return false;
        }
    }
}

如果您需要更多信息,这里有一个类似的SO文章


@SimonBS,你是使用你的 Google 用户名还是完整的电子邮件地址作为你的凭据?我认为从非 Google 域登录需要使用完整的电子邮件地址来登录。 - psubsee2003
我正在使用完整的电子邮件地址。例如:myusername@gmail.com - simonbs
1
@SimonBS,你的Gmail账户启用了POP吗? - psubsee2003
我现在正在工作,所以没有C#可以玩耍。等我回家后,我会看一下并尝试编写一个可行的示例,并适当地编辑我的答案。 - Jason Larke
完美的答案。也可以运行。 - Tejasvi Hegde
显示剩余5条评论

23

尝试运行这个:

mozroots --import --ask-remove

在您的系统中(仅限于bash或者如果在Windows上则从Mono命令提示符中)。然后再次运行代码。

编辑:

我忘了您还应该运行

certmgr -ssl smtps://smtp.gmail.com:465

(并在问题上回答是)。这对我来说在Mono 2.10.8,Linux上可行(使用您的示例)。


1
这将向我的“信任存储”添加140个“新根证书”,但它并没有解决问题,不过还是谢谢你的回答。 - simonbs
非常抱歉回复晚了。这个方法奏效了!非常感谢。我只是需要确保一下。如果有人运行我的软件,他们是否必须运行这些命令才能使用该软件?这并不是非常理想的。对于学校项目来说,这并不重要,但我想知道,这样我就可以在我的报告中提到它 :-) - simonbs
@SimonBS:它们是否拥有 - 这取决于使用的操作系统/发行版及其政策。这里描述得非常清楚(附带原因 :)):http://www.mono-project.com/FAQ:_Security - konrad.kruczynski
谢谢你们俩提供这个小技巧...在OSX和Ubuntu上非常有帮助和准确。祝你们俩一切顺利... - Tom Winans

7

您需要在 Gmail 账户中启用两步验证并创建应用程序密码(https://support.google.com/accounts/answer/185833?hl=en)。一旦您使用新的应用程序密码替换密码,它就可以正常工作。

Credentials = new System.Net.NetworkCredential("your email address", "your app password");

对我来说,这是实际的解决方案,因为Google账户特别不允许应用程序代表用户登录以发送电子邮件。谢谢。 - Max Sorin

4

我认为,您需要验证用于建立SSL连接的服务器证书...

使用以下代码发送电子邮件并验证服务器证书...

            this.client = new SmtpClient(_account.SmtpHost, _account.SmtpPort);
            this.client.EnableSsl = _account.SmtpUseSSL;
            this.client.Credentials = new NetworkCredential(_account.Username, _account.Password);

        try
        {
            // Create instance of message
            MailMessage message = new MailMessage();

            // Add receivers
            for (int i = 0; i < email.Receivers.Count; i++)
                message.To.Add(email.Receivers[i]);

            // Set sender
            message.From = new MailAddress(email.Sender);

            // Set subject
            message.Subject = email.Subject;

            // Send e-mail in HTML
            message.IsBodyHtml = email.IsBodyHtml;

            // Set body of message
            message.Body = email.Message;

            //validate the certificate
            ServicePointManager.ServerCertificateValidationCallback =
            delegate(object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
            { return true; };


            // Send the message
            this.client.Send(message);

            // Clean up
            message = null;
        }
        catch (Exception e)
        {
            Console.WriteLine("Could not send e-mail. Exception caught: " + e);
        }

导入 System.Security.Cryptography.X509Certificates 命名空间以使用 ServicePointManager


是的,当我尝试在没有验证X509证书的情况下运行代码时,它对我来说也很好用.....但有时,在尝试ssl连接时,需要验证证书..... - user1082916
我的同事在通过我们的电子邮件服务器从Android上的Mono应用程序发送电子邮件时遇到了问题。这个解决方案解决了问题。太棒了! - Steffen Winkler
这也是我的解决方法。我可以在命令提示符下运行我的Mono应用程序并发送邮件,但一旦它进入cron就停止工作了。当从cron运行时,x509行修复了该问题。 - b.pell

3
这段代码对我来说运行良好,请尝试将其粘贴到LinqPad中,编辑电子邮件地址和密码,然后告诉我们您看到了什么:
var client = new System.Net.Mail.SmtpClient("smtp.gmail.com", 587);
client.EnableSsl = true;
client.UseDefaultCredentials = false;
client.Credentials = new System.Net.NetworkCredential("me@gmail.com", "xxxxxxx");

try
{
    // Create instance of message
    System.Net.Mail.MailMessage message = new System.Net.Mail.MailMessage();

    // Add receiver
    message.To.Add("me@gmail.com");

    // Set sender
    // In this case the same as the username
    message.From = new System.Net.Mail.MailAddress("me@gmail.com");

    // Set subject
    message.Subject = "Test";

    // Set body of message
    message.Body = "En test besked";

    // Send the message
    client.Send(message);

    // Clean up
    message = null;
}
catch (Exception e)
{
    Console.WriteLine("Could not send e-mail. Exception caught: " + e);
}

我使用的是Mac,没有LinqPad。我只用MonoDevelop。使用上面的代码给了我完全相同的结果。 - simonbs
@SimonBS 你可能想要使用Mono重新标记这个问题。这可能是问题的一部分,因为似乎有多个人已经成功地在VS中运行了相同的代码。 - psubsee2003
@psubsee2003 你可能是对的。我没有考虑到那个。我还是很新的C#,没有想到IDE会有影响。我已经重新标记了它。 - simonbs
@SimonBS 我从未在 VisualStudio 之外进行过任何工作,因此我不知道您使用 MonoDevelop 是否会有影响,但考虑到事实,似乎下一步要检查的就是这个。 - psubsee2003

1

我在工作了6个月后,于2013年5月开始在GMail中遇到此问题。Mono项目的合理使用受信任的根证书文档提供了解决方案。我选择了第一种选项:

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

没有警告就停止我的服务的电子邮件太具有破坏性了。

2016年8月26日更新:用户Chico建议使用ServerCertificateValidationCallback回调的完整实现。我没有测试过。

ServicePointManager.ServerCertificateValidationCallback = MyRemoteCertificateValidationCallback;

bool MyRemoteCertificateValidationCallback(System.Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
    bool isOk = true;
    // If there are errors in the certificate chain, look at each error to determine the cause.
    if (sslPolicyErrors != SslPolicyErrors.None) {
        for (int i=0; i<chain.ChainStatus.Length; i++) {
            if (chain.ChainStatus [i].Status != X509ChainStatusFlags.RevocationStatusUnknown) {
                chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
                chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
                chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan (0, 1, 0);
                chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;
                bool chainIsValid = chain.Build ((X509Certificate2)certificate);
                if (!chainIsValid) {
                    isOk = false;
                }
            }
        }
    }
    return isOk;

}

虽然这确实可以解决服务问题并提供一种方法,使您不会遇到中断服务的情况,但它也通过表示我不关心证书来删除所有安全方面。因此,中间人攻击可以很容易地监听您的通信,而无需在回调中进行其他检查以验证“不受信任”的证书是否确实是您想要信任的证书。 - netniV

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接