如何根据新的安全策略在.Net中发送电子邮件?


71

为了更好地保护您的用户,GMail和其他邮件提供商建议将我们所有的应用程序升级到OAuth 2.0。

我是对的,这意味着它System.Net.Mail不再工作了,我们需要使用另一个库MailKit吗?

总的来说,我试图了解如何在不允许“访问不太安全的应用程序”的情况下发送电子邮件?

因为我有System.Net.Mail.SmtpException: The SMTP server requires a secure connection or the client was not authenticated. The server response was: 5.5.1 Authentication Required.smtpClient.Send(message);执行。

如果要解决这个问题的唯一方法是使用MailKit,我觉得这个问题将是一个很好的实际步骤一步转换教程从System.Net.Mail使用MailKitGoogle.Apis.Auth.OAuth2。我不知道一般的解决方案将使用DotNetOpenAuth

我的应用程序中包含以下类,用于将电子邮件发送到任何地址(gmail,yandex和其他地址):

public class EmailSender
{
    public void SendEmail(SmtpServerSettings serverSettings, SendEmailRequest emailRequest)
    {
        // Usually I have 587 port, SmtpServerName = smtp.gmail.com 
        _logger.Trace("Sending message with subject '{0}' using SMTP server {1}:{2}",
                      emailRequest.Subject,
                      serverSettings.SmtpServerName,
                      serverSettings.SmtpPort);

        try
        {
            using (var smtpClient = new SmtpClient(serverSettings.SmtpServerName, (int)serverSettings.SmtpPort))
            {
                smtpClient.EnableSsl = serverSettings.SmtpUseSsl; // true
                if (!string.IsNullOrEmpty(serverSettings.UserName) || !string.IsNullOrEmpty(serverSettings.EncryptedPassword))
                {
                    smtpClient.Credentials = new NetworkCredential(serverSettings.UserName, serverSettings.EncryptedPassword);
                }

                smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
                smtpClient.Timeout = (int)serverSettings.SmtpTimeout.TotalMilliseconds;

                using (var message = new MailMessage())
                {
                    message.From = new MailAddress(serverSettings.FromAddress);

                    emailRequest.To.ForEach(message.To.Add);
                    emailRequest.CC.ForEach(message.CC.Add);
                    emailRequest.Bcc.ForEach(message.Bcc.Add);

                    message.Subject = emailRequest.Subject.Replace('\r', ' ').Replace('\n', ' ');
                    message.Body = emailRequest.Body;
                    message.BodyEncoding = Encoding.UTF8;
                    message.IsBodyHtml = false;

                    smtpClient.Send(message);
                }
            }

            _logger.Trace("Sent message with subject '{0}' using SMTP server {1}:{2}",
                          emailRequest.Subject,
                          serverSettings.SmtpServerName,
                          serverSettings.SmtpPort);
        }
        catch (SmtpFailedRecipientsException e)
        {
            var failedRecipients = e.InnerExceptions.Select(x => x.FailedRecipient);
            LogAndReThrowWithValidMessage(e, EmailsLocalization.EmailDeliveryFailed, failedRecipients);
        }
   }
}

在使用新的Google安全策略之前,它可以正常工作。

我知道System.Net.Mail不支持OAuth2。我决定用来MailKit's SmtpClient发送消息。

经过调查后,我知道我的初始代码变化不大,因为MailKit'sAPI看起来非常相似(与System.Net.Mail)。

除了一个细节:我需要拥有用户的OAuth访问令牌(MailKit没有可获取OAuth令牌的代码,但如果有的话,它可以使用它)。

因此,将来我会有以下内容:

smtpClient.Authenticate (usersLoginName, usersOAuthToken);

我有一个想法要添加GoogleCredentialsSendEmail方法的新参数:

public void SendEmail(SmtpServerSettings serverSettings, SendEmailRequest emailRequest, 
                      GoogleCredentials credentials)
{
    var certificate = new X509Certificate2(credentials.CertificateFilePath,
                                           credentials.PrivateKey,
                                           X509KeyStorageFlags.Exportable);

     var credential = new ServiceAccountCredential(
                      new ServiceAccountCredential.Initializer(credentials.ServiceAccountEmail)
                             {
                                 Scopes = new[] { "https://mail.google.com/" },
                                 User = serverSettings.UserName
                             }.FromCertificate(certificate));

    ....
    //my previous code but with MailKit API
}

如何获得usersOAuthToken?是使用的最佳实践技术Google.Apis.Auth.OAuth2吗?

我上面发布的代码仅适用于GMail,不适用于yandex.ru或其他邮件提供商。要与他人合作,我可能需要使用其他OAuth2库。但是我不想在我的代码中为许多可能的邮件提供者提供许多身份验证机制。我想为每个邮件提供商提供一个通用的解决方案。还有一个可以发送电子邮件的库(就像.net smtpclient一样)


评论不作进一步讨论;此对话已转移至聊天
George Stocker

Answers:


34

通用解决方案是https://galleryserverpro.com/use-gmail-as-your-smtp-server-even-when-using-2-factor-authentication-2-step-verification/

1)使用浏览器登录到您的Google帐户,然后转到登录和安全设置。查找两步验证设置。

2)如果“两步验证”处于关闭状态,而您想要保持这种方式,则意味着您将需要实现您所说的许多身份验证机制。

解决方案:将其打开,然后生成并使用Google应用程序密码。它应该工作!您不需要使用其他库,例如mailkit


1
只有在您可以控制GMail帐户的情况下,此方法才有效,否则您将被卡住。如果您要编写一个程序来使用您不拥有的GMail(和其他)帐户,则需要使用OAuth2,或者告诉您的用户登录其GMail帐户并更改其设置(甚至可以用于像yamex.ru这样的服务器?)
jstedfast '16

我刚刚检查了Google,Yahoo,Yandex等是否具有两步验证

而且一切正常。我认为我的用户可以控制GMail帐户(或其他帐户),因为他们可以使用它

应用专用密码无法正常工作。仍然遇到相同的错误
Tariq

@jstedfast我面临着同样的问题。当我的程序向他们分发一封电子邮件时,我有很多用户帐户。如何防止代码中发生此错误?
Alaa'18年

19

如何获得用户OAuthToken?

您需要做的第一件事是按照Google的说明为您的应用程序获取OAuth 2.0凭据。

完成此操作后,获取访问令牌的最简单方法是使用Google的Google.Apis.Auth库:

using System;
using System.Threading;
using System.Security.Cryptography.X509Certificates;

using Google.Apis.Auth.OAuth2;

using MimeKit;
using MailKit.Net.Smtp;
using MailKit.Security;

namespace Example {
    class Program
    {
        public static async void Main (string[] args)
        {
            var certificate = new X509Certificate2 (@"C:\path\to\certificate.p12", "password", X509KeyStorageFlags.Exportable);
            var credential = new ServiceAccountCredential (new ServiceAccountCredential
                .Initializer ("your-developer-id@developer.gserviceaccount.com") {
                    // Note: other scopes can be found here: https://developers.google.com/gmail/api/auth/scopes
                    Scopes = new[] { "https://mail.google.com/" },
                    User = "username@gmail.com"
                }.FromCertificate (certificate));

            // Note: result will be true if the access token was received successfully
            bool result = await credential.RequestAccessTokenAsync (CancellationToken.None);

            if (!result) {
                Console.WriteLine ("Error fetching access token!");
                return;
            }

            var message = new MimeMessage ();
            message.From.Add (new MailboxAddress ("Your Name", "username@gmail.com"));
            message.To.Add (new MailboxAddress ("Recipient's Name", "recipient@yahoo.com"));
            message.Subject = "This is a test message";

            var builder = new BodyBuilder ();
            builder.TextBody = "This is the body of the message.";
            builder.Attachments.Add (@"C:\path\to\attachment");

            message.Body = builder.ToMessageBody ();

            using (var client = new SmtpClient ()) {
                client.Connect ("smtp.gmail.com", 587, SecureSocketOptions.StartTls);

                // use the access token as the password string
                client.Authenticate ("username@gmail.com", credential.Token.AccessToken);

                client.Send (message);

                client.Disconnect (true);
            }
        }
    }
}

使用Google.Apis.Auth.OAuth2是否是最佳做法?

您为什么不使用他们的API来获取身份验证令牌?似乎是把它拿给我的最好方法...

我可以将电子邮件发送到其他非Gmail帐户吗?

是的,您通过GMail发送的任何电子邮件都可以发送到其他任何电子邮件地址-不必仅发送到其他GMail地址。


2
@jstedfast我在哪里可以得到X509Certificate2?例如,我需要发送电子邮件到yandex post,在哪里可以得到credential.Token.AccessToken
阿纳托利

1
@jstedfast您认为我需要重新设计界面drive.google.com/file/d/0B_ikyn0YIY7KazYwcFN4SllFaWc/view 是否需要更改UI来添加新字段(例如在GoogleCredentials数据合同中)?

3
@jstedfast我对,您仅为gmail邮件服务器提供了解决方案吗?我怎么知道credential我的用户是否要发送电子邮件yandex.mail@yandex.ru并填写与yandex邮件服务器相对应的所有配置字段?

1
是的,你是对的。我上面发布的代码仅适用于GMail,不适用于yandex.ru。要使用yandex.ru,您可能需要使用另一个OAuth2库。nuget.org
jstedfast '16

2
@jstedfast但这是一个问题。对于每个可能的邮件服务器(yandex.ru,mail.ru,gmail.com,结束许多其他邮件服务器),我将需要具有自己的身份验证器。WTF?:)

1

当通过未实现Google特定安全要求的应用程序使用Gmail smtp服务器时,会发生身份验证错误。在Gmail帐户设置中,打开:“登录和安全”>“已连接的应用和网站”>“允许不太安全的应用”>“打开”


5
“允许不太安全的应用程序”
user713836 '16

1
真是

所有这些其他答案,但是我没有在其他任何地方遇到过这种显而易见的解决方案。
ds_practicioner
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.