你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

处理 MSAL.NET 中的错误和异常

本文概述不同类型的错误,并提供处理常见登录错误的相关建议。

MSAL 错误处理基础知识

Microsoft 身份验证库 (MSAL) 中的异常旨在帮助应用开发者进行故障排除,而不会向最终用户显示。 异常消息未经本地化。

处理异常和错误时,可以使用异常类型本身和错误代码来区分不同的异常。 有关错误代码的列表,请参阅 Azure AD 身份验证和授权错误代码

在登录体验期间,可能会遇到有关许可、条件访问(MFA、设备管理、基于位置的限制)、令牌颁发和兑换以及用户属性的错误。

以下部分提供了有关应用错误处理的更多详细信息。

MSAL.NET 中的错误处理

异常类型

当库本身检测到错误状态(例如配置错误)时,将引发 MsalClientException

当身份提供程序 (AAD) 返回错误时,将引发 MsalServiceException。 这是服务器错误的翻译。

MsalUIRequiredExceptionMsalServiceException 的类型,表示需要用户交互,例如因为需要 MFA 或因为用户更改了密码并且无法静默获取令牌。

处理异常

处理 .NET 异常时,可以使用异常类型本身和 ErrorCode 成员来区分不同的异常。 ErrorCode 的值是 MsalError 类型的常量。

也可以查看 MsalClientExceptionMsalServiceExceptionMsalUIRequiredException 字段。

如果引发 MsalServiceException,请尝试查看身份验证和授权错误代码,以查看其中是否列出了相关代码。

如果抛出 MsalUIRequiredException,则表明用户需要进行交互式流程才能解决问题。 在桌面和移动应用程序等公共客户端应用程序中,这是通过调用 AcquireTokenInteractive(用于显示浏览器)来解决的。 在机密客户端应用中,Web 应用应将用户重定向到授权页面,并且 Web API 应返回指示身份验证失败的 HTTP 状态代码和标头(“401 未授权”和 WWW-Authenticate 标头)。

常见的 .NET 异常

下面是可能引发的常见异常和一些可能的缓解措施:

异常 错误代码 缓解措施
MsalUiRequiredException AADSTS65001:用户或管理员尚未许可使用名为“{appName}”、ID 为“{appId}”的应用程序。 针对此用户和资源发送交互式授权请求。 首先获取用户同意。 如果未使用 .NET Core(它没有任何 Web UI),请调用 AcquireTokeninteractive(仅一次)。 如果使用 .NET Core 或者不希望执行 AcquireTokenInteractive,则用户可以导航到某个 URL 来提供许可:https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id={clientId}&response_type=code&scope=user.read。 要调用 AcquireTokenInteractive,请使用 app.AcquireTokenInteractive(scopes).WithAccount(account).WithClaims(ex.Claims).ExecuteAsync();
MsalUiRequiredException AADSTS50079:用户必须使用多重身份验证 (MFA) 无缓解措施。 如果已为租户配置 MFA 并且 Azure Active Directory (AAD) 决定对其进行强制实施,则回退到 AcquireTokenInteractive 等交互式流。
MsalServiceException AADSTS90010:/common 或 /consumers 终结点不支持此授权类型 。 请使用 /organizations 或特定于租户的终结点。 使用了 /common 根据 Azure AD 发出的消息中所述,颁发机构需要使用一个租户或 /organizations
MsalServiceException AADSTS70002:请求正文必须包含以下参数:client_secret or client_assertion 如果应用程序未注册为 Azure AD 中的公共客户端应用程序,则可能引发此异常。 在 Azure 门户中编辑应用程序的清单,并将 allowPublicClient 设置为 true
MsalClientException unknown_user Message:无法识别已登录的用户 库无法查询当前的 Windows 已登录用户,或者此用户未加入 AD 或 Azure AD(已加入工作区的用户不受支持)。 缓解措施 1:在 UWP 中,检查应用程序是否具有以下功能:企业身份验证、专用网络(客户端和服务器)、用户帐户信息。 缓解措施 2:实现自己的逻辑以提取用户名(例如 john@contoso.com),并使用 AcquireTokenByIntegratedWindowsAuth 表单来提取用户名。
MsalClientException integrated_windows_auth_not_supported_managed_user 此方法依赖于 Active Directory (AD) 公开的协议。 如果在 Azure AD 中创建了一个用户但该用户不受 AD 的支持(“托管”用户),则此方法将会失败。 在 AD 中创建的受 Azure AD 支持的用户(“联合”用户)可以受益于这种非交互式身份验证方法。 缓解:使用交互式身份验证。

MsalUiRequiredException

调用 AcquireTokenSilent() 时,从 MSAL.NET 返回的其中一个常见状态代码是 MsalError.InvalidGrantError。 此状态代码表示应用程序应再次调用身份验证库,但要在交互模式下调用(用于公共客户端应用程序的 AcquireTokenInteractive 或 AcquireTokenByDeviceCodeFlow 会在 Web 应用中执行质询)。 这是因为在颁发身份验证令牌之前,需要进行其他用户交互。

大多数情况下,AcquireTokenSilent 失败的原因是,令牌缓存没有与请求匹配的令牌。 访问令牌将在 1 小时后过期,AcquireTokenSilent 将尝试基于刷新令牌获取新的令牌(在 OAuth2 术语中,这是刷新令牌流)。 此流也可能因各种原因而失败,例如,如果租户管理员配置了更严格的登录策略。

交互的目标在于让用户执行一项操作。 在这些条件中,有些对于用户来说很容易解决(例如,通过单击接受使用条款),而有些则无法通过当前配置进行解决(例如,有问题的计算机需要连接到特定的公司网络)。 有些条件可帮助用户设置多重身份验证,或者在其设备上安装 Microsoft Authenticator。

MsalUiRequiredException 分类枚举

MSAL 公开一个 Classification 字段,你可以读取该字段,以便提供更好的用户体验。 例如,用于告知用户其密码已过期,或者他们需要许可使用某些资源。 支持的值是 UiRequiredExceptionClassification 枚举的一部分:

分类 含义 建议的处理方式
BasicAction 在交互式身份验证流中,条件可以通过用户交互来解决。 调用 AcquireTokenInteractively()。
AdditionalAction 在交互式身份验证流之外,条件可以通过与系统进行其他补救交互来解决。 调用 AcquireTokenInteractively() 以显示一条说明补救操作的消息。 如果用户不太可能完成补救操作,则调用应用程序可能会选择隐藏需要 additional_action 的流。
MessageOnly 条件目前无法解决。 启动交互式身份验证流将显示一条说明条件的消息。 调用 AcquireTokenInteractively() 以显示一条说明条件的消息。 用户读取该消息并关闭窗口后,AcquireTokenInteractively() 将返回 UserCanceled 错误。 如果用户不太可能从该消息中获益,则调用应用程序可能会选择隐藏导致 message_only 的流。
ConsentRequired 用户许可缺失或已撤销。 调用 AcquireTokenInteractively(),以取得用户许可。
UserPasswordExpired 用户的密码已过期。 调用 AcquireTokenInteractively(),以便用户可以重置其密码。
PromptNeverFailed 通过参数 prompt=never 调用交互式身份验证,强制 MSAL 依赖浏览器 cookie,而不是显示浏览器。 此操作已失败。 在没有 Prompt.None 的情况下,调用 AcquireTokenInteractively()
AcquireTokenSilentFailed MSAL SDK 没有足够的信息,无法从缓存中获取令牌。 这可能是因为缓存中没有令牌,或找不到帐户。 此错误消息包含更多详细信息。 调用 AcquireTokenInteractively()。
未提供更多详细信息。 在交互式身份验证流中,条件可以通过用户交互来解决。 调用 AcquireTokenInteractively()。

.NET 代码示例

AuthenticationResult res;
try
{
 res = await application.AcquireTokenSilent(scopes, account)
        .ExecuteAsync();
}
catch (MsalUiRequiredException ex) when (ex.ErrorCode == MsalError.InvalidGrantError)
{
 switch (ex.Classification)
 {
  case UiRequiredExceptionClassification.None:
   break;
  case UiRequiredExceptionClassification.MessageOnly:
  // You might want to call AcquireTokenInteractive(). Azure AD will show a message
  // that explains the condition. AcquireTokenInteractively() will return UserCanceled error
  // after the user reads the message and closes the window. The calling application may choose
  // to hide features or data that result in message_only if the user is unlikely to benefit 
  // from the message
  try
  {
      res = await application.AcquireTokenInteractive(scopes).ExecuteAsync();
  }
  catch (MsalClientException ex2) when (ex2.ErrorCode == MsalError.AuthenticationCanceledError)
  {
   // Do nothing. The user has seen the message
  }
  break;

  case UiRequiredExceptionClassification.BasicAction:
  // Call AcquireTokenInteractive() so that the user can, for instance accept terms
  // and conditions

  case UiRequiredExceptionClassification.AdditionalAction:
  // You might want to call AcquireTokenInteractive() to show a message that explains the remedial action. 
  // The calling application may choose to hide flows that require additional_action if the user 
  // is unlikely to complete the remedial action (even if this means a degraded experience)

  case UiRequiredExceptionClassification.ConsentRequired:
  // Call AcquireTokenInteractive() for user to give consent.
  
  case UiRequiredExceptionClassification.UserPasswordExpired:
  // Call AcquireTokenInteractive() so that user can reset their password
  
  case UiRequiredExceptionClassification.PromptNeverFailed:
  // You used WithPrompt(Prompt.Never) and this failed
  
  case UiRequiredExceptionClassification.AcquireTokenSilentFailed:
  default:
  // May be resolved by user interaction during the interactive authentication flow.
  res = await application.AcquireTokenInteractive(scopes)
                         .ExecuteAsync(); break;
 }
}

条件访问和声明质询

以静默方式获取令牌时,如果你尝试访问的 API 需要条件访问声明质询(例如 MFA 策略),则应用程序可能会收到错误。

处理此错误的模式是使用 MSAL 以交互方式获取令牌。 这会提示用户,并使他们能够满足所需的条件访问策略。

在某些情况下调用需要条件访问的 API 时,API 返回的错误中可能会包含声明质询。 例如,如果条件访问策略要求使用托管设备 (Intune),则错误将类似于 AADSTS53000:需要管理你的设备才能访问此资源。 在这种情况下,可以在令牌获取调用中传递声明,使系统提示用户,以满足相应的策略。

从 MSAL.NET 调用需要条件访问的 API 时,应用程序需要处理声明质询异常。 此错误将显示为 MsalServiceException,其中的 Claims 属性不为空。

要处理声明质询,需要使用 PublicClientApplicationBuilder 类的 .WithClaim() 方法。

出现错误和异常后重试

调用 MSAL 时,应实现自己的重试策略。 MSAL 对 Azure AD 服务进行 HTTP 调用,有时会出现失败。 例如网络崩溃或服务器重载。

HTTP 429

如果服务令牌服务器 (STS) 因请求过多而重载,则将返回 HTTP 错误 429,并在 Retry-After 响应字段中提示还要多久才能重试。

HTTP 错误代码 500-600

对于 HTTP 错误代码为 500-600 的错误,MSAL.NET 实现一个简单的重试一次机制。

MsalServiceExceptionSystem.Net.Http.Headers.HttpResponseHeaders 作为 namedHeaders 属性展现。 可以利用错误代码中的附加信息来提高应用程序的可靠性。 对于前面所述的情况,可以使用 RetryConditionHeaderValue 类型的 RetryAfterproperty 并计算重试时间。

下面是使用客户端凭据流的守护程序示例。 可以将此流修改为使用任何用于获取令牌的方法。


bool retry = false;
do
{
    TimeSpan? delay;
    try
    {
         result = await publicClientApplication.AcquireTokenForClient(scopes, account).ExecuteAsync();
    }
    catch (MsalServiceException serviceException)
    {
         if (serviceException.ErrorCode == "temporarily_unavailable")
         {
             RetryConditionHeaderValue retryAfter = serviceException.Headers.RetryAfter;
             if (retryAfter.Delta.HasValue)
             {
                 delay = retryAfter.Delta;
             }
             else if (retryAfter.Date.HasValue)
             {
                 delay = retryAfter.Date.Value.Offset;
             }
         }
    }
    // . . .
    if (delay.HasValue)
    {
        Thread.Sleep((int)delay.Value.TotalMilliseconds); // sleep or other
        retry = true;
    }
} while (retry);

后续步骤

请考虑启用 MSAL.NET 中的日志,以帮助诊断并调试问题。