使用 OAuth 访问邮件服务
Contents
[
Hide
]
Aspose.Email for .NET 支持 OAuth 2.0,可用于访问 SMTP、POP3、IMAP 和 EWS 服务器。一般来说,任何支持 OAuth 2.0 持有者令牌的服务器都可以与 Aspose.Email 配合使用,但已对 Google 邮件服务器和 Microsoft Office 365 服务器的电子邮件客户端进行了测试。
从服务器的访问 SmtpClient, Pop3Client, ImapClient,以及 EWSClient 使用 OAuth 可以通过两种方式实现:
- 直接向电子邮件客户端的构造函数提供访问令牌。在这种情况下,您必须记住访问令牌的生命周期是有限的。令牌过期后,电子邮件客户端将无法再用于访问服务器。
- 基于提供自定义令牌提供程序实现** ITokenProvider 接口到电子邮件客户端的构造函数。在这种情况下,客户端会检查令牌的到期时间,并从 ITokenProvider 当之前的令牌已过期时。这样,客户端会定期刷新令牌,并且可以无限期地与服务器一起工作。大多数服务都支持一种简单的刷新访问令牌方式。例如,Google 服务中的刷新令牌,或 Microsoft 身份平台中的 ROPC 认证流程,可用于实现令牌提供程序。
在适当的服务器上配置账户
以下文章帮助您配置帐户以访问邮件服务:
- 针对 Office 365
- 针对 Gmail
使用访问令牌访问邮件服务
以下代码示例演示了如何使用访问令牌连接邮件服务。
// Connecting to SMTP server
using (SmtpClient client = new SmtpClient(
"smtp.gmail.com",
587,
"user1@gmail.com",
"accessToken",
true,
SecurityOptions.SSLExplicit))
{
}
// Connecting to IMAP server
using (ImapClient client = new ImapClient(
"imap.gmail.com",
993,
"user1@gmail.com",
"accessToken",
true,
SecurityOptions.SSLImplicit))
{
}
// Connecting to POP3 server
using (Pop3Client client = new Pop3Client(
"pop.gmail.com",
995,
"user1@gmail.com",
"accessToken",
true,
SecurityOptions.Auto))
{
}
使用令牌提供程序访问邮件服务
以下代码示例展示了如何使用令牌提供程序连接到邮件服务。内置的 TokenProvider.Google 工厂用于获取能够使用 Google 刷新令牌自动刷新访问令牌的提供程序。
ITokenProvider tokenProvider = TokenProvider.Google.GetInstance(
"ClientId",
"ClientSecret",
"RefreshToken");
// Connecting to SMTP server
using (SmtpClient client = new SmtpClient(
"smtp.gmail.com",
587,
"user1@gmail.com",
tokenProvider,
SecurityOptions.SSLExplicit))
{
}
// Connecting to IMAP server
using (ImapClient client = new ImapClient(
"imap.gmail.com",
993,
"user1@gmail.com",
tokenProvider,
SecurityOptions.SSLImplicit))
{
}
// Connecting to POP3 server
using (Pop3Client client = new Pop3Client(
"pop.gmail.com",
995,
"user1@gmail.com",
tokenProvider,
SecurityOptions.Auto))
{
}
为 Office 365 实现自定义 ITokenProvider
当内置令牌提供程序不适用于您的场景时,您可以实现 ITokenProvider 接口本身。下面的示例实现了 Azure 资源所有者密码凭证(ROPC)流程,以获取并刷新 Office 365 邮件服务的访问令牌。该 GetAccessToken 方法缓存令牌,仅在当前令牌过期时向服务器请求新令牌。
using JsonConvert = Newtonsoft.Json.JsonConvert;
using Aspose.Email.Clients;
using Aspose.Email.Common.Utils;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Net;
using System.Text;
namespace TestNS
{
/// <summary>
/// Azure resource owner password credential (ROPC) token provider
/// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc
/// https://portal.azure.com
/// https://developer.microsoft.com/en-us/graph/graph-explorer/#
/// token parser https://jwt.io
/// </summary>
internal class AzureROPCTokenProvider : ITokenProvider
{
private const string uriFormat = "https://login.microsoftonline.com/{0}/oauth2/v2.0/token";
private const string bodyFormat =
"client_id={0}" +
"&scope={1}" +
"&username={2}" +
"&password={3}" +
"&grant_type={4}";
private readonly string scope;
private const string grant_type = "password";
private readonly object tokenSyncObj = new object();
private OAuthToken token;
private readonly string tenant;
private readonly string clientId;
private readonly string clientSecret;
private readonly string userName;
private readonly string password;
/// <summary>
/// Initializes a new instance of the <see cref="AzureROPCTokenProvider"/> class
/// </summary>
public AzureROPCTokenProvider(
string tenant,
string clientId,
string clientSecret,
string userName,
string password,
string[] scopeAr)
{
this.tenant = tenant;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.userName = userName;
this.password = password;
this.scope = string.Join(" ", scopeAr);
}
/// <summary>
/// Gets oAuth access token.
/// </summary>
/// <param name="ignoreExistingToken">
/// If ignoreExistingToken is true, requests a new token from the server. Otherwise the behaviour depends on whether the token exists or not.
/// If the token exists and its expiration date is not expired, returns the current token, otherwise requests a new token from the server.
/// </param>
/// <returns>Returns oAuth access token</returns>
public virtual OAuthToken GetAccessToken(bool ignoreExistingToken)
{
lock (tokenSyncObj)
{
if (this.token != null && !this.token.Expired && !ignoreExistingToken)
return this.token;
token = null;
string uri = string.Format(uriFormat, string.IsNullOrWhiteSpace(tenant) ? "common" : tenant);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
string body = string.Format(bodyFormat,
HttpUtility.UrlEncode(clientId),
HttpUtility.UrlEncode(scope),
HttpUtility.UrlEncode(userName),
HttpUtility.UrlEncode(password),
HttpUtility.UrlEncode(grant_type));
byte[] bytes = Encoding.ASCII.GetBytes(body);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = bytes.Length;
using (Stream requestStream = request.GetRequestStream())
requestStream.Write(bytes, 0, bytes.Length);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StringBuilder responseText = new StringBuilder();
bytes = new byte[1024];
int read = 0;
using (Stream stream = response.GetResponseStream())
{
while ((read = stream.Read(bytes, 0, bytes.Length)) > 0)
responseText.Append(Encoding.ASCII.GetString(bytes, 0, read));
}
string jsonString = responseText.ToString();
AzureTokenResponse t = JsonConvert.DeserializeObject<AzureTokenResponse>(jsonString);
token = new OAuthToken(
t.access_token,
TokenType.AccessToken,
DateTime.Now.AddSeconds(t.expires_in));
return token;
}
}
/// <summary>
/// Gets oAuth access token.
/// If the token exists and its expiration date is not expired, returns the current token, otherwise requests a new token from the server.
/// </summary>
/// <returns>Returns oAuth access token</returns>
public OAuthToken GetAccessToken()
{
return GetAccessToken(false);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public virtual void Dispose()
{
}
}
/// <summary>
/// A success response contains a JSON OAuth 2.0 response with the following parameters.
/// </summary>
public class AzureTokenResponse
{
/// <summary>
/// The requested access token. The calling web service can use this token to authenticate to the receiving web service.
/// </summary>
public string access_token { get; set; }
/// <summary>
/// Indicates the token type value. The only type that Azure AD supports is Bearer. For more information about bearer tokens,
/// see The OAuth 2.0 Authorization Framework: Bearer Token Usage (RFC 6750).
/// </summary>
public string token_type { get; set; }
/// <summary>
/// How long the access token is valid (in seconds).
/// </summary>
public int expires_in { get; set; }
/// <summary>
/// How long the access token is valid (in seconds).
/// </summary>
public int ext_expires_in { get; set; }
/// <summary>
/// The time when the access token expires.
/// The date is represented as the number of seconds from 1970-01-01T00:00:00Z UTC until the expiration time.
/// This value is used to determine the lifetime of cached tokens.
/// </summary>
public int expires_on { get; set; }
/// <summary>
/// The App ID URI of the receiving web service.
/// </summary>
public string resource { get; set; }
/// <summary>
/// If an access token was returned, this parameter lists the scopes the access token is valid for.
/// </summary>
public string scope { get; set; }
/// <summary>
/// Issued if the original scope parameter included the openid scope.
/// </summary>
public string id_token { get; set; }
/// <summary>
/// Issued if the original scope parameter included offline_access.
/// </summary>
public string refresh_token { get; set; }
}
}
下面的代码示例展示了如何使用上述自定义令牌提供程序连接到 Office 365 服务。同一提供程序实例可传递给 SMTP、IMAP、POP3 和 EWS 客户端。
ITokenProvider tokenProvider = new AzureROPCTokenProvider(
"Tenant",
"ClientId",
"ClientSecret",
"EMail",
"Password",
scopes);
// Connecting to SMTP server
using (SmtpClient client = new SmtpClient(
"smtp.office365.com",
587,
"Test1@test.onmicrosoft.com",
tokenProvider,
SecurityOptions.SSLExplicit))
{
}
// Connecting to IMAP server
using (ImapClient client = new ImapClient(
"outlook.office365.com",
993,
"Test1@test.onmicrosoft.com",
tokenProvider,
SecurityOptions.SSLImplicit))
{
}
// Connecting to POP3 server
using (Pop3Client client = new Pop3Client(
"outlook.office365.com",
995,
"Test1@test.onmicrosoft.com",
tokenProvider,
SecurityOptions.Auto))
{
}
// Connecting to EWS server
const string mailboxUri = "https://outlook.office365.com/ews/exchange.asmx";
ICredentials credentials = new OAuthNetworkCredential(tokenProvider);
using (IEWSClient ewsClient = EWSClient.GetEWSClient(mailboxUri, credentials))
{
}