我正在尝试使用 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()
,我也会收到一个异常,说缓存中没有刷新令牌。
我完全迷失了我在这里做错的事情,任何帮助将不胜感激。
我最近在运行长时间运行的 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。
希望这有帮助:)
@myotherusernameisbetter :我正在寻找类似问题的解决方案,@HappyDump 的建议答案对我有用。我使用了一个可预测的
sessionKey
,每当我想重新获取 OBO 令牌时就可以生成它,并且它可以完美地工作。对于分布式缓存,我使用 Azure Cosmos DB。我没有在缓存条目上设置过期时间,但由于我必须在完成长时间运行的任务后手动从缓存中删除该条目。
如果您仍然坚持这个问题,我也可以提供帮助! :)