[英]How to use MailKit with IMAP for Exchange with OAuth2 for daemon / non-interactive apps
I have a daemon that reads the inbox of an email address and performs actions to the emails.我有一个守护进程,它读取电子邮件地址的收件箱并对电子邮件执行操作。 I'm using MailKit to connect to the exchange server with IMAP but Microsoft has shut down basic authentication for us (at 4am, without warning...).我正在使用 MailKit 通过 IMAP 连接到交换服务器,但 Microsoft 已为我们关闭了基本身份验证(凌晨 4 点,没有警告......)。 So I need a new way to connect to my mailbox.所以我需要一种新的方式来连接我的邮箱。
Using graph would require a major rewrite of my application.使用图表需要对我的应用程序进行重大重写。 I plan to do that, but in the mean time, I need an intermediary solution that would keep MailKit.我打算这样做,但与此同时,我需要一个中间解决方案来保留 MailKit。
This is using ROPC .这是使用ROPC 。
First, register an Azure Active Directory app:首先,注册一个 Azure Active Directory 应用:
Even though this is a daemon-like application, we're using delegated permissions because we're using the ROPC grant.尽管这是一个类似守护进程的应用程序,但我们使用的是委托权限,因为我们使用的是 ROPC 授权。
Then you can use this code which uses the following nuget packages:然后您可以使用此代码,该代码使用以下 nuget 包:
using MailKit;
using MailKit.Net.Imap;
using MailKit.Net.Smtp;
using MailKit.Search;
using MailKit.Security;
using MimeKit;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace MailKitExchangeDaemon
{
class Program
{
const string ScopeEmail = "email";
const string ScopeOpenId = "openid";
const string ScopeOfflineAccess = "offline_access";
const string ScopeImap = "https://outlook.office.com/IMAP.AccessAsUser.All";
const string ScopeSmtp = "https://outlook.office.com/SMTP.Send";
const string SmtpHost = "smtp.office365.com";
const string ImapHost = "outlook.office365.com";
const string TenantId = "<GUID>";
const string AppId = "<GUID>";
const string AppSecret = "<secret value>";
const string Username = "<email address>";
const string Password = "<password>";
static async Task Main(string[] args)
{
Console.WriteLine($"Sending an email to {Username}...");
await sendEmail();
System.Threading.Thread.Sleep(2000);
Console.WriteLine($"Printing {Username} inbox...");
await printInbox();
Console.Write("Press ENTER to end this program");
Console.ReadLine();
}
static async Task printInbox()
{
var accessToken = await getAccessToken(ScopeEmail, ScopeOpenId, ScopeOfflineAccess, ScopeImap);
using (var client = new ImapClient(/*new MailKit.ProtocolLogger(Console.OpenStandardOutput())*/))
{
try
{
await client.ConnectAsync(ImapHost, 993, true);
await client.AuthenticateAsync(accessToken);
client.Inbox.Open(FolderAccess.ReadOnly);
var emailUIDs = client.Inbox.Search(SearchQuery.New);
Console.WriteLine($"Found {emailUIDs.Count} new emails in the {Username} inbox");
foreach (var emailUID in emailUIDs)
{
var email = client.Inbox.GetMessage(emailUID);
Console.WriteLine($"Got email from {email.From[0]} on {email.Date}: {email.Subject}");
}
}
catch (Exception e)
{
Console.Error.WriteLine($"Error in 'print inbox': {e.GetType().Name} {e.Message}");
}
}
}
static async Task sendEmail()
{
var accessToken = await getAccessToken(ScopeEmail, ScopeOpenId, ScopeOfflineAccess, ScopeSmtp);
using (var client = new SmtpClient(/*new MailKit.ProtocolLogger(Console.OpenStandardOutput())*/))
{
try
{
client.Connect(SmtpHost, 587, SecureSocketOptions.Auto);
client.Authenticate(accessToken);
var email = new MimeMessage();
email.From.Add(MailboxAddress.Parse(Username));
email.To.Add(MailboxAddress.Parse(Username));
email.Subject = "SMTP Test";
email.Body = new TextPart("plain") { Text = "This is a test" };
client.Send(email);
}
catch (Exception e)
{
Console.Error.WriteLine($"Error in 'send email': {e.GetType().Name} {e.Message}");
}
}
}
/// <summary>
/// Get the access token using the ROPC grant (<see cref="https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc"/>).
/// </summary>
/// <param name="scopes">The scopes/permissions the app requires</param>
/// <returns>An access token that can be used to authenticate using MailKit.</returns>
private static async Task<SaslMechanismOAuth2> getAccessToken(params string[] scopes)
{
if (scopes == null || scopes.Length == 0) throw new ArgumentException("At least one scope is required", nameof(scopes));
var scopesStr = String.Join(" ", scopes.Select(x => x?.Trim()).Where(x => !String.IsNullOrEmpty(x)));
var content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", Username),
new KeyValuePair<string, string>("password", Password),
new KeyValuePair<string, string>("client_id", AppId),
new KeyValuePair<string, string>("client_secret", AppSecret),
new KeyValuePair<string, string>("scope", scopesStr),
});
using (var client = new HttpClient())
{
var response = await client.PostAsync($"https://login.microsoftonline.com/{TenantId}/oauth2/v2.0/token", content).ConfigureAwait(continueOnCapturedContext: false);
var responseString = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(responseString);
var token = json["access_token"];
return token != null
? new SaslMechanismOAuth2(Username, token.ToString())
: null;
}
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.