Azure AD 设置与 Microsoft Graph 身份验证

Aspose.Email for Java 完全集成 Microsoft Graph,允许开发者管理 Microsoft 365 帐户中的邮件、联系人、日历和任务。本指南将带您创建 Azure AD 应用程序并配置身份验证,以便开始使用 Aspose.Email 编码 GraphClient.

在使用 Aspose.Email 调用 Microsoft Graph API 之前,您需要在 Azure Active Directory(Azure AD)中注册应用程序并配置身份验证。本页面涵盖以下内容:

  • 创建 Azure AD 应用程序(项目)。

  • 分配必要的 Microsoft Graph 权限。

  • 生成凭据(客户端 ID、客户端密钥、租户 ID)。

  • 在 Java 中使用 Aspose.Email 令牌提供程序进行身份验证。

完成后,您就可以在 Java 应用程序中使用 Microsoft Graph 了。

1. 创建 Azure AD 应用程序

按照以下步骤在 Azure 门户中注册您的应用程序:

  1. 登录到 Azure 门户.
  2. 导航至 Azure Active Directory应用注册新建注册

todo:image_alt_text

  1. 为您的应用程序输入 名称(例如,AsposeEmailGraphApp)。
  2. 选择受支持的帐户类型:
    • 单租户(仅限您的组织使用)
    • 多租户(如果多个组织需要访问)
  3. 可选地,设置 重定向 URI(交互式或网页身份验证所需)。
  4. 点击 注册

todo:image_alt_text

2. 创建客户端密钥

  1. 注册完成后,前往 证书与密钥新建客户端密钥
  2. 添加描述和过期时间。

todo:image_alt_text

  1. 复制生成的密钥值——以后将不再显示。

请妥善保管 客户端密钥;它是机密客户端身份验证所必需的。

您应该会看到新注册的应用程序页面。

todo:image_alt_text

3. 配置 Microsoft Graph 权限

  1. 导航至 API 权限添加权限Microsoft Graph
  2. 根据您的场景选择权限类型:委托应用
  3. 添加 Aspose.Email 操作所需的权限:
    • Contacts.ReadWrite – 用于管理联系人
    • Calendars.ReadWrite – 用于管理日历
    • Mail.ReadWrite – 用于读取和发送邮件
    • Tasks.ReadWrite – 用于管理任务
  4. 如有需要,点击 授予管理员同意

todo:image_alt_text

4. 允许公共客户端流程

指定应用程序是否为公共客户端。适用于使用不需要重定向 URI 的令牌授权流程的应用程序。

todo:image_alt_text

5. Microsoft Graph 身份验证

Aspose.Email 支持的身份验证方法

| 令牌提供程序 | 使用案例 | | ——————————– | ————————————————————————————— | | AzureConfidentialTokenProvider | 机密客户端(客户端 ID+密钥),用于服务器端应用 | | AzureROPCConfiguration | 资源所有者密码凭证(用户名+密码),用于非交互式场景 | | AzurePublicTokenProvider | 公共客户端(交互式登录) | | AzureTokenProviderBase | 自定义身份验证实现的基类 |

使用机密客户端进行身份验证

使用 AzureConfidentialTokenProvider 当您拥有 客户端 ID、客户端密钥和租户 ID 时进行身份验证:

AzureConfidentialTokenProvider provider = new AzureConfidentialTokenProvider(
    tenantId,
    clientId,
    clientSecret
);

IGraphClient client = GraphClient.getClient(provider, tenantId);
client.setResource(ResourceType.Users);
client.setResourceId(username);
client.setEndpoint("https://graph.microsoft.com");

这将设置一个完全验证的 IGraphClient,准备好与 Microsoft Graph 交互。

使用 ROPC(用户名和密码)进行身份验证

对于拥有用户名和密码的场景,请使用 AzureROPCConfiguration:

AzureROPCConfiguration ropcConfig = new AzureROPCConfiguration(
    tenantId,
    clientId,
    clientSecret,
    username,
    password
);

IGraphClient client = GraphClient.getClient(ropcConfig, tenantId);
client.setResource(ResourceType.Users);
client.setResourceId(username);
client.setEndpoint("https://graph.microsoft.com");

Microsoft Graph 的自定义令牌提供程序

Aspose.Email for Java 通过以下方式与 Microsoft Graph 集成 IGraphClient 接口。要对请求进行身份验证,需要实现该接口的 ITokenProvider 是必需的。虽然大多数开发者会使用内置的身份验证提供程序,但在某些情况下,例如使用 资源所有者密码凭证 (ROPC) 流程时,您可能需要创建自己的提供程序。

1. 使用 AzureROPCTokenProvider 实现 ITokenProvider

此类提供了接口的实现 ITokenProvider 使用 Azure 资源所有者密码凭证 (ROPC) 流程。以下示例仅用于演示。在生产环境中,我们建议使用更安全的流程,如客户端凭证或带 PKCE 的授权码。

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 = getToken();

        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> getToken() {
        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();
    }
}

2. 创建 ITokenProvider 对象

IGraphClient 接口负责构建请求、将其发送到 Microsoft Graph 并处理响应。要创建该接口的实例 IGraphClient,您必须提供该接口的实现 ITokenProvider。令牌提供程序通过提供有效的 OAuth 访问令牌来对请求进行身份验证。

以下代码示例演示了如何创建该接口的基本内联实现 ITokenProvider 接口,这是对 Microsoft Graph 请求进行身份验证所必需的:

ITokenProvider tokenProvider = new ITokenProvider() {
    Date expirationDate = null;

    @Override
    public void dispose() {
        // Clean up resources if necessary
    }

    @Override
    public OAuthToken getAccessToken(boolean ignoreExistingToken) {
        // Retrieve an OAuth access token.
        // If ignoreExistingToken is true, always request a new token.
        // Otherwise, return the existing token if it is valid, or request a new one.
        return null;
    }

    @Override
    public OAuthToken getAccessToken() {
        // Return a valid OAuth token.
        // If no valid token exists, request a new one.
        return new OAuthToken("token", expirationDate);
    }
};

3. 使用自定义令牌提供程序

一旦 ITokenProvider 设置完成后,您可以创建一个 GraphClient 实例。该客户端在调用 Microsoft Graph 时将使用提供的令牌提供程序进行身份验证。

ITokenProvider provider = new AzureROPCTokenProvider(
        tenantId,
        clientId,
        clientSecret,
        userName,
        password,
        new String[] {"https://graph.microsoft.com/.default"}
);

IGraphClient client = GraphClient.getClient(provider, tenantId);
client.setResource(ResourceType.Users);
client.setResourceId(userName);
client.setEndpoint("https://graph.microsoft.com");

// Now you can call Microsoft Graph APIs
var folders = client.listFolders(null);
for (GraphFolderInfo folder : folders) {
    System.out.println(folder.getDisplayName());
}

创建并认证客户端后,您可以开始向 Microsoft Graph 服务发起请求。