OAuth를 사용한 메일 서비스 액세스
Aspose.Email for .NET은 OAuth 2.0을 지원하며, 이를 사용해 SMTP, POP3, IMAP, EWS 서버에 액세스할 수 있습니다. 일반적으로 OAuth 2.0 베어러 토큰을 지원하는 모든 서버와 함께 사용할 수 있지만, 이메일 클라이언트는 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 Resource Owner Password Credential(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))
{
}