65.9K
CodeProject 正在变化。 阅读更多。
Home

创建 MSAL 身份验证提供程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (3投票s)

2021 年 11 月 17 日

CPOL

4分钟阅读

viewsIcon

10188

在本文中,我们将创建一个身份验证提供程序,并了解如何在 Spring Boot Web 应用程序中使用 Graph API 客户端。

Microsoft Graph API 提供了对与用户及其文档、电子邮件、联系人、会议等相关的丰富数据的访问。所有这些数据都通过 HTTP API 及其返回的 JSON 对象提供。但是,解析原始 JSON 容易出错。此外,它通常涉及对映射的繁琐遍历 — 创建类来表示返回的对象将非常耗时。

在我们第一个系列中,我们创建了一个基本的 Graph 应用程序,该应用程序读取已登录用户的日历数据。在那里,我们直接调用 Graph API 以保持我们的介绍简单。但是,当我们构建更复杂的 Graph 应用程序时,我们更希望使用一种经过改进、类型检查的方法来访问 API。

幸运的是,Microsoft 提供了类型化的库,并将类映射到 Graph API 公开的实体。使用 Microsoft Graph SDK for Java 等库,我们的应用程序可以以类型安全且对 IDE 友好的方式导航 Graph API。

但是,首先,我们需要创建一个身份验证提供程序,让我们的 Java 应用程序可以代表用户调用 Graph API。因此,在本文中,我们将创建一个身份验证提供程序,并了解如何在 Spring Boot Web 应用程序中使用 Graph API 客户端。

添加 Graph 客户端依赖项

第一步是添加 Graph 客户端依赖项。这通过 Microsoft Graph Maven 依赖项提供。

<dependency>
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph</artifactId>
<version>5.4.0</version>
</dependency>

查询 Graph API

要使用 Graph API 客户端,我们调用 GraphServiceClient,它公开了一个用于所有可用终结点的生成器接口。在这里,我们调用 me 终结点,它返回当前登录用户的信息。

GraphServiceClient.builder
        .authenticationProvider (new SomeAuthenticationProviderGoesHere)
        .buildClient
        .me
        .buildRequest
        .get;

请注意,我们在调用 authenticationProvider 方法时使用了占位符类。

要调用 Graph API,我们需要传递一个有效的访问令牌。此令牌由 IAuthenticationProvider 接口的实现生成,并通过 authenticationProvider 方法传递给 Graph 客户端。SDK 开箱即用地提供了许多此类实现,但没有为需要由 OAuth2 资源服务器生成的代表(OBO)令牌的 Java 应用程序提供实现。

幸运的是,我们可以创建自己的身份验证提供程序。

生成 OBO 令牌

OBO 令牌是通过将资源服务器收到的 JSON Web 令牌(JWT)作为特殊格式的 HTTP 请求的一部分传递给 OAuth2 授权服务器来生成的。Microsoft 文档提供了 HTTP 请求的详细说明。

除了原始 HTTP 调用之外,还可以使用 MSAL 库中提供的类来完成 OBO 流。我们可以看到 AADOBOOAuth2AuthorizedClientProvider 类中实现了 OBO 流的示例。使用此现有代码作为灵感,我们可以创建自己的身份验证提供程序,名为 OboAuthenticationProvider。要继续学习,请查看此类的完整代码,位于以下包中:

package com.matthewcasperson.onenotebackend.providers;

让我们来分解一下这段代码。

我们的类扩展了 BaseAuthenticationProvider 类。该类反过来实现了 IAuthenticationProvider 接口。

public class OboAuthenticationProvider extends BaseAuthenticationProvider {

我们需要 Azure AD 应用程序的详细信息来完成请求,包括客户端 ID、客户端密钥、租户 ID 以及我们请求访问的范围。

  private final String tenantId;
  private final String clientId;
  private final String clientSecret;
  private final Set<String> scopes;
 
  public OboAuthenticationProvider(
      final Set<String> scopes,
      final String tenantId,
      final String clientId,
      final String clientSecret) {
    this.scopes = scopes;
    this.tenantId = tenantId;
    this.clientId = clientId;
    this.clientSecret = clientSecret;
  }

getAuthorizationTokenAsync 方法返回一个 Future,如果提供的 URL 不需要令牌,或者如果从授权服务器返回 OBO 令牌,则该 Future 包含 null

  @Nonnull
  @Override
  public CompletableFuture<String> getAuthorizationTokenAsync(@Nonnull final URL url) {

并非所有请求都需要令牌。如果请求的 URL 不需要令牌,shouldAuthenticateRequestWithUrl 方法将返回 false。在这种情况下,我们将 null 返回给调用者。

    if (!shouldAuthenticateRequestWithUrl(url)) {
      return CompletableFuture.completedFuture(null);
    }

对于确实需要令牌的请求,我们首先读取调用者传递给资源服务器的访问令牌。

    final AbstractOAuth2TokenAuthenticationToken oauth2Token =
        (AbstractOAuth2TokenAuthenticationToken) SecurityContextHolder
            .getContext()
            .getAuthentication();

    final String accessToken = oauth2Token.getToken().getTokenValue();

要传递给授权服务器的参数由 OnBehalfOfParameters 类捕获。

    final OnBehalfOfParameters parameters = OnBehalfOfParameters
        .builder(scopes, new UserAssertion(accessToken))
        .build();

然后使用这些参数从 ConfidentialClientApplication 获取令牌,并将生成的令牌返回给调用者。

    return createApp()
        .map(a -> a.acquireToken(parameters).thenApply(IAuthenticationResult::accessToken))
        .orElse(CompletableFuture.failedFuture(new Exception("Failed to generate obo token.")));
  }

我们使用 createApp 方法创建一个 ConfidentialClientApplication

  private Optional<ConfidentialClientApplication> createApp() {

客户端需要一个基于租户 ID 的颁发者(authority)值以及客户端密钥。

    final String authority = "https://login.microsoftonline.com/" + tenantId;
    final IClientSecret clientCredential = ClientCredentialFactory.createFromSecret(clientSecret);

然后,我们通过生成器接口创建一个 ConfidentialClientApplication

try {
      return Optional
          .of(ConfidentialClientApplication.builder(clientId, clientCredential)
              .authority(authority)
              .build());
    } catch (final MalformedURLException e) {
      LOGGER.error("Failed to create ConfidentialClientApplication", e);
    }
    return Optional.empty();
  }

访问 Azure AD 应用程序详细信息

要创建 OboAuthenticationProvider 类的新实例,我们需要访问 Azure AD 应用程序客户端 ID、客户端密钥和租户 ID。

在典型的启用了 Azure AD 的 Spring 应用程序中,这些值定义在 application.properties 文件或 application.yml 文件中。以下是 application.yml 的示例:

azure:
  activedirectory:
    client-id: ${CLIENT_ID}
    client-secret: ${CLIENT_SECRET}
    app-id-uri: ${APP_URI}
    tenant-id: ${TENANT_ID}

我们可以将 AADAuthenticationProperties 的实例注入到我们的 Spring bean 中来访问这些属性。

@Autowired
AADAuthenticationProperties azureAd;

然后,我们使用 AADAuthenticationProperties 的 getter 来访问所需的属性,从而使我们能够创建 OboAuthenticationProvider 类的新实例。

GraphServiceClient.builder()
        .authenticationProvider(new OboAuthenticationProvider(
            Set.of("https://graph.microsoft.com/user.read"),
            azureAd.getTenantId(),
            azureAd.getClientId(),
            azureAd.getClientSecret()))
        .buildClient()
        .me()
        .buildRequest()
        .get();

这样,我们现在就可以通过 Graph API 客户端从我们的 Spring 资源服务器查询 Graph API 了。

结论

在本文中,我们学习了如何将 Microsoft Graph API 客户端添加到我们的 Spring 应用程序,以及如何构建一个身份验证提供程序来从调用者传入的访问令牌生成 OBO 令牌。这为我们提供了创建充当前端应用程序和 Graph API 之间网关的资源服务器所需的基础。

本系列的第二篇文章中,我们将使用 Graph API 客户端通过一个微服务来消费 OneNote 文档,该微服务允许将它们转换为 Markdown 格式。

© . All rights reserved.