SMTP和OAuth 2

23

.NET是否支持通过OAuth协议进行SMTP身份验证?基本上,我想能够使用OAuth访问令牌代表用户发送电子邮件。然而,在.NET框架中,我找不到对此的支持。

Google在其他环境中提供了一些示例,但没有在.NET中提供。

4个回答

31

System.Net.Mail不支持OAuth或OAuth2。但是,您可以使用MailKit的(SmtpClient只支持OAuth2) SmtpClient发送邮件,只要您有用户的OAuth访问令牌( MailKit没有代码来获取OAuth令牌,但如果您有它,它可以使用它)。

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

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

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));

bool result = await credential.RequestAccessTokenAsync (CancellationToken.None);

// Note: result will be true if the access token was received successfully

现在您已经获得了访问令牌 (credential.Token.AccessToken),您可以像使用密码一样在 MailKit 中使用它:

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

    // use the access token
    var oauth2 = new SaslMechanismOAuth2 ("username@gmail.com", credential.Token.AccessToken);
    client.Authenticate (oauth2);

    client.Send (message);

    client.Disconnect (true);
}

3
先生,这个图书馆解决了我的问题,非常好用!谢谢你的分享。 - user3288287
2
感谢@jstedfast。代码是正确的。如果有人尝试此操作并收到“身份验证失败”错误,则我的问题在于OAuth中的“范围”。我只想发送邮件,因此我使用了“https://www.googleapis.com/auth/gmail.compose”,但使用MailKit进行身份验证将要求您将其更改为“https://mail.google.com/”(完整的gmail权限)。 - JeffT
4
如何获得谷歌信任的X509证书? - Andy Furniss
1
我刚刚看到了,谢谢!如果这样做有些过分,请原谅,但是您是否知道如何使用可以下载的json文件来完成相同的操作?根据此处显示的示例 - https://dev59.com/_loT5IYBdhLWcg3wlwaR#38403006 - 我可以尝试读取与您示例中使用的证书相同位置获取的json密钥,但Mailkit会抛出一个AuthenticationException异常,其中包含消息“334:eyJzdGF0...” - Murphybro2
1
我从JSON中获取了private_key,然后使用FromPrivateKey方法而不是FromCertificate。这为我提供了一个ServiceAccountCredential,我可以使用它来请求访问令牌,然后进行身份验证(与上面相同)。但是,当使用我的令牌调用Authenticate方法时,Mailkit会抛出AuthenticationException。基本上与此答案相同-https://dev59.com/_loT5IYBdhLWcg3wlwaR#38403006。 - Murphybro2
显示剩余11条评论

8

我通过使用 Microsoft.Identity.ClientMailKit.Net.Smtp.SmtpClient 获得了成功,像这样在 Office 365 / Exchange Online 中使用。应用程序注册需要 API 权限 SMTP.Send

var options = new PublicClientApplicationOptions
{
    ClientId = "00000000-0000-0000-0000-000000000000",
    TenantId = " 00000000-0000-0000-0000-000000000000",
    RedirectUri = "http://localhost"
};

var publicClientApplication = PublicClientApplicationBuilder
    .CreateWithApplicationOptions(options)
    .Build();

var scopes = new string[] {
    "email",
    "offline_access",
    "https://outlook.office.com/SMTP.Send" // Only needed for SMTP
};

var authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync();

//Test refresh token
var newAuthToken = await publicClientApplication.AcquireTokenSilent(scopes, authToken.Account).ExecuteAsync(cancellationToken);

var oauth2 = new SaslMechanismOAuth2(authToken.Account.Username, authToken.AccessToken);

using (var client = new SmtpClient())
{
    await client.ConnectAsync("smtp.office365.com", 587, SecureSocketOptions.StartTls);
    await client.AuthenticateAsync(oauth2);

    var message = new MimeMessage();
    message.From.Add(MailboxAddress.Parse(authToken.Account.Username));
    message.To.Add(MailboxAddress.Parse("toEmail"));
    message.Subject = "Test";
    message.Body = new TextPart("plain") { Text = @"Oscar Testar" };

    await client.SendAsync(message, cancellationToken);

    await client.DisconnectAsync(true);
}

根据这个例子:

https://github.com/jstedfast/MailKit/blob/master/ExchangeOAuth2.md


很遗憾,这个答案只适用于“公共”应用程序。 - CervEd
@CervEd 确实,但如果您需要在服务中运行它,则可以存储刷新令牌。虽然远非最佳选择。https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-acquire-cache-tokens#advanced-accessing-the-users-cached-tokens-in-background-apps-and-services - Ogglas
2
你可以这样做,但仍需要公共凭证流,即使只是间歇性的。私有解决方案是可行的,但它非常麻烦。整个情况一团糟,我对设计这种可憎的东西的人没有愉快的想法或高度评价。 - CervEd
这个答案对我来说非常有效!对于那些正在尝试的人,不要忘记在O365中启用SMTP AUTH。 - Baskman

2

补充以上答案。我也花了很多时间在使用mailkit在.net中使用gmail oAuth2发送电子邮件的事情上进行研究,因为我正在使用它向我的应用程序用户发送电子邮件。感谢mailkit开发人员。

现在我们需要:

  • 授权码
  • 客户端ID
  • 客户端密钥
  • 刷新令牌
  • 访问令牌

您可以通过创建项目直接从google控制台获取客户端ID和客户端密钥。

接下来,您可以在Google Developers OAuth Playground中使用自己的OAuth凭据启用gmail应用程序,方法是使用左上角设置按钮。

之后选择并授权API https://mail.google.com/

现在,您可以通过此http POST请求https://developers.google.com/oauthplayground/refreshAccessToken直接刷新令牌。您将在其中找到参数。

现在,您可以直接在C#代码中使用MailKit使用此代码:

using (var client = new SmtpClient())
{
    client.Connect("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
    
    var oauth2 = new SaslMechanismOAuth2(GMailAccount, token.AccessToken);
    client.Authenticate(oauth2);
    
    await client.SendAsync(mailMessage);
    client.Disconnect(true);
}

现在您可以通过服务器端从您的Gmail帐户发送电子邮件。

0

使用其他答案中提到的MailKit时,我遇到了一个需要从Gmail请求更多范围的身份验证问题。对于任何遇到其他答案中的“身份验证失败错误”的人,此答案改用Gmail API以避免请求更多范围。

使用此答案中的一些片段:https://stackoverflow.com/a/35795756/7242722

这是一个完整的示例,对我有效:

var fromAddress = new MailboxAddress(fromName, fromEmail);
var toAddress = new MailboxAddress(toName, toEmail);

List<MailboxAddress> ccMailAddresses = new List<MailboxAddress>();
if (ccEmails != null)
    foreach (string ccEmail in ccEmails)
        ccMailAddresses.Add(new MailboxAddress(string.Empty, ccEmail));

var message = new MimeMessage();
message.To.Add(toAddress);
message.From.Add(fromAddress);
message.Subject = subject;

var bodyBuilder = new BodyBuilder();
bodyBuilder.HtmlBody = body;
bodyBuilder.TextBody = HtmlUtilities.ConvertToPlainText(body);
message.Body = bodyBuilder.ToMessageBody();

foreach (MailboxAddress ccMailAddress in ccMailAddresses)
    message.Cc.Add(ccMailAddress);
    
GoogleAuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
    ClientSecrets = new ClientSecrets()
    {
        ClientId = <ClientId>,
        ClientSecret = <ClientSecret>
    },
});

TokenResponse tokenResponse = await authorizationCodeFlow.RefreshTokenAsync("id", <RefreshToken>, CancellationToken.None);

UserCredential credential = new UserCredential(authorizationCodeFlow, "id", tokenResponse);

var gmailService = new GmailService(new BaseClientService.Initializer()
{
    ApplicationName = <AppName>,
    HttpClientInitializer = credential,
});

Google.Apis.Gmail.v1.Data.Message gmailMessage = new Google.Apis.Gmail.v1.Data.Message();
gmailMessage.Raw = Utilities.Base64UrlEncode(message.ToString());
var result = gmailService.Users.Messages.Send(gmailMessage, "me").Execute();


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