检测到多个匹配令牌?

时间:2017-10-06 21:25:12

标签: c# asp.net azure azure-active-directory access-token

我有一个在Azure上注册的ASP.NET应用程序。它使用Azure Active目录对应用程序的用户进行身份验证。 有一个用户,他的姓氏由于某种原因不得不改变(John Smith - > John Dough),他的帐户也连接到E1许可证,因此他的电子邮件地址已更改(smithj@ddd.com - > smithd @ ddd.com)。

此用户尝试对我的应用程序进行身份验证,但失败并显示以下消息:

multiple_matching_tokens_detected: The cache contains multiple tokens satisfying the requirements. Call AcquireToken again providing more requirements (e.g. UserId)

您应该知道该应用程序本地托管在其中一个Azure虚拟机上,但它已在Azure上注册,因此用户可以使用相同的凭据登录

我的堆栈跟踪如下所示:

[AdalException: multiple_matching_tokens_detected: The cache contains multiple tokens satisfying the requirements. Call AcquireToken again providing more requirements (e.g. UserId)]
   Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache.LoadSingleItemFromCache(String authority, String resource, String clientId, TokenSubjectType subjectType, String uniqueId, String displayableId, CallState callState) +724
   Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache.LoadFromCache(String authority, String resource, String clientId, TokenSubjectType subjectType, String uniqueId, String displayableId, CallState callState) +110
   Microsoft.IdentityModel.Clients.ActiveDirectory.<RunAsync>d__0.MoveNext() +1796
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +31
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.IdentityModel.Clients.ActiveDirectory.<AcquireTokenSilentCommonAsync>d__10.MoveNext() +317
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +31
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.IdentityModel.Clients.ActiveDirectory.<AcquireTokenSilentAsync>d__5c.MoveNext() +268
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +31
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   MRTWebApplication.<GetTokenForApplication>d__6.MoveNext() +559
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +31
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   MRTWebApplication.<<GetUserData>b__5_0>d.MoveNext() +194
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +31
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.Azure.ActiveDirectory.GraphClient.Extensions.<SetToken>d__1.MoveNext() +207
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +31
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.Azure.ActiveDirectory.GraphClient.Extensions.<ExecuteAsync>d__4d`2.MoveNext() +993
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +31
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.Azure.ActiveDirectory.GraphClient.Extensions.<<ExecuteAsync>b__0>d__2.MoveNext() +263

[AggregateException: One or more errors occurred.]
   System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) +4719080
   MRTWebApplication._Default.GetUserData() +773
   MRTWebApplication._Default.Page_Load(Object sender, EventArgs e) +43
   System.Web.UI.Control.OnLoad(EventArgs e) +103
   System.Web.UI.Control.LoadRecursive() +68
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3811 

我正在研究如何清除此缓存,以便此用户可以再次重新进行身份验证并获取令牌。我无法找到方法!     我还研究了更改已注册应用程序的权限并再次向用户重新授予它们,但这不起作用。     我的代码如下:

private static string clientId = ConfigurationManager.AppSettings["ida:ClientID"];
private static string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string graphResourceId = "https://graph.windows.net";

protected void Page_Load(object sender, EventArgs e)
{
      if (Request.IsAuthenticated)
      {
          IList<string> groups = GetUserData();
      }
}

请注意,我正在调用函数&#39; GetUserData()&#39;这实际上带来了用户所属的所有组。该函数的代码如下:

  public IList<string> GetUserData()
        {
            string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
            string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;

            Uri servicePointUri = new Uri(graphResourceId);
            Uri serviceRoot = new Uri(servicePointUri, tenantID);
            ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(serviceRoot,
                    async () => await GetTokenForApplication());

            IList<string> groupMembership = new List<string>();
            // use the token for querying the graph to get the user details
            IUser user = activeDirectoryClient.Users
                .Where(u => u.ObjectId.Equals(userObjectID))
                .ExecuteAsync().Result.CurrentPage.ToList().First();
            var userFetcher = (IUserFetcher)user;
            requestor = user.DisplayName;
            IPagedCollection<IDirectoryObject> pagedCollection = userFetcher.MemberOf.ExecuteAsync().Result;
            do
            {
                List<IDirectoryObject> directoryObjects = pagedCollection.CurrentPage.ToList();
                foreach (IDirectoryObject directoryObject in directoryObjects)
                {
                    if (directoryObject is Group)
                    {
                        var group = directoryObject as Group;
                        groupMembership.Add(group.DisplayName);
                    }
                }
                pagedCollection = pagedCollection.GetNextPageAsync().Result;
            } while (pagedCollection != null);

            return groupMembership;
        }

&#39; GetUserData()&#39;函数正在调用另一个名为&#39; GetTokenForApplication()&#39;负责获取令牌表单Azure。上一个函数的源代码如下:

  protected async Task<string> GetTokenForApplication()
    {
        string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
        string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
        string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;

        // 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 authenticationContext = new AuthenticationContext(aadInstance + tenantID, new ADALTokenCache(signedInUserID));
        AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(graphResourceId, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
        return authenticationResult.AccessToken;


    } 

我坚信我可以通过更改调用2个主要函数AfterAccessNotification(),BeforeAccessNotification()函数的MyADALTokenCache函数来避免这种情况发生,如下所示:

  public ADALTokenCache(string signedInUserId)
        {
            // associate the cache to the current user of the web app
            userId = signedInUserId;
            this.AfterAccess = AfterAccessNotification;
            this.BeforeAccess = BeforeAccessNotification;
            this.BeforeWrite = BeforeWriteNotification;
            // look up the entry in the database
            Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
            // place the entry in memory
            //this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits,"ADALCache"));
            this.Deserialize((Cache == null) ? null : Cache.cacheBits);
        }

AfterAccessNotification函数如下所示:

void AfterAccessNotification(TokenCacheNotificationArgs args)
        {
            // if state changed
            if (this.HasStateChanged)
            {
                if(Cache == null)
                {
                    Cache = new UserTokenCache
                    {
                        webUserUniqueId = userId,
                        //cacheBits = MachineKey.Protect(this.Serialize(), "ADALCache"),
                        //LastWrite = DateTime.Now
                    };
                }
                Cache.cacheBits = this.Serialize();
                Cache.LastWrite = DateTime.Now;
                // update the DB and the lastwrite 
                db.Entry(Cache).State = Cache.UserTokenCacheId == 0 ? EntityState.Added : EntityState.Modified;
                db.SaveChanges();
                this.HasStateChanged = false;
            }
        }

BeforeAccessNotification功能如下:

void BeforeAccessNotification(TokenCacheNotificationArgs args)
        {
            if (Cache == null)
            {
                // first time access
                Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
            }
            else
            { 
                // retrieve last write from the DB
                var status = from e in db.UserTokenCacheList
                             where (e.webUserUniqueId == userId)
                select new
                {
                    LastWrite = e.LastWrite
                };

                // if the in-memory copy is older than the persistent copy
                if (status.First().LastWrite > Cache.LastWrite)
                {
                    // read from from storage, update in-memory copy
                    Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
                }
            }
            //this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits, "ADALCache"));
            this.Deserialize((Cache == null) ? null : Cache.cacheBits);
        }

清除功能只是清理数据库

public override void Clear()
        {
            base.Clear();
            foreach (var cacheEntry in db.UserTokenCacheList)
                db.UserTokenCacheList.Remove(cacheEntry);
            db.SaveChanges();
        }

这个问题的理想解决方案是什么? 谢谢!

1 个答案:

答案 0 :(得分:2)

您有两种选择:

选项1 :使用UserIdentifier对象获取令牌无声时,使用 upn

AuthenticationResult authenticationResult = await 
authenticationContext.AcquireTokenSilentAsync(graphResourceId, clientcred, new UserIdentifier(userUpn, UserIdentifierType.RequiredDisplayableId));
  

注意:有关UserIdentifierType here

的更多信息

选项2:清理缓存

另请参阅描述类似问题的this question