1

我有一个 ASP MVC 5 站点,它使用新的身份框架和使用实体框架的 3rd 方身份验证,因为它开箱即用。我有一个登录用户,他们有 3 个声明(标准名称 nameidentifier 和身份验证提供程序似乎由默认 ClaimsIdentityFactory 提供)。

当用户执行某些操作时,我会使用以下方法为他们添加声明:

userManager.AddClaim(userId, new Claim(claimType,claimValue));

如果我查看 AspNetUserClaims 表中的数据库,那么我会看到新声明的一行。伟大的。

但是,当我的用户随后访问其他页面时,我会检查这个新声明,但我从未找到它。

我最初的问题是,登录的用户从未更新过他们的 cookie 以包含新的声明,所以我将他们注销,认为当他们重新登录框架时会读取声明并为用户创建新的声明,并且然后他们就会在 cookie 中。但是我必须遗漏一些东西,因为即使我的用户再次登录他们也没有声明。

事实上,如果我在 ExternalLoginCallback 中调用此行时记录数据库活动:

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

现在,当我查看我的 currentClaims 时,它们确实正确包含表中的声明。欢呼!

除非我随后打电话:

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!

然后,当我第一次创建 claimIdentity 时,我没有加载数据库中的声明(其中有 3 个),但是在我手动查询数据库后第二次创建身份时,声明就在那里。

为什么会这样?我现在感觉就像它的 EF 相关但不知道为什么......

4

1 回答 1

1

是的,如果您升级到 v2.0,这是一个应该修复的错误。基本上你要确保启用 LazyLoading,更多信息在这里

于 2014-04-10T21:42:57.417 回答