找回密码
 立即注册
首页 业界区 安全 翻译:基于 OAuth2 在 Web 服务中使用 MailKit ...

翻译:基于 OAuth2 在 Web 服务中使用 MailKit

毋献仪 9 小时前
MailKit 基于 OAuth2 操作 Exchange (IMAP, POP3 和 SMTP)

原文地址:https://github.com/jstedfast/MailKit/blob/master/ExchangeOAuth2.md#using-oauth2-with-exchange-imap-pop3-or-smtp
1. 注册应用

不管你是准备在桌面应用、移动应用,还是在 Web 服务中基于 MailKit 来访问 Exchange,你需要做的第一件事,就是在微软的 Identity Platform 中注册你的应用。
你需要按照 快速入门 中的要求来完成这一步。
注册之后,你可以得到你所注册的应用 Id, 或者在 ids 中被称为 client id.
1.png

在上图中,client id 为:11111111-1111-1111-1111-11111111, 同时,还可以看到相应的 Object ID: 00000000-0000-0000-0000-00000000
2. 为应用配置正确的 API 权限

基于你的应用希望支持的邮件协议,有许多种不同的 API 权限需要配置。见:为你的 Extra AD 应用准备权限:POP, IMAP, SMTP

  • 在 Azure 门户种,通过 Microsoft Extra appliation 的管理视图,选择 API Permissions 权限
  • 选择我的组织使用的 API 权限 (APSs my organization uses),然后,选择 "Office 365 Exchange Online"
  • 点击应用权限 (Application permissions)

    • 对于 POP 访问协议,选择 POP.AccessAsApp 权限
    • 对于 IMAP 访问协议,选择 IMAP.AccessAsApp
    • 对于 SMTP 访问协议,选择 SMTP.SendAsApp

  • 选择正确的协议之后,点击增加权限 Add permissions
2.png

现在,你应该在你的 Entra application 权限种添加了 SMTP, POP 或者 IMAP 权限。
3. 基于 OAuth2 在 Web 服务中使用 MailKit

3.1 为您的 Web 服务注册服务主体

Web 应用代理用户访问 Exchange 需要先进行授权的,这需要到 Exchange 服务器上进行授权。
您的 Web 服务注册完成后,租户管理员需要注册您的服务主体。这是在 Exchange 中配置 Web 服务的访问权限。
要使用 New-ServicePrincipal 命令,请打开 Azure Powershell 终端,安装 ExchangeOnlineManagement 并连接到您的租户,如下所示:
  1. Install-Module -Name ExchangeOnlineManagement -allowprerelease
  2. Import-module ExchangeOnlineManagement
  3. Connect-ExchangeOnline -Organization <tenantId>
复制代码
然后,为你的 Web 服务注册服务主体, 这需要使用 New-ServicePrincipal 命令。
此 cmdlet 仅适用于基于云的服务。
使用 New-ServicePrincipal cmdlet 可在基于云的组织中创建服务主体。
Microsoft Entra ID 中的服务主体用于定义应用程序可以执行的操作、谁可以访问应用程序以及应用程序可以访问的资源。在 Exchange Online 中,服务主体是对 Microsoft Entra ID 中服务主体的引用。要将 Exchange Online 基于角色的访问控制 (RBAC) 角色分配给 Microsoft Entra ID 中的服务主体,请使用 Exchange Online 中的服务主体引用。Exchange Online PowerShell 中的 *-ServicePrincipal cmdlet 允许您查看、创建和删除这些服务主体引用。
有关更多信息,请参阅 Microsoft Entra ID 中的应用程序和服务主体对象。
您需要先获得权限才能运行此 cmdlet。虽然本主题列出了此 cmdlet 的所有参数,但如果某些参数未包含在分配给您的权限中,您可能无法访问它们。要查找在您的组织中运行任何 cmdlet 或参数所需的权限,请参阅查找运行任何 Exchange cmdlet 所需的权限。
ObjectId 参数指定服务主体的唯一 ObjectId GUID 值。例如,7c7599b2-23af-45e3-99ff-0025d148e929。
此参数的有效值可在以下位置获取:

  • Microsoft Graph PowerShell 中 Get-MgServicePrincipal cmdlet 输出中的 Id 属性。
  • Microsoft Entra 管理中心中企业应用程序中的 Object ID 属性
  1. New-ServicePrincipal -AppId  -ObjectId <OBJECT_ID> [-Organization <ORGANIZATION_ID>]
复制代码
3.2 为你的 Web 服务进行授权

为了授予您的 Web 服务访问 Office365 和/或 Exchange 帐户的权限,您需要首先使用以下命令获取上一步中注册的服务主体 ID:
  1. Get-ServicePrincipal | fl
复制代码
一旦你的 Web 服务拥有了服务主体标识 Service Principal ID,使用下面的命令,来为你 Web 服务种使用的邮箱账号添加完整的邮箱权限。
  1. Add-MailboxPermission -Identity "john.smith@example.com" -User <SERVICE_PRINCIPAL_ID> -AccessRights FullAccess
复制代码
3.3 使用 OAuth2 认证 Web 服务

现在,已经有了 Client ID 和 Tenant ID,需要把他们使用到你的应用中。
在下面的示例代码中,使用 Microsoft.Identity.Client NuGet 包来获得访问令牌 Access token, MailKit 需要将它传递给 Exchange Server.
  1. // protocal 是准备使用的邮件访问协议,例如 "SMTP"
  2. // 不同的协议,需要使用的 scope 不同
  3. static async Task GetConfidentialClientOAuth2CredentialsAsync (string protocol, CancellationToken cancellationToken = default)
  4. {
  5.     var confidentialClientApplication = ConfidentialClientApplicationBuilder.Create (clientId)
  6.         .WithAuthority ($"https://login.microsoftonline.com/{tenantId}/v2.0")
  7.         .WithCertificate (certificate) // or .WithClientSecret (clientSecret)
  8.         .Build ();
  9.     string[] scopes;
  10.     if (protocol.Equals ("SMTP", StringComparison.OrdinalIgnoreCase)) {
  11.         scopes = new string[] {
  12.             // For SMTP, use the following scope
  13.             "https://outlook.office365.com/.default"
  14.         };
  15.     } else {
  16.         scopes = new string[] {
  17.             // For IMAP and POP3, use the following scope
  18.             "https://ps.outlook.com/.default"
  19.         };
  20.     }
  21.     return await confidentialClientApplication.AcquireTokenForClient (scopes).ExecuteAsync (cancellationToken);
  22. }
复制代码
3.3.1 SMTP

使用通过 GetConfidentialClientOAuth2CredentialsAsync() 方法获得的令牌访问邮箱。
  1. var result = await GetConfidentialClientOAuth2CredentialsAsync ("SMTP");
  2. var oauth2 = new SaslMechanismOAuth2 ("username@outlook.com", result.AccessToken);
  3. using (var client = new SmtpClient ()) {
  4.     await client.ConnectAsync ("smtp.office365.com", 587, SecureSocketOptions.StartTls);
  5.     await client.AuthenticateAsync (oauth2);
  6.     await client.DisconnectAsync (true);
  7. }
复制代码
4. 桌面和移动应用

现在您已获得客户端 ID 和租户 ID 字符串,您需要将这些值插入到您的应用程序中。
以下示例代码使用 Microsoft.Identity.Client Nuget 包来获取访问令牌,MailKit 需要该令牌才能将其传递给 Exchange 服务器。
  1. // 获得指定用户的访问令牌,protocol 为邮件访问协议,emailAddress 为用户邮箱
  2. static async Task GetPublicClientOAuth2CredentialsAsync (string protocol, string emailAddress, CancellationToken cancellationToken = default)
  3. {
  4.     var options = new PublicClientApplicationOptions {
  5.         ClientId = "Application (client) ID",
  6.         TenantId = "Directory (tenant) ID",
  7.         // Use "https://login.microsoftonline.com/common/oauth2/nativeclient" for apps using
  8.         // embedded browsers or "http://localhost" for apps that use system browsers.
  9.         RedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"
  10.     };
  11.     var publicClientApplication = PublicClientApplicationBuilder
  12.         .CreateWithApplicationOptions (options)
  13.         .Build ();
  14.     string[] scopes;
  15.     if (protocol.Equals ("IMAP", StringComparison.OrdinalIgnoreCase)) {
  16.         scopes = new string[] {
  17.             "email",
  18.             "offline_access",
  19.             "https://outlook.office.com/IMAP.AccessAsUser.All"
  20.         };
  21.     } else if (protocol.Equals ("POP", StringComparison.OrdinalIgnoreCase)) {
  22.         scopes = new string[] {
  23.             "email",
  24.             "offline_access",
  25.             "https://outlook.office.com/POP.AccessAsUser.All"
  26.         };
  27.     } else {
  28.         scopes = new string[] {
  29.             "email",
  30.             "offline_access",
  31.             "https://outlook.office.com/SMTP.Send"
  32.         };
  33.     }
  34.     try {
  35.         // First, check the cache for an auth token.
  36.         return await publicClientApplication.AcquireTokenSilent (scopes, emailAddress).ExecuteAsync (cancellationToken);
  37.     } catch (MsalUiRequiredException) {
  38.         // If that fails, then try getting an auth token interactively.
  39.         return await publicClientApplication.AcquireTokenInteractive (scopes).WithLoginHint (emailAddress).ExecuteAsync (cancellationToken);
  40.     }
  41. }
复制代码
4.1 简单邮件发送协议 SMTP
  1. var result = await GetPublicClientOAuth2CredentialsAsync ("SMTP", "username@outlook.com");
  2. // Note: We always use result.Account.Username instead of `Username` because the user may have selected an alternative account.
  3. var oauth2 = new SaslMechanismOAuth2 (result.Account.Username, result.AccessToken);
  4. using (var client = new SmtpClient ()) {
  5.     await client.ConnectAsync ("smtp.office365.com", 587, SecureSocketOptions.StartTls);
  6.     await client.AuthenticateAsync (oauth2);
  7.     await client.DisconnectAsync (true);
  8. }
复制代码
注意:一旦使用上述交互式方法获取了身份验证令牌,您就可以避免提示用户,如果您缓存结果。帐户信息,然后使用以下代码在将来静默地重新获取身份验证令牌:
  1. var result = await publicClientApplication.AcquireTokenSilent(scopes, account).ExecuteAsync(cancellationToken);
复制代码
注意:有关缓存令牌的信息,请参阅 Microsoft 有关如何实现 跨平台令牌缓存 的文档。
4.2 IMAP
  1. var result = await GetPublicClientOAuth2CredentialsAsync ("IMAP", "username@outlook.com");
  2. // Note: We always use result.Account.Username instead of `Username` because the user may have selected an alternative account.
  3. var oauth2 = new SaslMechanismOAuth2 (result.Account.Username, result.AccessToken);
  4. using (var client = new ImapClient ()) {
  5.     await client.ConnectAsync ("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect);
  6.     await client.AuthenticateAsync (oauth2);
  7.     await client.DisconnectAsync (true);
  8. }
复制代码
5 Web 应用

独立用户通过微软认证之后,发送或者接收邮件。
5.1 使用 OAuth2 认证 Web 应用

如果你希望代理用户进行邮件收/发,请使用如下代码:
  1. // Common Code
  2. using Microsoft.Graph;
  3. using Microsoft.Identity.Client;
  4. using Microsoft.Kiota.Abstractions.Authentication;
  5. public static class OAuthMicrosoft
  6. {
  7.     public static readonly string[] RegistrationScopes = new string[] {
  8.         "offline_access",
  9.         "User.Read",
  10.         "Mail.Send",
  11.         "https://outlook.office.com/SMTP.Send",
  12.         "https://outlook.office.com/IMAP.AccessAsUser.All",
  13.     };
  14.     public static readonly string[] SmtpScopes = new string[] {
  15.         "email",
  16.         "offline_access",
  17.         "https://outlook.office.com/SMTP.Send"
  18.     };
  19.     public static readonly string[] ImapScopes = new string[] {
  20.         "email",
  21.         "offline_access",
  22.         "https://outlook.office.com/IMAP.AccessAsUser.All",
  23.     };
  24.     // 获得访问令牌
  25.     public static IConfidentialClientApplication CreateConfidentialClient ()
  26.     {
  27.         var clientId = "Application (client) ID";
  28.         var tenantId = "common"; // common = anybody with microsoft account personal or organization; other options see https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints
  29.         var clientSecret = "client secret";
  30.         var redirectURL = "https://example.com/oauth/microsoft/callback";
  31.         var confidentialClientApplication = ConfidentialClientApplicationBuilder.Create (clientId)
  32.             .WithAuthority ($"https://login.microsoftonline.com/{tenantId}/v2.0")
  33.             .WithClientSecret (clientSecret)
  34.             .WithRedirectUri (redirectURL)
  35.             .Build ();
  36.         // You also need to configure an MSAL token cache. so that token are remembered.
  37.         return confidentialClientApplication;
  38.     }
  39. }
复制代码
注册页面,将用户重定向到微软页面进行认证
  1. // Registration page - redirect user to Microsoft to get authorization
  2. // 注册页面,重定向用户到微软认证页面
  3. public async Task<IActionResult> OnPostAsync ()
  4. {
  5.     var client = OAuthMicrosoft.CreateConfidentialClient ();
  6.     // Note: When getting authorization, specify all of the scopes that your application will ever need (eg. SMTP /and/ IMAP).
  7.     // Later, when requesting an access token, you will only ask for the specific scopes that you need (e.g. SMTP).
  8.     var authurlbuilder = client.GetAuthorizationRequestUrl (OAuthMicrosoft.RegistrationScopes);
  9.     var authurl = await authurlbuilder.ExecuteAsync ();
  10.     return this.Redirect (authurl.ToString ());
  11. }
  12. // Callback page = https://example.com/oauth/microsoft/callback in this example
  13. // 回调页面
  14. public async Task<IActionResult> OnGet ([FromQuery] string code)
  15. {
  16.     var confidentialClientApplication = OAuthMicrosoft.CreateConfidentialClient ();
  17.     var scopes = OAuthMicrosoft.SmtpScopes;
  18.     var auth = await confidentialClientApplication.AcquireTokenByAuthorizationCode (scopes, code).ExecuteAsync (); //this saves the token in msal cache
  19.     var ident = auth.Account.HomeAccountId.Identifier;
  20.     // Note: you will need to persist the ident to refer to later.
  21. }
  22. // Use the credentials
  23. // 发邮件
  24. public async Task SendEmailAsync (string ident)
  25. {
  26.     var confidentialClientApplication = OAuthMicrosoft.CreateConfidentialClient ();
  27.     var account = await confidentialClientApplication.GetAccountAsync (ident);
  28.     var scopes = OAuthMicrosoft.SmtpScopes;
  29.     try {
  30.         var auth = await confidentialClientApplication.AcquireTokenSilent (scopes, account).ExecuteAsync ();
  31.         using (var client = new SmtpClient ()) {
  32.             await client.ConnectAsync ("smtp-mail.outlook.com", 587, SecureSocketOptions.StartTls);
  33.             var oauth2 = new SaslMechanismOAuth2 (auth.Account.Username, auth.AccessToken);
  34.             await client.AuthenticateAsync (oauth2);
  35.             var serverfeedback = await client.SendAsync (message);
  36.             await client.DisconnectAsync (true);
  37.         }
  38.     } catch (MsalUiRequiredException) {
  39.         throw new Exception ("Need to get authorization again");
  40.     }
  41. }
  42. public async Task TestImapAsync (string ident)
  43. {
  44.     var confidentialClientApplication = OAuthMicrosoft.CreateConfidentialClient ();
  45.     var account = await confidentialClientApplication.GetAccountAsync (ident);
  46.     var scopes = OAuthMicrosoft.ImapScopes;
  47.     var auth = await confidentialClientApplication.AcquireTokenSilent (scopes, account).ExecuteAsync ();   
  48.     var oauth2 = new SaslMechanismOAuth2 (auth.Account.Username, auth.AccessToken);
  49.     using (var client = new ImapClient ()) {
  50.         await client.ConnectAsync ("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect);
  51.         await client.AuthenticateAsync (oauth2);
  52.         await client.DisconnectAsync (true);
  53.     }
  54. }
复制代码
参考资料


  • How can I implement service-to-service authentication with Office365 using the OAuth2 SASL mechanism? #1126

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册