MSAL.NET OBO 刷新令牌问题

问题描述 投票:0回答:2

我正在尝试使用 MSAL.NET 在中间层 API (.NET 5.0) 上实现到图形 API 的 OBO 流。我遇到了两个令人沮丧的问题,而且我找不到任何人有类似的问题,所以我认为我误解了一些东西!

问题 1:每当我调用 MSAL 的 GetAccountAsync 时,当应该加载帐户时,它总是返回 null。

问题 2:每当我调用 MSAL 的 AcquireTokenSilent 时,我总是收到错误“在缓存中找不到刷新令牌”。即使我有一个。

这是我所拥有的:

Web 应用程序经过身份验证后,它会将令牌传递到 API 上的图形身份验证端点:

            var authenticationResult = await ClaimHelper.ClientApplication.AcquireTokenByAuthorizationCode(GraphHelpers.BasicGraphScopes, context.Code).ExecuteAsync();

            var apiUserSession = await CouncilWiseAPIHelper.APIClient.Graph.AuthoriseUserAsync(authenticationResult.AccessToken);

这似乎工作正常,并通过 JWT 传递到 API 身份验证端点。该 API 实现 MSAL 机密客户端应用程序,并使用 SetBeforeAccess/SetAfterAccess 令牌缓存方法将缓存保存到数据库。

         _msalClient = ConfidentialClientApplicationBuilder.Create(_graphConfig.ClientId)
            .WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
            .WithClientSecret(_graphConfig.ClientSecret)
            .Build();

        SetSerialiser(serialiser);
        public void SetSerialiser(MSALTokenCacheSerialiser serialiser)
        {
            _msalClient.UserTokenCache.SetBeforeAccessAsync(serialiser.BeforeAccessCallbackAsync);
            _msalClient.UserTokenCache.SetAfterAccessAsync(serialiser.AfterAccessCallbackAsync);
        }

序列化器方法如下所示:

public async Task BeforeAccessCallbackAsync(TokenCacheNotificationArgs notification)
        {
            GraphUserTokenCache tokenCache = await _graphUserTokenCacheRepository.GetByUserIdentifier(notification.SuggestedCacheKey);
            if (tokenCache == null)
            {
                tokenCache = await _graphUserTokenCacheRepository.Get(notification.SuggestedCacheKey);
            }

            if (tokenCache != null)
            {
                notification.TokenCache.DeserializeMsalV3(tokenCache.Value);
            }
        }

        public async Task AfterAccessCallbackAsync(TokenCacheNotificationArgs notification)
        {
            if (!notification.HasTokens)
            {
                // Delete from the cache
                await _graphUserTokenCacheRepository.Delete(notification.SuggestedCacheKey);
            }

            if (!notification.HasStateChanged)
            {
                return;
            }
            GraphUserTokenCache tokenCache;
            if (notification.SuggestedCacheKey == notification.Account.HomeAccountId.Identifier)
            {
                tokenCache = await _graphUserTokenCacheRepository.GetByUserIdentifier(notification.SuggestedCacheKey);
            }
            else
            {
                tokenCache = await _graphUserTokenCacheRepository.Get(notification.SuggestedCacheKey);
            }
            if (tokenCache == null)
            {
                var cache = notification.TokenCache.SerializeMsalV3();
                tokenCache = new GraphUserTokenCache
                {
                    Id = notification.SuggestedCacheKey,
                    AccountIdentifier = notification.Account.HomeAccountId.ToString(),
                    Value = cache
                };

                await _graphUserTokenCacheRepository.Add(tokenCache);
            }
            else
            {
                await _graphUserTokenCacheRepository.Update(tokenCache.Id, notification.TokenCache.SerializeMsalV3());
            }
        }

我可以看到令牌 BeforeAccess 和 AfterAccess 方法被调用,并且我可以看到在数据库中创建的缓存(在我尝试追踪此问题时,加密已被删除)。如果我检查正在保存的序列化令牌缓存,它永远不会填充刷新令牌,但如果我使用 fiddler 检查请求,我可以看到确实提供了刷新令牌。 最后,这是用于检索访问令牌的代码,每当发出图形请求时都会调用该代码:

public async Task<AuthenticationResult> GetAccessToken(string accountId, string jwtBearerToken)
        {
            try
            {
                IAccount account = null;
                if (accountId.IsNotNullOrEmpty())
                {
                    account = await _msalClient.GetAccountAsync(accountId);
                }

                var scope = _graphConfig.Scopes.Split(' ');

                if (account == null)
                {
                    var result = await _msalClient.AcquireTokenOnBehalfOf(scope,
                        new UserAssertion(jwtBearerToken))
                        .ExecuteAsync();

                    return result;
                }
                else
                {
                    var result = await _msalClient.AcquireTokenSilent(scope, account)
                    .ExecuteAsync();

                    return result;
                }
            }
            catch (MsalClientException ex)
            {
                ex.CwApiLog();
                return null;
            }
            catch(Exception ex)
            {
                ex.CwApiLog();
                return null;
            }
        }

当使用 jwtBearerToken 调用它时,它将成功调用

AcquireTokenOnBehalfOf()
并且令牌被缓存并返回结果,但是当我回来通过
GetAccountAsync()
检索帐户时,它总是返回 null,即使我可以看到令牌缓存已加载到
BeforeAccessCallbackAsync()

此外,即使我在使用刚刚返回的帐户获取obo令牌后立即调用

AcquireTokenSilent()
,我也会收到一个异常,说缓存中没有刷新令牌。

我完全迷失了我在这里做错的事情,任何帮助将不胜感激。

oauth-2.0 microsoft-graph-api azure-ad-msal
2个回答
3
投票

我最近在运行长时间运行的 OBO 流程时遇到了同样的问题,MSAL 最近为这些用例实现了一个接口

ILongRunningWebApi
,您可以去看看这个 新文档

这是摘录:

一个 OBO 场景是当 Web API 在上运行长时间运行的进程时 代表用户(例如,OneDrive 为以下对象创建相册) 你)。从 MSAL.NET 4.38.0 开始,可以这样实现:

在开始长时间运行的流程之前,请致电:

string sessionKey = // custom key or null
var authResult = await ((ILongRunningWebApi)confidentialClientApp)
         .InitiateLongRunningProcessInWebApi(
              scopes,
              userToken,
              ref sessionKey)
         .ExecuteAsync();

userToken 是用于调用此 Web API 的用户令牌。会话密钥将 用作缓存和检索 OBO 令牌时的密钥。如果设置为 null,MSAL 会将其设置为传入用户的断言哈希 令牌。开发人员也可以将其设置为 标识特定的用户会话,例如可选的 sid 声明 用户令牌(有关详细信息,请参阅提供可选声明 你的应用程序)。如果缓存已包含有效的 OBO 令牌 sessionKey,InitiateLongRunningProcessInWebApi 将返回它。 否则,用户令牌将用于从以下位置获取新的 OBO 令牌: AAD,然后将被缓存并返回。

在长时间运行的过程中,每当需要OBO代币时,调用:

var authResult = await ((ILongRunningWebApi)confidentialClientApp)
         .AcquireTokenInLongRunningProcess(
              scopes,
              sessionKey)
         .ExecuteAsync();

传递与当前用户关联的sessionKey 会话并将用于检索相关的 OBO 令牌。如果 令牌已过期,MSAL 将使用缓存的刷新令牌来获取 来自 AAD 的新 OBO 访问令牌并将其缓存。如果没有找到令牌 对于这个 sessionKey,MSAL 将抛出 MsalClientException。确保 首先调用InitiateLongRunningProcessInWebApi。

希望这有帮助:)


0
投票

@myotherusernameisbetter :我正在寻找类似问题的解决方案,@HappyDump 的建议答案对我有用。我使用了一个可预测的

sessionKey
,每当我想重新获取 OBO 令牌时就可以生成它,并且它可以完美地工作。对于分布式缓存,我使用 Azure Cosmos DB。我没有在缓存条目上设置过期时间,但由于我必须在完成长时间运行的任务后手动从缓存中删除该条目。

如果您仍然坚持这个问题,我也可以提供帮助! :)

© www.soinside.com 2019 - 2024. All rights reserved.