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

本文关键字:Net 电子邮件 安全策略 何根 | 更新日期: 2023-09-27 18:30:44

为了更好地保护您的用户,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's API 看起来非常相似(带有 System.Net.Mail)。

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

所以将来我会有以下几行:

smtpClient.Authenticate (usersLoginName, usersOAuthToken);

我有一个想法,将GoogleCredentials作为新参数添加到SendEmail方法中:

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所做的那样)

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

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

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

2)如果两步验证已关闭,并且您希望保持这种方式,这意味着您将需要实现您所说的许多身份验证机制。

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

如何获取用户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地址。

未实施 Google 特定安全要求的应用中使用 Gmail SMTP 服务器时,会发生身份验证错误。在 Gmail 帐号设置中,开启: "登录和安全"> "关联的应用和网站"> "允许安全性较低的应用"> 上

using System;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Threading;
using System.ComponentModel;
namespace Examples.SmtpExamples.Async
{
public class SimpleAsynchronousExample
{
    static bool mailSent = false;
    private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
    {
        // Get the unique identifier for this asynchronous operation.
         String token = (string) e.UserState;
        if (e.Cancelled)
        {
             Console.WriteLine("[{0}] Send canceled.", token);
        }
        if (e.Error != null)
        {
             Console.WriteLine("[{0}] {1}", token, e.Error.ToString());
        } else
        {
            Console.WriteLine("Message sent.");
        }
        mailSent = true;
    }
    public static void Main(string[] args)
    {
        // Command-line argument must be the SMTP host.
        SmtpClient client = new SmtpClient(args[0]);
        // Specify the email sender.
        // Create a mailing address that includes a UTF8 character
        // in the display name.
        MailAddress from = new MailAddress("jane@contoso.com",
           "Jane " + (char)0xD8+ " Clayton",
        System.Text.Encoding.UTF8);
        // Set destinations for the email message.
        MailAddress to = new MailAddress("ben@contoso.com");
        // Specify the message content.
        MailMessage message = new MailMessage(from, to);
        message.Body = "This is a test email message sent by an application. ";
        // Include some non-ASCII characters in body and subject.
        string someArrows = new string(new char[] {''u2190', ''u2191', ''u2192', ''u2193'});
        message.Body += Environment.NewLine + someArrows;
        message.BodyEncoding =  System.Text.Encoding.UTF8;
        message.Subject = "test message 1" + someArrows;
        message.SubjectEncoding = System.Text.Encoding.UTF8;
        // Set the method that is called back when the send operation ends.
        client.SendCompleted += new
        SendCompletedEventHandler(SendCompletedCallback);
        // The userState can be any object that allows your callback
        // method to identify this send operation.
        // For this example, the userToken is a string constant.
        string userState = "test message1";
        client.SendAsync(message, userState);
        Console.WriteLine("Sending message... press c to cancel mail. Press any other key to exit.");
        string answer = Console.ReadLine();
        // If the user canceled the send, and mail hasn't been sent yet,
        // then cancel the pending operation.
        if (answer.StartsWith("c") && mailSent == false)
        {
            client.SendAsyncCancel();
        }
        // Clean up.
        message.Dispose();
        Console.WriteLine("Goodbye.");
    }
}