c# - 检测到多个匹配标记?

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

我有一个在 Azure 上注册的 ASP.NET 应用程序。它使用 Azure Active Directory 对应用程序的用户进行身份验证。 有一位用户由于某种原因必须更改姓氏(John Smith -> John Dough),他的帐户也连接到 E1 许可证,因此他的电子邮件地址已更改( [email protected] -> [email protected] )。

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

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 

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

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();
      }
}

请注意,我正在调用函数“GetUserData()”,该函数实际上会带来用户所属的所有组。该函数的代码如下:

  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;
        }

“GetUserData()”函数正在调用另一个名为“GetTokenForApplication()”的函数,该函数负责从 Azure 获取 token 。上一个函数的源码如下:

  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);
        }

Clear函数只是清理数据库

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

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

最佳答案

您有两个选择:

选项 1:使用 UserIdentifier 对象获取静默 token 时使用 upn:

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

Note: More info about UserIdentifierType here.

选项 2:清理缓存

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

关于c# - 检测到多个匹配标记?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46614130/

相关文章:

c# - 通过 Interop/pinvoke 传递 C# 回调函数

azure - Azure 搜索服务中 Asterisk(*) 的行为

azure - 逻辑应用程序中的 Webhook 与 Http 请求

azure - Windows Nano Server 上的 Docker : There is not enough space on the disk

c# - 日期时间.TryParse -> "Ghost ticks"

c# - X509Certificate2.Verify() 方法始终为有效证书返回 false

c# - 从通用基类获取实现的类型

asp.net - 将 unicode 数字从英语转换为梵文

javascript - 返回 true 时调用另一个 Javascript 函数

asp.net - ASP.NET MVC 中的 URL 的 Url.Content ("~/...") 和 "~/"之间有区别吗?