简体   繁体   English

如何将 MailKit 与 IMAP 用于 Exchange 与 OAuth2 用于守护程序/非交互式应用程序

[英]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 应用:

  • single tenant (I haven't tried the other options)单租户(我没有尝试过其他选项)
  • Authentication / Allow public client flows (not sure that's required but that's what I have)身份验证/允许公共客户端流(不确定是否需要,但这就是我所拥有的)
  • create a secret创建一个秘密
  • API permissions: use delegated permissions and have an admin grant consent for them API 权限:使用委派权限并获得管理员同意
    • email电子邮件
    • offline_access离线访问
    • openid打开ID
    • IMAP.AccessAsUser.All IMAP.AccessAsUser.All
    • SMTP.Send SMTP.发送
    • User.Read (not sure that's needed) User.Read(不确定是否需要)

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 包:

  • MailKit邮件套件
  • Newtonsoft.Json牛顿软件.Json
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.

相关问题 在非交互环境中认证和使用 Firebase - Authenticate and use Firebase in non-interactive environments 如何使用非交互式身份验证连接到Power BI API? - how to connect to Power BI API using non-interactive authentication? Connect-MsolService 非交互式 - Connect-MsolService non-interactive Powershell 中用于 Key Vault 的非交互式登录 - non-interactive login in Powershell for Key Vault 如何在使用Azure Webjobs和计划部署Web项目时启用非交互式身份验证? - How to enable non-interactive authentication when deploying Web project with Azure Webjobs and schedules? 如何使用Powershell执行非交互式MFA登录到Azure订阅 - How to perform a Non-Interactive MFA Login to Azure Subscription using Powershell 使用 MailKit 的 gmail 帐户的 OAuth2 在 Azure WebJobs 上不起作用 - OAuth2 with gmail account using MailKit do not work on Azure WebJobs 通过非交互式 powershell 脚本连接到 Microsoft Teams - Connecting to Microsoft Teams through non-interactive powershell script Azure CLI 无 GUI 虚拟机中的非交互式登录 - Azure CLI non-interactive login in GUIless VM WebAPI使用用户名和密码对Azure AD进行非交互式身份验证 - WebAPI Non-interactive authenticating to Azure AD using username & password
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM