【问题标题】:Unable to read Graph API Access token which is stored in ADAL TokenCache from WebAPI无法从 WebAPI 读取存储在 ADAL TokenCache 中的 Graph API 访问令牌
【发布时间】:2017-01-23 10:08:15
【问题描述】:

StackOverflow 中贴出的问题如下

我们有一个多租户 Web 应用程序 (ASP.NET MVC 5.2.2),它受 Azure AD 保护以进行用户身份验证,并且 Web 应用程序调用后端 Rest API (ASP.NET Web API 5.2.3),它也是受保护的 VIA OAuth 2.0 不记名令牌。我们在使用 Open-ID Connect OWIN 模块的 Web 应用程序中使用 Open-ID Connect 协议。

我们需要使用 Graph API 1.5 版将租户的 Azure AD 目录用户和组放入应用程序商店。我们使用 Microsoft ADAL 2.0 获取访问令牌和刷新令牌并将它们存储在 ADAL 令牌缓存中扩展到 Redis 缓存。

该设计是这样一种方式,即 Web App 将用户上下文传递给 Web API,其中包括 SignInUserId、ObjectId、TenantId 和 Web Api 使用此上下文以及 Web App 身份来读取已经存储在 TokenCache 中的 Access Token (如果过期则刷新访问令牌)并使用此令牌获取租户 AD 数据。

           // get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
           ClientCredential clientcred = new ClientCredential(clientId, appKey);
          // initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's EF DB
           AuthenticationContext authContext = new AuthenticationContext(string.Format("https://login.microsoftonline.com/{0}", tenantID), new CustomTokenCache(signedInUserID));
           AuthenticationResult result = await authContext.AcquireTokenSilentAsync(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
           return result.AccessToken;

读取令牌时,即使立即从缓存中访问令牌,也会引发 FailedToRefreshAccessToken 异常。

任何帮助将不胜感激。

自定义令牌缓存代码

public class PerUserCache
    {
        public string userUniqueId { get; set; }
        public byte[] cacheBits { get; set; }
        public DateTime LastWrite { get; set; }

    }

    public class CustomTokenCache : TokenCache
    {

        string userID;
        PerUserCache Cache;
        ICache database = CacheFactory.GetCacheInstance();

     /// <summary>
     /// 
     /// </summary>
     /// <param name="userID"></param>
        public CustomTokenCache(string userID)
        {
            // associate the cache to the web api
            this.userID = userID;

            this.AfterAccess = AfterAccessNotification;
            this.BeforeAccess = BeforeAccessNotification;
            this.BeforeWrite = BeforeWriteNotification;

            // look up the entry in the DB

                Cache = database.Get<PerUserCache>(this.userID);

            // place the entry in memory
            this.Deserialize((Cache == null) ? null : Cache.cacheBits);
        }

        // clean up the DB
        public override void Clear()
        {
            base.Clear();
        }
    enter code here
        // Notification raised before ADAL accesses the cache.
        // This is your chance to update the in-memory copy from the DB, if the in-memory version is stale
        void BeforeAccessNotification(TokenCacheNotificationArgs args)
        {
            if (Cache == null)
            {
                // first time access
                    Cache = database.Get<PerUserCache>(userID);
            }`enter code here`
            else
            {   // retrieve last write from the DB
                var status = database.Get<PerUserCache>(userID).LastWrite;
                // if the in-memory copy is older than the persistent copy
                if (status > Cache.LastWrite)
                //// read from from storage, update in-memory copy
                {
                    Cache = database.Get<PerUserCache>(userID);
                }
            }
            this.Deserialize((Cache == null) ? null : Cache.cacheBits);
        }
        // Notification raised after ADAL accessed the cache.
        // If the HasStateChanged flag is set, ADAL changed the content of the cache
        void AfterAccessNotification(TokenCacheNotificationArgs args)
        {
            // if state changed
            if (this.HasStateChanged)
            {
                Cache = new PerUserCache
                {
                    userUniqueId = userID,
                    cacheBits = this.Serialize(),
                    LastWrite = DateTime.Now
                };
                //// update the DB and the lastwrite                
                database.Set<PerUserCache>(userID, Cache,null);
                this.HasStateChanged = false;
            }
        }
        void BeforeWriteNotification(TokenCacheNotificationArgs args)
        {
            // if you want to ensure that no concurrent write take place, use this notification to place a lock on the entry
        }
    }

}

【问题讨论】:

  • 你能得到关于FailedToRefreshAccessToken异常的详细异常信息吗?并且分享CustomTokenCache类的代码也很有帮助。
  • 嗨@Fei Xue,编辑了帖子
  • @madhu 你是怎么解决这个问题的?

标签: adal azure-active-directory azure-ad-graph-api


【解决方案1】:

CustomTokenCache的代码似乎没有问题。通常,此问题是由于无法找到特定用户的缓存造成的。

您可以通过在BeforeAccessNotification的方法上设置断点来验证这个问题,并确保Cache对象在反序列化时不为空。

你也可以直接根据userObjectID查看Redis中的缓存。

此外,由于您指出您的网络应用程序将令牌存储到上图中的缓存存储中。您介意在您的网络应用中分享有关如何获取令牌的代码吗?

【讨论】:

  • 嗨飞雪,我们已经检查了 CustomTokenCache 类,我们能够获取脱轨缓存对象。当我们尝试从 Web 应用程序访问时,当我们尝试从 Web APi 访问时,我们能够获取令牌抛出异常
  • 请确保您是基于相同的用户对象ID从缓存中获取令牌。而且我还建议调试web API,确保Cache对象在web API项目中反序列化时不为空。
猜你喜欢
  • 1970-01-01
  • 2018-02-05
  • 2013-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-04
  • 1970-01-01
  • 2018-10-10
相关资源
最近更新 更多