Я разрабатываю тестовый проект на основе ASP.NET Identity проекта с открытым исходным кодом
В конкретном случае у меня есть проект ASP.Net Web Api, в котором я расширил CookieAuthenticationProvider
и переопределил метод ValidateIdentity
.
Это класс, который я расширил
public sealed class MeteoraInternalCookieAuthenticationProvider : CookieAuthenticationProvider
{
private readonly TimeSpan _validateInterval;
public MeteoraInternalCookieAuthenticationProvider(TimeSpan validateInterval)
: base()
{
_validateInterval = validateInterval;
}
public override async Task ValidateIdentity(CookieValidateIdentityContext context)
{
var currentUtc = DateTimeOffset.UtcNow;
if (context.Options != null && context.Options.SystemClock != null)
{
currentUtc = context.Options.SystemClock.UtcNow;
}
var issuedUtc = context.Properties.IssuedUtc;
// Only validate if enough time has elapsed
var validate = (issuedUtc == null);
if (issuedUtc != null)
{
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
validate = timeElapsed > _validateInterval;
}
if (validate)
{
var manager = context.OwinContext.Get<UserManager>();
var userId = context.Identity.GetUserId<Guid>();
var app = await this.GetApplicationContextForValidateIdentity(context.OwinContext.Get<Guid>(MeteoraOwinSetsKeys.ApplicationId).ToString());
if (manager != null && userId != null)
{
var user = manager.FindById(userId);
var reject = true;
//Refresh the identity if the stamp matches, otherwise reject
if (user != null && manager.SupportsUserSecurityStamp)
{
var securityStamp = context.Identity.FindFirstValue(MeteoraClaimTypes.SecurityStampClaimType);
if (securityStamp == manager.GetSecurityStamp(userId))
{
reject = false;
// Regenerate fresh claims if possible and resign in
var identity = await this.RegenerateIdentity(manager, user, app);
/*
Other Code
*/
}
}
if (reject)
{
context.RejectIdentity();
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
}
}
}
}
private async Task<ClaimsIdentity> RegenerateIdentity(UserManager usrMgr, IdentityUser usr, IdentityApplication app)
{
ClaimsIdentity identity = await usrMgr.CreateIdentityAsync(usr, app, OAuthDefaults.AuthenticationType /* default is "Bearer" */).WithCurrentCulture();
identity.AddClaim(new IdentityClaim(MeteoraClaimTypes.ApplicationIdType, app.Id.ToString()));
return identity;
}
/*
Other Methods
*/
}
Это приложение размещено в IIS, и когда я делаю запрос, вызывается метод ValidateIdentity
.
Тогда внутри вызывается метод RegenerateIdentity
.
RegenerateIdentity
использует класс UserManager для генерации нового ClaimsIdentity
.
Здесь есть весь код
public class UserManager<TUser, TApplication, ... >
{
public virtual async Task<ClaimsIdentity> CreateIdentityAsync(TUser user, TApplication app, string authenticationType)
{
/* Other Code */
return await ClaimsIdentityFactory.CreateAsync(this, user, app, authenticationType);
}
public virtual async Task<IList<string>> GetRoleNamesAsync(TApplication app, TUser user)
{
/* Other Code */
var userRoleStore = GetUserRoleStore();
return await userRoleStore.GetRoleNamesAsync(app, user).WithCurrentCulture();
}
}
public class ClaimsIdentityFactory<TUser, TApplication, TRole, ... >
{
public virtual async Task<ClaimsIdentity> CreateAsync(UserManager<TUser, TApplication, ...> manager, TUser user, TApplication app, string authenticationType)
{
/* Other Code */
ClaimsIdentity cid = new ClaimsIdentity(authenticationType, UserNameClaimType, RoleClaimType);
cid.AddClaim(new Claim(UserIdClaimType, ConvertIdToString(user.Id), ClaimValueTypes.String));
cid.AddClaim(new Claim(UserNameClaimType, user.UserName, ClaimValueTypes.String));
cid.AddClaim(new Claim(IdentityProviderClaimType, DefaultIdentityProviderClaimValue, ClaimValueTypes.String));
/* Other code */
if (manager.SupportsUserRole)
{
IList<string> roles = await manager.GetRoleNamesAsync(app, user); // *** CALLING THIS METHOD ***
foreach (string roleName in roles)
cid.AddClaim(new Claim(RoleClaimType, roleName, ClaimValueTypes.String));
}
/* Other Code */
return cid;
}
}
public abstract class UserStore <TKey, TUser, TApplication ...>
{
public virtual async Task<IList<string>> GetRoleNamesAsync(TApplication app, TUser user)
{
/* Other Code */
return await this.GetRoleNamesAsync(app.Id, user.Id);
}
public virtual async Task<IList<string>> GetRoleNamesAsync(TKey appId, TKey userId)
{
/* Other Code */
var query = from userAppRole in _userApplicationRoles
where userAppRole.User.Id.Equals(userId) && userAppRole.Application.Id.Equals(appId)
join role in _roleStore.DbEntitySet on userAppRole.Role.Id equals role.Id
select role.InvariantName;
return await query.ToListAsync(); // *** DEADLOCK ????? ***
}
}
Сначала называется метод ClaimsIdentityFactory.CreateAsync
.
Внутри ClaimsIdentityFactory
class CreateAsync
метод называется manager.GetRoleNamesAsync
, который при использовании хранилища на основе EntityFramework вызывает метод userRoleStore.GetRoleNamesAsync
.
В классе UserStore
я, кажется, экспериментирую с какой-то тупиковой ситуацией после вызова return await query.ToListAsync();
, потому что метод никогда не возвращается.
Эта проблема отсутствует в моем проекте UnitTest, но возникает в среде IIS.
Что я могу сделать, чтобы понять, что происходит на самом деле?