代码:TokenNotFound消息:在令牌缓存中找不到用户。也许服务器重启了

时间:2018-05-04 09:39:18

标签: c# .net asp.net-mvc asp.net-core azure-active-directory

我有以下功能从活动目录使用图形api调用用户。 在文本框的每个键盘上都会触发此功能。但我得到以下错误

  

代码:TokenNotFound消息:在令牌缓存中找不到用户。也许吧   服务器重新启动。

var user = await graphClient.Users.Request().GetAsync();

整个功能下面:

     public async Task<string> GetUsersJSONAsync(string textValue)
            {
               // email = email ?? User.Identity.Name ?? User.FindFirst("preferred_username").Value;
                var identifier = User.FindFirst(Startup.ObjectIdentifierType)?.Value;
                var graphClient = _graphSdkHelper.GetAuthenticatedClient(identifier);
                string usersJSON = await GraphService.GetAllUserJson(graphClient, HttpContext, textValue);
                return usersJSON;
            }


    public static async Task<string> GetAllUserJson(GraphServiceClient graphClient, HttpContext httpContext, string textValue)
            {
               // if (email == null) return JsonConvert.SerializeObject(new { Message = "Email address cannot be null." }, Formatting.Indented);

                try
                {
                    // Load user profile.
                    var user = await graphClient.Users.Request().GetAsync();                    
                    return JsonConvert.SerializeObject(user.Where(u => !string.IsNullOrEmpty(u.Surname) && ( u.Surname.ToLower().StartsWith(textValue) || u.Surname.ToUpper().StartsWith(textValue.ToUpper()))), Formatting.Indented);
                }
                catch (ServiceException e)
                {
                    switch (e.Error.Code)
                    {
                        case "Request_ResourceNotFound":
                        case "ResourceNotFound":
                        case "ErrorItemNotFound":
                        //case "itemNotFound":
                        //    return JsonConvert.SerializeObject(new { Message = $"User '{email}' was not found." }, Formatting.Indented);
                        //case "ErrorInvalidUser":
                        //    return JsonConvert.SerializeObject(new { Message = $"The requested user '{email}' is invalid." }, Formatting.Indented);
                        case "AuthenticationFailure":
                            return JsonConvert.SerializeObject(new { e.Error.Message }, Formatting.Indented);
                        case "TokenNotFound":
                            await httpContext.ChallengeAsync();
                            return JsonConvert.SerializeObject(new { e.Error.Message }, Formatting.Indented);
                        default:
                            return JsonConvert.SerializeObject(new { Message = "An unknown error has occured." }, Formatting.Indented);
                    }
                }
            }


 // Gets an access token. First tries to get the access token from the token cache.
        // Using password (secret) to authenticate. Production apps should use a certificate.
        public async Task<string> GetUserAccessTokenAsync(string userId)
        {
            _userTokenCache = new SessionTokenCache(userId, _memoryCache).GetCacheInstance();

            var cca = new ConfidentialClientApplication(
                _appId,
                _redirectUri,
                _credential,
                _userTokenCache,
                null);

            if (!cca.Users.Any()) throw new ServiceException(new Error
            {
                Code = "TokenNotFound",
                Message = "User not found in token cache. Maybe the server was restarted."
            });

            try
            {
                var result = await cca.AcquireTokenSilentAsync(_scopes, cca.Users.First());
                return result.AccessToken;
            }

            // Unable to retrieve the access token silently.
            catch (Exception)
            {
                throw new ServiceException(new Error
                {
                    Code = GraphErrorCode.AuthenticationFailure.ToString(),
                    Message = "Caller needs to authenticate. Unable to retrieve the access token silently."
                });
            }
        }

你能帮忙出错吗?

2 个答案:

答案 0 :(得分:1)

我知道这已经4个月了-这仍然是您的问题吗?

正如前一位受访者所指出的那样,您看到的错误正在代码中的catch块中抛出,旨在处理空用户集合。

以防万一,或者其他人来了-如果您使用this sample(或在任何方面使用ConfidentialClientApplication)并抛出此异常,那是因为_userTokenCache没有用户*。当然,这不是因为您的广告没有用户,否则您将无法进行身份验证。这很可能是因为浏览器中的陈旧cookie被作为访问令牌传递给authProvider。您可以使用Fiddler(或仅检查本地浏览器的cookie)来查找它(应称为AspNetCore.Cookies,但您可能希望清除所有它们)。

如果要在会话中存储令牌缓存(如示例所示),请记住,每次启动和停止应用程序时,工作内存都会被丢弃,因此浏览器提供的令牌将不再与新的令牌匹配一个应用程序将在再次启动时检索(除非再次清除浏览器cookie)。

* {cca.Users已不再被MSAL使用或支持,您必须使用cca.GetAccountsAsync()。如果您有已部署的应用程序使用不推荐的IUser实现运行,则必须更改此设置。否则,在开发中,编译器会抱怨并且不允许您进行编译,因此您已经知道这一点。

答案 1 :(得分:0)

看代码,似乎缺少一些逻辑块。例如,您有方法

public async Task<string> GetUserAccessTokenAsync(string userId)

但是我看不到它在哪里被调用。除此之外,我也看不到用于从Azure AD提取令牌的代码。最后,您提到的错误消息

  

代码:TokenNotFound消息:在令牌缓存中找不到用户。也许服务器已重新启动。

似乎是您抛出的错误

if (!cca.Users.Any()) throw new ServiceException(new Error
{
     Code = "TokenNotFound",
     Message = "User not found in token cache. Maybe the server was restarted."
});

由于代码不完整,我将尝试对可能出问题的地方进行假设。

首先,假设您使用的是MSAL.Net,则缺少获取令牌的步骤。

一般流程为(使用GetTokenByAuthorizationCodeAsync()

  
      
  1. 客户挑战用户
  2.   
  3. 用户被重定向并登录
  4.   
  5. 调用回叫,客户端从登录过程中收到code
  6.   
  7. 将代码传递到GetTokenByAuthorizationCodeAsync()以获得id_token并根据权限获得访问令牌。   GetTokenByAuthorizationCodeAsync()将令牌存储在缓存中   已提供给ConfidentialClientApplication
  8.   
  9. 使用AcquireTokenSilentAsync()
  10. 从缓存中检索令牌   
  11. 如果我们无法使用AcquireTokenSilentAsync()从缓存中检索令牌,我们将通过发送新请求   AcquireTokenAsync()
  12.   

该流程的大部分似乎都已存在于您的代码中,但可能是您缺少实际的令牌获取。由于未检索到令牌,因此没有用户添加到ConfidentialClientApplication,这意味着cca.Users.Any()返回false,从而导致ServiceError

假设整个流程到位,并且您实际上正在获取令牌,我的第二个假设是_memoryCache是不同的。保存令牌的_memoryCache与用于静默获取令牌的_memoryCache不同。

我建议阅读the documentation on token acquisition来确定检索类型最适合您的应用程序。

编辑

实际上,我认为您的代码受此example的启发。 这部分特别有趣

public GraphServiceClient GetAuthenticatedClient(string userId)
    {
        _graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(
        async requestMessage =>
        {
            // Passing tenant ID to the sample auth provider to use as a cache key
            var accessToken = await _authProvider.GetUserAccessTokenAsync(userId);
         ...
        }
        return _graphClient;
    }

似乎正在发生的是,调用var user = await graphClient.Users.Request().GetAsync();会调用提供给GraphServiceClient的委托。这依次调用_authProvider.GetUserAccessTokenAsync(userId);,使我们进入public async Task<string> GetUserAccessTokenAsync(string userId)方法。我们的错误很可能是由于ConfidentialClientApplication.Users集合中没有用户

希望这会有所帮助!