我有一个ASP MVC 5网站,它使用新的身份框架,并使用实体框架进行第三方身份验证。我有一个登录用户,他们有3个声明(标准名称名称标识符和身份验证提供程序,似乎由默认的ClaimsIdentity Factory提供)。
当用户执行一些操作时,我会使用以下内容为他们添加一个声明:
userManager.AddClaim(userId, new Claim(claimType,claimValue));
如果我查看AspNetUserClaims表中的数据库,那么我会看到一行是我的新声明。太棒了
然而,当我的用户访问其他页面时,我会检查这个新的索赔,但我从来没有找到它
我最初认为的问题是,登录的用户从未更新过他们的cookie以包含新声明,所以我注销了他们,以为当他们重新登录到框架中时,会读取声明并为用户创建新声明,然后他们就会在cookie中。但我一定错过了什么,因为即使我的用户再次登录,他们也没有索赔。
事实上,如果我在ExternalLoginCallback:中调用此行时记录DB活动
// Sign in the user with this external login provider if the user already has a login
ApplicationUser user = await UserManager.FindAsync(loginInfo.Login);
我得到的是:
iisexpress.exe' (CLR v4.0.30319: /LM/W3SVC/2/ROOT-2-130416316906840516): Loaded
'EntityFrameworkDynamicProxies-Microsoft.AspNet.Identity.EntityFramework'.
SELECT TOP (1)
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name]
FROM [dbo].[AspNetRoles] AS [Extent1]
WHERE (((UPPER([Extent1].[Name])) = (UPPER(@p__linq__0))) AND ( NOT ((UPPER([Extent1].[Name]) IS NULL) OR (UPPER(@p__linq__0) IS NULL)))) OR ((UPPER([Extent1].[Name]) IS NULL) AND (UPPER(@p__linq__0) IS NULL))
-- p__linq__0: 'TeamManagement' (Type = String, Size = -1)
-- Executing at 10/04/2014 20:28:29 +01:00
-- Completed in 2 ms with result: GlimpseDbDataReader
'iisexpress.exe' (CLR v4.0.30319: /LM/W3SVC/2/ROOT-2-130416316906840516): Loaded 'EntityFrameworkDynamicProxies-Haccapp.Model'.
SELECT
[Extent1].[Discriminator] AS [Discriminator],
[Extent1].[Id] AS [Id],
[Extent1].[UserName] AS [UserName],
[Extent1].[PasswordHash] AS [PasswordHash],
[Extent1].[SecurityStamp] AS [SecurityStamp],
[Extent1].[PreferredEmailAddress] AS [PreferredEmailAddress]
FROM [dbo].[AspNetUsers] AS [Extent1]
WHERE ([Extent1].[Discriminator] IN (N'ApplicationUser',N'IdentityUser')) AND ([Extent1].[Id] = @p0)
-- p0: 'SiteOwner' (Type = String, Size = -1)
-- Executing asynchronously at 10/04/2014 20:28:29 +01:00
GlimpseDbCommand.TimerStrategy is null
-- Completed in 6 ms with result: GlimpseDbDataReader
SELECT
[Limit1].[Discriminator] AS [Discriminator],
[Limit1].[Id] AS [Id],
[Limit1].[UserName] AS [UserName],
[Limit1].[PasswordHash] AS [PasswordHash],
[Limit1].[SecurityStamp] AS [SecurityStamp],
[Limit1].[PreferredEmailAddress] AS [PreferredEmailAddress]
FROM ( SELECT TOP (1)
[Extent2].[Id] AS [Id],
[Extent2].[UserName] AS [UserName],
[Extent2].[PasswordHash] AS [PasswordHash],
[Extent2].[SecurityStamp] AS [SecurityStamp],
[Extent2].[PreferredEmailAddress] AS [PreferredEmailAddress],
[Extent2].[Discriminator] AS [Discriminator]
FROM [dbo].[AspNetUserLogins] AS [Extent1]
LEFT OUTER JOIN [dbo].[AspNetUsers] AS [Extent2] ON ([Extent2].[Discriminator] IN (N'ApplicationUser',N'IdentityUser')) AND ([Extent1].[UserId] = [Extent2].[Id])
WHERE ([Extent1].[LoginProvider] = @p__linq__0) AND (@p__linq__0 IS NOT NULL) AND ([Extent1].[ProviderKey] = @p__linq__1) AND (@p__linq__1 IS NOT NULL)
) AS [Limit1]
-- p__linq__0: 'Google' (Type = String, Size = -1)
-- p__linq__1: 'https://www.google.com/accounts/o8/id?id=some_account_id' (Type = String, Size = -1)
-- Executing asynchronously at 10/04/2014 20:28:29 +01:00
它不包含任何试图在数据库中查询索赔的内容,事实上,返回的我的用户有0个索赔。
然后代码继续执行以下操作:
ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, defaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties {IsPersistent = isPersistent}, identity);
然后该身份具有3个权利要求。
当我加载用户时,我必须做些什么才能从数据库加载声明?一旦我做到了这一点,我需要做什么来确保我的用户在使用网站期间产生的任何新索赔都在用户cookie中更新?
我觉得我一定错过了一些基本的东西。
编辑
因此,我在这方面做了更多的工作,并最终实现了我自己的ClaimsIdentityFactory
:
public class FullyLoadingClaimsIdentityFactory : ClaimsIdentityFactory<ApplicationUser>
{
private readonly ApplicationDb db;
public FullyLoadingClaimsIdentityFactory(ApplicationDb db)
{
this.db = db;
}
public override async Task<ClaimsIdentity> CreateAsync(UserManager<ApplicationUser> manager, ApplicationUser user, string authenticationType)
{
var currentClaims = db.UserClaims.Where(x => x.User.Id == user.Id).ToList();
var claimsIdentity = await base.CreateAsync(manager, user, authenticationType);
claimsIdentity.AddClaims(currentClaims.Select(c=>new Claim(c.ClaimType,c.ClaimValue)));
return claimsIdentity;
}
}
现在,它显式地查询数据库以加载用户声明。我必须向我的类添加一个从IdentityDbContext
派生的属性,这样我就可以公开框架IdentityUserClaim对象:
public IDbSet<IdentityUserClaim> UserClaims { get; set; }
现在,当我查看我当前的索赔时,它们确实正确地包含了表中的索赔。好哇!
除非我打电话给
var claimsIdentity = await base.CreateAsync(manager, user, authenticationType);
它还加载了我以前不会加载的声明。
事实上,如果我改成这个:
var claimsIdentity = await base.CreateAsync(manager, user, authenticationType);
Trace.WriteLine(claimsIdentity.Claims.Count()); <-- traces 3
var currentClaims = db.UserClaims.Where(x => x.User.Id == user.Id).ToList();
claimsIdentity = await base.CreateAsync(manager, user, authenticationType);
Trace.WriteLine(claimsIdentity.Claims.Count()); <-- traces 6!
那么当我第一次创建claimsIdentity时,我不会在数据库中加载声明(其中有3个),但当我手动查询数据库后第二次创建标识时,声明就在那里。
为什么会这样?我现在觉得它与EF有关,但不确定为什么。。。
是的,如果升级到v2.0,这是一个应该修复的错误。基本上,您希望确保启用LazyLoading,更多信息请点击此处