Azure - AD - AcquireTokenSilent 给出错误 failed_to_acquire_token_silently

Azure - AD - AcquireTokenSilent giving error failed_to_acquire_token_silently

我们正在使用 Azure AD 进行身份验证并每 30 分钟获取一次刷新的访问令牌。我们调用下面的方法获取安全令牌并将其添加到请求 header.

var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
var authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectId));
var credential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientId"],
ConfigurationManager.AppSettings["ida:ClientSecret"]);

    try
    {
    var authenticationResult = authContext.AcquireTokenSilent(ConfigurationManager.AppSettings["WebAPIBaseAddress"], credential, new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
    //set cookie for azure oauth refresh token - on successful login
    var httpCookie = HttpContext.Current.Response.Cookies["RefreshToken"];
    if (httpCookie != null)
        httpCookie.Value = authenticationResult.RefreshToken;

    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
    }
    catch
    {
    //Get access token using Refresh Token 
    var authenticationResult = authContext.AcquireTokenByRefreshToken(httpCookie.Value, credential, ConfigurationManager.AppSettings["WebAPIBaseAddress"]);
    }

在上面的方法中,我们使用了 AcquireTokenSilent 方法,它为我们提供了访问令牌。由于访问令牌仅持续一段时间。 到期后,我们调用 AcquireTokenByRefreshToken 获取刷新令牌。

上面的代码运行良好,但是我们随机出现以下异常:

Microsoft.IdentityModel.Clients.ActiveDirectory.AdalSilentTokenAcquisitionException: Failed to acquire token silently. Call method AcquireToken 
   at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenSilentHandler.SendTokenRequestAsync() 
   at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenHandlerBase.<RunAsync>d__0.MoveNext()
ErrorCode: failed_to_acquire_token_silently

这种不一致行为的原因可能是什么?相同的代码适用于少数环境 (Stage/Dev),但它在生产环境中随机抛出错误。

求推荐。

首先,在使用 AcquireTokenSilent 之前,您必须调用 AcquireTokenByAuthorizationCodeAsync

var context = new AuthenticationContext(authorityUri);
var credential = new ClientCredential(clientId, clientSecretKey);

await context.AcquireTokenByAuthorizationCodeAsync(authorizationCode, new Uri(redirectUri), credential);

AcquireTokenByAuthorizationCodeAsync 将访问令牌和刷新令牌存储在 TokenCache.DefaultShared 中(对于从身份验证过程接收到的用户 uniqueId)。

假设您这样做,访问令牌和刷新令牌确实会过期。如果发生这种情况,您必须捕获 AdalSilentTokenAcquisitionException 异常:

try
{
    // currentUser = new UserIdentifier() for: ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
    AuthenticationResult authResult = await context.AcquireTokenSilentAsync(resourceUri, credential, currentUser);

    return authResult.AccessToken;
}
catch (AdalSilentTokenAcquisitionException)
{
    return null;
}

在每次请求资源之前调用此方法。它花费不多,理想情况下没有花费,或者 oauth API 使用 refreshToken 命中。

但是当 AdalSilentTokenAcquisitionException 被抛出时(或者是第一次调用)。您必须调用从 oauth API 执行完全访问代码检索的过程。什么程序?这取决于您使用的身份验证过程的类型。

使用完整的 owin 身份验证可以是:

  • 使用 {"response_type", "code" }
  • 重定向到权限 uri
  • 或调用 HttpContext.GetOwinContext().Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType);(控制器操作中的 return 为空,因为 Challenge() 方法会更改 HTTP 响应以强制重定向到身份验证服务器)。结束当前请求的处理(returning null)。 Auth 服务器将使用新的授权代码调用您的授权方法(来自 UseOpenIdConnectAuthentication 的通知的 AuthorizationCodeReceived 事件)。稍后,重定向回需要令牌的原始页面。

因此,您可能会收到 AdalSilentTokenAcquisitionException,因为缓存已过期且 refreshToken 已过期。您必须重新进行身份验证(它是透明的,不需要登录页面)。

我们能够解决这个问题。这似乎是代码本身的一个小错误。 当 AccessToken 过期时,它会抛出一个异常,并尝试在 catch 块中使用 AcquireTokenByRefreshToken 获取一个新的异常。这里我们没有在 Cookie 中设置新收到的刷新令牌。 我们还需要在 catch 块中添加以下语句,以便它获得刷新令牌,然后可以将其传回以生成新的访问令牌。

httpCookie.Value = authenticationResult.RefreshToken;