从移动应用调用 Web API

在你的应用将用户登录并收到令牌以后,Microsoft 身份验证库 (MSAL) 会公开有关用户、用户的环境以及所颁发令牌的信息。 应用可以使用这些值来调用 web API 或向用户显示欢迎消息。

本文首先介绍 MSAL 结果。 然后介绍如何使用 AuthenticationResultresult 中的访问令牌来调用受保护的 Web API。

MSAL 结果

MSAL 提供下列值:

  • AccessToken 在 HTTP 持有者请求中调用受保护的 Web API。
  • IdToken 包含有关已登录用户的有用信息。 此信息包括用户的姓名、主租户和存储的唯一标识符。
  • ExpiresOn 是令牌的过期时间。 MSAL 处理应用的自动刷新。
  • TenantId 是用户登录到的租户的标识符。 对于 Microsoft Entra B2B 中的来宾用户,此值标识用户登录到的租户。 该值不能标识用户的主租户。
  • Scopes 指示通过令牌授予的作用域。 授予的作用域可能是你请求的作用域的子集。

MSAL 还为 Account 值提供抽象。 Account 值表示当前用户的登录帐户:

  • HomeAccountIdentifier 标识用户的主租户。
  • UserName 是用户的首选用户名。 对于 Azure AD B2C 用户,此值可能为空。
  • AccountIdentifier 标识已登录的用户。 在大多数情况下,此值与 HomeAccountIdentifier 值相同,除非用户是另一个租户中的来宾。

调用 API

拥有访问令牌后,可以调用 Web API。 应用将使用该令牌生成 HTTP 请求,然后运行该请求。

Android

        RequestQueue queue = Volley.newRequestQueue(this);
        JSONObject parameters = new JSONObject();

        try {
            parameters.put("key", "value");
        } catch (Exception e) {
            // Error when constructing.
        }
        JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, MSGRAPH_URL,
                parameters,new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                // Successfully called Graph. Process data and send to UI.
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // Error.
            }
        }) {
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> headers = new HashMap<>();

                // Put access token in HTTP request.
                headers.put("Authorization", "Bearer " + authResult.getAccessToken());
                return headers;
            }
        };

        request.setRetryPolicy(new DefaultRetryPolicy(
                3000,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        queue.add(request);

适用于 iOS 和 MacOS 的 MSAL

用于获取令牌的方法将返回 MSALResult 的对象。 MSALResult 公开 accessToken 属性。 可以使用 accessToken 来调用 Web API。 在调用之前,将此属性添加到 HTTP 授权标头,用于访问受保护的 Web API。

NSMutableURLRequest *urlRequest = [NSMutableURLRequest new];
urlRequest.URL = [NSURL URLWithString:"https://contoso.api.com"];
urlRequest.HTTPMethod = @"GET";
urlRequest.allHTTPHeaderFields = @{ @"Authorization" : [NSString stringWithFormat:@"Bearer %@", accessToken] };

NSURLSessionDataTask *task =
[[NSURLSession sharedSession] dataTaskWithRequest:urlRequest
     completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {}];
[task resume];
let urlRequest = NSMutableURLRequest()
urlRequest.url = URL(string: "https://contoso.api.com")!
urlRequest.httpMethod = "GET"
urlRequest.allHTTPHeaderFields = [ "Authorization" : "Bearer \(accessToken)" ]

let task = URLSession.shared.dataTask(with: urlRequest as URLRequest) { (data: Data?, response: URLResponse?, error: Error?) in }
task.resume()

Xamarin

MSAL.NET 中的 AuthenticationResult 属性

用于获取令牌的方法返回 AuthenticationResult。 对于异步方法,将返回 Task<AuthenticationResult>

在 MSAL.NET 中,AuthenticationResult 会公开

  • AccessToken,以便 Web API 访问资源。 此参数是一个字符串,通常是一个 Base-64 编码的 JWT。 客户端不应该查看访问令牌的内部。 不保证格式稳定,并且可以为资源加密令牌。 编写的代码依赖于客户端上的访问令牌内容是最大的错误来源之一,并且会违反客户端逻辑。 有关详细信息,请参阅访问令牌
  • 用户的 IdToken。 此参数是编码的 JWT。 有关详细信息,请参阅 ID 令牌
  • ExpiresOn 会告知令牌过期的日期和时间。
  • TenantId 包含用户所在的租户。 对于来宾用户(Microsoft Entra B2B 方案),租户 ID 是来宾租户,而不是唯一的租户。 为用户传送令牌时,AuthenticationResult 还包含有关此用户的信息。 对于在请求令牌时未提供应用用户的机密客户端流,此用户信息为 null。
  • 令牌的颁发Scopes
  • 用户的唯一 ID。

IAccount

MSAL.NET 通过 IAccount 接口定义了帐户的概念。 此中断性变更提供了正确的语义。 同一用户可以在不同的 Microsoft Entra 目录中拥有多个帐户。 此外,由于会提供主帐户信息,MSAL.NET 可以在使用来宾方案的情况下提供更有用的信息。 下图显示了 IAccount 接口的结构。

IAccount interface structure

AccountId 类使用下表中显示的属性标识特定租户中的帐户。

属性 说明
TenantId GUID 的字符串表示形式,是帐户所在租户的 ID。
ObjectId GUID 的字符串表示形式,是拥有租户中的帐户的用户的 ID。
Identifier 帐户的唯一标识符。 IdentifierObjectIdTenantId 的串联,由逗号分隔。 它们不是 Base 64 编码的。

IAccount 接口表示单个帐户的相关信息。 同一用户可以存在于不同的租户中,这意味着一个用户可以有多个帐户。 其成员显示在下表中。

属性 说明
Username 一个字符串,包含 UserPrincipalName (UPN) 格式的可显示值,例如 john.doe@contoso.com。 此字符串可以为 null,这不同于 HomeAccountId 和 HomeAccountId.Identifier,后两者不会为 null。 此属性替换 MSAL.NET 旧版本中 IUserDisplayableId 属性。
Environment 一个字符串,包含此帐户的标识提供者,例如 login.partner.microsoftonline.cn。 此属性替换 IUserIdentityProvider 属性,不同之处是 IdentityProvider 还包含除云环境以外的租户信息。 而此处的该值仅仅是主机。
HomeAccountId 用户的主帐户的帐户 ID。 此属性在 Microsoft Entra 租户中唯一标识用户。

使用令牌调用受保护的 API

在 MSAL 在 result 中返回 AuthenticationResult 后,将它添加到 HTTP 授权标头,然后再调用该令牌以访问受保护的 Web API。

httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

// Call the web API.
HttpResponseMessage response = await _httpClient.GetAsync(apiUri);
...
}

发出多个 API 请求

若要多次调用同一 API,或者要调用多个 API,请在构建应用时考虑以下主题:

  • 增量同意:Microsoft 标识平台允许应用在需要权限时(而不是在开始时)获取用户同意。 每次应用准备好调用 API 时,它应只请求所需的作用域。

  • 条件访问:当发出多个 API 请求时,在某些情况下可能必须满足其他条件访问要求。 如果第一个请求没有条件访问策略,并且你的应用尝试以无提示方式访问要求条件访问的新 API,这样就会导致所需满足的要求增加。 若要解决此问题,请务必捕获无提示请求中的错误,并准备好发送交互式请求。 有关详细信息,请参阅条件访问指导

若要为同一用户调用多个 API,则在为用户获取令牌后,可以通过随后调用 AcquireTokenSilent 获取令牌来避免重复要求用户提供凭据:

var result = await app.AcquireTokenXX("scopeApi1")
                      .ExecuteAsync();

result = await app.AcquireTokenSilent("scopeApi2")
                  .ExecuteAsync();

以下情况需要交互:

  • 用户同意了第一个 API,但现在需要同意更多作用域。 在这种情况下,使用增量同意。
  • 第一个 API 不需要多重身份验证,但下一个 API 需要。
var result = await app.AcquireTokenXX("scopeApi1")
                      .ExecuteAsync();

try
{
 result = await app.AcquireTokenSilent("scopeApi2")
                  .ExecuteAsync();
}
catch(MsalUiRequiredException ex)
{
 result = await app.AcquireTokenInteractive("scopeApi2")
                  .WithClaims(ex.Claims)
                  .ExecuteAsync();
}

后续步骤

转到此方案中的下一篇文章:移到生产环境