oauth-2.0 如何使用MailKit中的OAuth 2.0连接到Exchange Online?

d4so4syb  于 2022-10-31  发布在  其他
关注(0)|答案(4)|浏览(426)

我有一个Web应用程序,它使用MailKit和基本身份验证通过Exchange Online(Office365)向用户发送电子邮件。我们公司是MS合作伙伴,因此有义务在2020年2月底之前关闭我们服务的基本身份验证。
所以,我想使用OAuth 2.0连接到Exchange Online,类似于这个例子。事实上,根据这个answer,可能有一个解决方案,但我找不到任何关于它的东西。
现在我正在玩MS身份平台v2.0,但我不知道如何做。
任何帮助都将不胜感激。

更新1

我不想代表已登录用户发送邮件,但有一个单独的Office365用户帐户可用于向其他人发送邮件(通知等)。

更新2

我设法使用Microsoft Graph SDK和用户名/密码提供程序更接近于我想做的事情。
我们的用户帐户需要多因素身份验证,因此我在使用用户密码时遇到错误,因为我无法满足第二个因素。当我使用应用程序时,由于密码不正确,密码身份验证失败。

更新3

我现在改用邮件中继。但如果我能找到答案,我会更新这个问题。

lymnna71

lymnna711#

通过使用Microsoft.Identity.Client,您可以生成一个令牌,然后使用它通过身份验证。
我发现下面的IMAP,POP3和SMTP,所以适合我的项目,以获得一个工作的解决方案.虽然例子显示的是交互式的方法,我正在尝试使用客户端凭据流与应用程序的秘密.
MailKit - Using OAuth2 With Exchange (IMAP, POP3 or SMTP)
Microsoft - Authenticate an IMAP, POP or SMTP connection using OAuth

nhaq1z21

nhaq1z212#

我的选择是查看Microsoft Graph API。它是所有Microsoft服务(包括电子邮件)的单一端点。电子邮件特定端点文档是here
Microsoft提供了不同语言的SDK来使用Graph API开发客户端应用程序。
在高级别上,您需要执行以下操作。
i)在Azure Active Directory中注册应用程序。请参阅here
ii)使用Oauth2“授权代码授予”流获取刷新令牌。请参阅here
iii)将刷新令牌交换为访问令牌,并使用该访问令牌来调用Microsoft Graph API。
iv)如果您的应用程序需要在用户脱机的情况下执行操作,您还需要存储刷新令牌。在这种情况下,请确保在步骤ii)中包含“脱机”范围

pbgvytdp

pbgvytdp3#

我建议您研究一下DotNetOpenAuth或类似的库并阅读它们的示例。如果DotNetOpenAuth库没有内置Windows Live URL,您可能需要知道用于此目的的Windows Live URL。
可在此处找到示例:https://github.com/DotNetOpenAuth/DotNetOpenAuth.Samples

uujelgoq

uujelgoq4#

我知道这是一个老职位,但随着微软逐步滚动现代身份验证的所有Office 365租户。
我没有使用过MFA设置。
我用它来获取附件通过POP3从自动邮件来在我们的租户的邮箱,该应用程序运行从一个预定的任务,所以它需要能够运行没有交互。
首先,您需要获取租户管理员在registering租户上的应用程序时获得的TenantID和ClientID。bootstrap doc将这些信息记入@jstedfast,以便优雅地使用这些信息。然后,为身份验证令牌设置缓存(在此article和与之链接的wiki页面之后)。然后处理使用交互式身份验证还是静默身份验证的逻辑,避免每次都提示登录。(直接从documentation复制/粘贴,而不是留下一个可能会中断的链接...)我将所有这些都打包到一个函数中,稍后调用该函数来处理身份验证。

private static async Task<AuthenticationResult> GetMSALTokenAsync()
{
    var scopes = new string[] {
        "email",
        "offline_access",
        "https://outlook.office.com/POP.AccessAsUser.All"
    };

    var options = new PublicClientApplicationOptions
    {
        ClientId = Settings.Default.MSALClientId,
        TenantId = Settings.Default.MSALTenantId,
        RedirectUri = Settings.Default.MSALRedirectURI
    };

    var storageProperties = new StorageCreationPropertiesBuilder(
                Settings.Default.MSALTokenCache,
                MsalCacheHelper.UserRootDirectory)
            .Build();

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

    var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
    cacheHelper.RegisterCache(publicClientApplication.UserTokenCache);

    var accounts = await publicClientApplication.GetAccountsAsync();

    CancellationTokenSource source = new CancellationTokenSource();
    CancellationToken token = source.Token;
    AuthenticationResult authToken;

    try
    {
        authToken = await publicClientApplication.AcquireTokenSilent(scopes, accounts.First(o => o.Username == Settings.Default.LoginPop)).ExecuteAsync();
        return authToken;
    }
    catch (MsalUiRequiredException ex) when (ex.ErrorCode == MsalError.InvalidGrantError)
    {
        switch (ex.Classification)
        {
            case UiRequiredExceptionClassification.None:
                break;
            case UiRequiredExceptionClassification.MessageOnly:
                // You might want to call AcquireTokenInteractive(). Azure AD will show a message
                // that explains the condition. AcquireTokenInteractively() will return UserCanceled error
                // after the user reads the message and closes the window. The calling application may choose
                // to hide features or data that result in message_only if the user is unlikely to benefit 
                // from the message
                try
                {
                    authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync(token);
                    return authToken;
                }
                catch (MsalClientException ex2) when (ex2.ErrorCode == MsalError.AuthenticationCanceledError)
                {
                    // Do nothing. The user has seen the message
                }
                break;

            case UiRequiredExceptionClassification.BasicAction:
            // Call AcquireTokenInteractive() so that the user can, for instance accept terms
            // and conditions

            case UiRequiredExceptionClassification.AdditionalAction:
            // You might want to call AcquireTokenInteractive() to show a message that explains the remedial action. 
            // The calling application may choose to hide flows that require additional_action if the user 
            // is unlikely to complete the remedial action (even if this means a degraded experience)

            case UiRequiredExceptionClassification.ConsentRequired:
            // Call AcquireTokenInteractive() for user to give consent.

            case UiRequiredExceptionClassification.UserPasswordExpired:
            // Call AcquireTokenInteractive() so that user can reset their password

            case UiRequiredExceptionClassification.PromptNeverFailed:
            // You used WithPrompt(Prompt.Never) and this failed

            case UiRequiredExceptionClassification.AcquireTokenSilentFailed:
            default:
                // May be resolved by user interaction during the interactive authentication flow.
                authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync(token);
                return authToken;
        }
    }
    catch (InvalidOperationException)
    {
        authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync(token);
        return authToken;
    }
    log.Error("Authentication failed.");
    return null;
}

然后,您可以继续使用实际的逻辑来处理Exchange服务器。

private static async Task PopDownloadAsync()
{
   using (var client = new Pop3Client())
    {
        try
        {
            await client.ConnectAsync(Settings.Default.SrvPop, 995, SecureSocketOptions.SslOnConnect);
        }
        catch (Pop3CommandException ex)
        {
            // do stuff
            return;
        }
        catch (Pop3ProtocolException ex)
        {
            // do stuff
            return;
        }

        try
        {
            var result = await GetMSALTokenAsync();

            if (result != null)
            {
                var oauth2 = new SaslMechanismOAuth2(result.Account.Username, result.AccessToken);
                await client.AuthenticateAsync(oauth2);
            }
            else
            {
                throw new AuthenticationException("Something went wrong during authentication...");
            }
        }
        catch (AuthenticationException ex)
        {
            // do stuff
            return;
        }
        catch (Pop3CommandException ex)
        {
            // do stuff
            return;
        }
        catch (Pop3ProtocolException ex)
        {
            // do stuff
            return;
        }

        if (client.Capabilities.HasFlag(Pop3Capabilities.UIDL))
        {
            try
            {
                // do stuff
            }
            catch (Pop3CommandException ex)
            {
                // do stuff
            }
            catch (Pop3ProtocolException ex)
            {
                // do stuff
                if (!client.IsConnected)
                    return;
            }
            catch (Exception e)
            {
                // do stuff
                return;
            }
        }

        if (client.IsConnected)
        {
            await client.DisconnectAsync(true);
        }
    }
}

相关问题