استفاده از OAuth برای دسترسی به سرویس‌های ایمیل

پشتیبانی از OAuth 2.0 به Aspose.Email اضافه شده است و می‌تواند برای دسترسی به سرورهای SMTP، POP3، IMAP و EWS استفاده شود. به طور کلی، تمام سرورهایی که توکن‌های حامل OAuth 2.0 را پشتیبانی می‌کنند می‌توانند با Aspose.Email استفاده شوند، اما کلاینت‌های ایمیل ما با سرورهای ایمیل گوگل و سرورهای Microsoft Office 365 آزمایش شده‌اند. دسترسی به سرور از طریق SmtpClient, Pop3Client, ImapClient و EWSClient با OAuth می‌تواند به دو روش پیاده‌سازی شود.

  1. توکن دسترسی را مستقیماً به سازندهٔ کلاینت ایمیل ارائه کنید. در این حالت، کاربر باید بداند که طول عمر توکن‌های دسترسی محدود است. هنگامی که توکن منقضی شود، کلاینت ایمیل نمی‌تواند برای دسترسی به سرور استفاده شود.
  2. ارائه یک پیاده‌سازی سفارشی از ارائه‌دهنده توکن بر پایه ITokenProvider رابط کاربری به سازندهٔ کلاینت ایمیل. در این حالت، کلاینت زمان انقضای توکن را بررسی می‌کند و درخواست می‌کند ITokenProvider برای دریافت توکن دسترسی جدید زمانی که توکن قبلی منقضی شده باشد. به این ترتیب، کلاینت به‌طور دوره‌ای توکن‌ها را تازه‌سازی می‌کند و می‌تواند برای زمان نامحدود با سرور کار کند. اکثر سرویس‌ها روش ساده‌ای برای تازه‌سازی توکن‌های دسترسی ارائه می‌دهند. برای مثال، استفاده از توکن‌های تازه‌سازی در سرویس‌های گوگل یا جریان احراز هویت ROPC در پلتفرم هویت مایکروسافت می‌تواند برای پیاده‌سازی ارائه‌دهنده توکن استفاده شود.

پیکربندی حساب بر روی سرور مناسب

مقالات زیر به شما کمک می‌کنند حساب‌ها را برای دسترسی به سرویس‌های ایمیل پیکربندی کنید.

دسترسی به سرویس‌های ایمیل با توکن‌های دسترسی

نمونه‌های کد زیر نشان می‌دهند چگونه با استفاده از توکن‌های دسترسی به سرویس‌های ایمیل متصل شوید.

// Connecting to SMTP server
try (SmtpClient client = new SmtpClient(
        "smtp.gmail.com",
        587,
        "user1@gmail.com",
        "accessToken",
        true,
        SecurityOptions.SSLExplicit)) {

}

// Connecting to IMAP server
try (ImapClient client = new ImapClient(
        "imap.gmail.com",
        993,
        "user1@gmail.com",
        "accessToken",
        true,
        SecurityOptions.SSLImplicit)) {

}

// Connecting to POP3 server
try (Pop3Client client = new Pop3Client(
        "pop.gmail.com",
        995,
        "user1@gmail.com",
        "accessToken",
        true,
        SecurityOptions.Auto)) {

}

دسترسی به سرویس‌های ایمیل با ارائه‌دهندگان توکن

نمونه‌های کد زیر نشان می‌دهند چگونه با استفاده از یک ارائه‌دهنده توکن به سرویس‌های ایمیل متصل شوید.

ITokenProvider tokenProvider = TokenProvider.Google.getInstance(
        "ClientId",
        "ClientSecret",
        "RefreshToken");

// Connecting to SMTP server
try (SmtpClient client = new SmtpClient(
        "smtp.gmail.com",
        587,
        "user1@gmail.com",
        tokenProvider,
        SecurityOptions.SSLExplicit)) {

}

// Connecting to IMAP server
try (ImapClient client = new ImapClient(
        "imap.gmail.com",
        993,
        "user1@gmail.com",
        tokenProvider,
        SecurityOptions.SSLImplicit)) {

}

// Connecting to POP3 server
try (Pop3Client client = new Pop3Client(
        "pop.gmail.com",
        995,
        "user1@gmail.com",
        tokenProvider,
        SecurityOptions.Auto)) {

}

پیاده‌سازی ITokenProvider سفارشی برای Office 365

می‌توانید از پیاده‌سازی ارائه‌دهنده توکن زیر برای دسترسی به سرویس‌های ایمیل Office 365 استفاده کنید.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * Azure resource owner password credential (ROPC) token provider
 * https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc
 * https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth
 * https://portal.azure.com
 * https://developer.microsoft.com/en-us/graph/graph-explorer/#
 * token parser https://jwt.io
 * </p>
 */
class AzureROPCTokenProvider implements ITokenProvider {

    private static final String GRANT_TYPE = "password";

    private final String clientId;
    private final String clientSecret;
    private final String userName;
    private final String password;
    private final String tenant;
    private final String scope;

    private OAuthToken token;

    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 = joinToStr(scopeAr, " ");
    }

    public synchronized OAuthToken getAccessToken(boolean ignoreExistingToken) {
        if (this.token != null && !this.token.getExpired() && !ignoreExistingToken)
            return this.token;
        token = null;

        Map<String, String> tokenArgs = geToken();

        java.util.Calendar c = java.util.Calendar.getInstance();
        c.add(java.util.Calendar.SECOND, Integer.parseInt(tokenArgs.get("expires_in")));
        token = new OAuthToken(tokenArgs.get("access_token"), TokenType.AccessToken, c.getTime());
        return token;
    }

    public final OAuthToken getAccessToken() {
        return getAccessToken(false);
    }

    public void dispose() {
    }

    private String getEncodedParameters() {
        return "client_id=" + urlEncode(clientId) + "&scope=" + urlEncode(scope) + "&username=" + urlEncode(userName)
                + "&password=" + urlEncode(password) + "&grant_type="
                + urlEncode(GRANT_TYPE);
    }

    private String getUri() {
        if (tenant == null || tenant.trim().isEmpty())
            return "https://login.microsoftonline.com/common/oauth2/v2.0/token";
        else
            return "https://login.microsoftonline.com/" + tenant + "/oauth2/v2.0/token";
    }

    private Map<String, String> geToken() {
        try {
            HttpURLConnection connection = (HttpURLConnection) new URL(getUri()).openConnection();
            connection.setRequestMethod("POST");

            byte[] requestData = getEncodedParameters().getBytes(StandardCharsets.UTF_8);

            connection.setUseCaches(false);
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            connection.setRequestProperty("Content-Length", "" + requestData.length);

            final OutputStream st = connection.getOutputStream();
            try {
                st.write(requestData, 0, requestData.length);
            } finally {
                st.flush();
                st.close();
            }

            connection.connect();

            if (connection.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
                throw new IllegalAccessError("Operation failed: " + connection.getResponseCode() + "/" +
                        connection.getResponseMessage() + "\r\nDetails:\r\n{2}"
                        + readInputStream(connection.getErrorStream()));
            }

            String responseText = readInputStream(connection.getInputStream());

            Map<String, String> result = new HashMap<>();
            String[] fromJsonToKeyValue = responseText.replace("{", "").replace("}", "")
                    .replace("\"", "").replace("\r", "")
                    .replace("\n", "").split(",");
            for (String keyValue : fromJsonToKeyValue) {
                String[] pair = keyValue.split(":");
                String name = pair[0].trim().toLowerCase();
                String value = urlDecode(pair[1].trim());
                result.put(name, value);
            }

            return result;
        } catch (IOException e) {
            throw new IllegalAccessError(e.getMessage());
        }
    }

    static String urlEncode(String value) {
        try {
            return URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
        } catch (UnsupportedEncodingException e) {
            throw new IllegalAccessError(e.getMessage());
        }
    }

    static String urlDecode(String value) {
        try {
            return URLDecoder.decode(value, StandardCharsets.UTF_8.toString());
        } catch (UnsupportedEncodingException e) {
            throw new IllegalAccessError(e.getMessage());
        }
    }

    static String readInputStream(InputStream is) {
        if (is == null)
            return "";

        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder result = new StringBuilder();
        String line;
        try {
            while ((line = reader.readLine()) != null) {
                result.append(line);
            }
        } catch (IOException e) {
            // ignore
        }
        return result.toString();
    }

    static String joinToStr(String[] arr, String sep) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            if (i > 0)
                sb.append(sep);
            sb.append(arr[i]);
        }
        return sb.toString();
    }
}

نمونه‌های کد زیر نشان می‌دهند چگونه با استفاده از ارائه‌دهنده توکن سفارشی به سرویس‌های Office 365 متصل شوید.

ITokenProvider tokenProvider = new AzureROPCTokenProvider(
        "Tenant",
        "ClientId",
        "ClientSecret",
        "EMail",
        "Password",
        scopes);

// Connecting to SMTP server
try (SmtpClient client = new SmtpClient(
        "smtp.office365.com",
        587,
        "Test1@test.onmicrosoft.com",
        tokenProvider,
        SecurityOptions.SSLExplicit)) {

}

// Connecting to IMAP server
try (ImapClient client = new ImapClient(
        "outlook.office365.com",
        993,
        "Test1@test.onmicrosoft.com",
        tokenProvider,
        SecurityOptions.SSLImplicit)) {

}

// Connecting to POP3 server
try (Pop3Client client = new Pop3Client(
        "outlook.office365.com",
        995,
        "Test1@test.onmicrosoft.com",
        tokenProvider,
        SecurityOptions.Auto)) {

}

// Connecting to EWS server
final String mailboxUri = "https://outlook.office365.com/ews/exchange.asmx";
ICredentials credentials = new OAuthNetworkCredential(tokenProvider);
try (IEWSClient ewsClient = EWSClient.getEWSClient(mailboxUri, credentials)) {

}

مجوزهای دسترسی به Office 365 از طریق IMAP، POP3 یا SMTP

ما نیاز داریم مجوزهای صحیح API را اعمال کنیم و موافقت مدیر را برای دسترسی به سرویس‌های ایمیل Office 365 اعطا کنیم:

todo:image_alt_text

در ویزارد مجوزهای API / افزودن یک مجوز، Microsoft Graph را انتخاب کنید و سپس مجوزهای Delegated را برای یافتن دامنه‌های مجوز زیر انتخاب کنید:

offline_access
IMAP.AccessAsUser.All
POP.AccessAsUser.All
SMTP.Send

مثال ارائه‌دهنده توکن:

ITokenProvider tokenProvider = new AzureROPCTokenProvider(OAuth.Tenant, OAuth.ClientId, OAuth.ClientSecret, User.EMail, User.Password,
        new String[] {
                "offline_access",
                "https://outlook.office.com/IMAP.AccessAsUser.All", // IMAP scope
                "https://outlook.office.com/POP.AccessAsUser.All",  // POP3 scope
                "https://outlook.office.com/SMTP.Send"              // SMTP scope
        });