我有一个在 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/