【问题标题】:Azure - AD - AcquireTokenSilent giving error failed_to_acquire_token_silentlyAzure - AD - AcquireTokenSilent 给出错误 failed_to_acquire_token_silently
【发布时间】:2023-03-12 18:56:01
【问题描述】:

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

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)上工作,但它在生产环境中随机抛出错误。

请提出建议。

【问题讨论】:

  • 你是如何配置认证的?你看过这篇stackoverflow.com/questions/34888661/… 的帖子了吗?
  • 您使用的 Microsoft.IdentityModel.Clients.ActiveDirectory 是什么版本?
  • @Thomas:谢谢。我们已经在 startup.cs 中以与您共享的链接中提到的相同的方式完成了身份验证。
  • @SushantSonarghare,当您尝试静默获取令牌时,它将从 TokenCache 中获取令牌。因此,如果令牌不再有效,您将收到此错误。您可以做的是捕获 AdalException 并强制用户重新进行身份验证。
  • 旁注,您永远不需要调用 AcquireTokenByRefreshToken。如果您调用 AcquireTokenSilent,ADAL 将自动从缓存中选择最佳刷新令牌 - 它会透明地保存新的刷新令牌。让我强调一点:假设您正在持久化缓存,则不应该有任何情况下您必须直接操作刷新令牌。

标签: c# azure azure-active-directory adal


【解决方案1】:

首先,在使用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;
}

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

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

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

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

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

【讨论】:

  • 感谢您的详细回答,抱歉回复晚了,但我们正在执行您建议的步骤。您能否详细说明答案的完整代码检索部分? var authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectId)); authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, GraphResourceId);
  • @SushantSonarghare - 您使用什么样的身份验证过程? a) 像这里这样的 Owin .net 库:github.com/Azure-Samples/…;或 b) ADAL 库 powerbi.microsoft.com/en-us/documentation/… ?
【解决方案2】:

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

httpCookie.Value = authenticationResult.RefreshToken;

【讨论】:

  • 这不是 OAuth 的使用方式!将任何令牌存储在 cookie 中是非常不安全的(请参阅 security.stackexchange.com/questions/100600/… )。此外,它还会引导您进入奇怪的身份验证流程。请再次阅读此处的答案或我的其他答案 (stackoverflow.com/questions/25267831/…) 以了解如何处理刷新令牌。基本上说它们对你来说应该是透明的(获取静默会自动完成)。
  • 您不能在 catch 方法中执行此操作。这里的问题是 authenticationResult 没有选择数据并在那时引发错误。 authenticationResult.RefreshToken 怎么会有值?
猜你喜欢
  • 2023-02-16
  • 1970-01-01
  • 2018-05-02
  • 2018-12-05
  • 1970-01-01
  • 2020-10-09
  • 2023-04-08
  • 2021-12-23
  • 2018-01-01
相关资源
最近更新 更多