MVC5(VS2012)标识CreateIdentityAsync - 值不能为null

时间:2014-02-20 19:47:20

标签: asp.net asp.net-mvc-5 asp.net-identity identity claims-based-identity

我正在尝试为MVC5网站设置OAuth(在VS2012中)。

我正在使用Fluent NHibernate。我已经设置了自己的Userstore并传入一个存储库对象来访问NHibernate会话对象。我将我的商店传递给默认的aspnet usermanager提供程序。这最终适用于本地注册和登录。我不是要设置连接/注册Facebook。

它获得了一个成功的帐户。在用户表中添加用户,在登录表中添加记录然后爆炸。我没有在用户存储中实现声明,或者在用户对象中放置声明集合。 (不确定这是否真的需要,我正在剥离所有可能出错的东西以找到问题的根源。)

爆炸的行是(在帐户控制器中):

var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);            

在此方法中:

private async Task SignInAsync(IdentityUser user, bool isPersistent)

这是堆栈跟踪的结束

[ArgumentNullException: Value cannot be null.
Parameter name: value]
   System.Security.Claims.Claim..ctor(String type, String value, String valueType, String issuer, String originalIssuer, ClaimsIdentity subject, String propertyKey, String propertyValue) +14108789
   System.Security.Claims.Claim..ctor(String type, String value, String valueType) +62
   Microsoft.AspNet.Identity.<CreateAsync>d__0.MoveNext() +481
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +144
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +84
   System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +49
   Web.Controllers.<SignInAsync>d__42.MoveNext() in d:\Google Drive\Development\GoalManagement\Web\Controllers\AccountController.cs:375
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +144
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +84
   Web.Controllers.<ExternalLoginConfirmation>d__35.MoveNext() in d:\Google Drive\Development\GoalManagement\Web\Controllers\AccountController.cs:311
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +144
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +84

public class IdentityUser : IUser
{
    public IdentityUser()
    {
        Logins = new List<IdentityUserLogin>();
    }

    public string Id { get; set; }
    public string UserName { get; set; }
    public string PasswordHash { get; set; }
    public string SecurityStamp { get; set; }
    public IList<IdentityUserLogin> Logins { get; set; }

}

public class IdentityUserLogin
{
    public string LoginProvider { get; set; }
    public string ProviderKey { get; set; }
}

如果需要,我可以包含我的用户代码:我没有把它放入,因为它是一个大文件,可能会减损这个问题。

我不确定为什么它甚至试图创建声明对象以及为什么它会爆炸。因为我只有VS2012,所以我一直在网上的例子中修补它们。


正如@Shoe所建议我继承自UserManager

public class NHibernateAspnetUserManager<TUser> : UserManager<TUser> where TUser : IdentityUser
{
    public NHibernateAspnetUserManager(IUserStore<TUser> store) : base(store)
    {
    }        

    public override Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)
    {
        ClaimsIdentity identity = new ClaimsIdentity();
        return Task.FromResult(identity);
    }
}

它现在不再抛出错误,但实际上并没有验证我多少次使用Facebook注册/登录。


总结一下。在@ Shoe的信息中,我尝试用UserManager.CreateIdentityAsync覆盖:

public override Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)
    {
        var identity = new ClaimsIdentity();
        identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
        return Task.FromResult(identity);
    }

并尝试使用返回的默认值(空列表)实现IUserClaimStore。

第一个不会通过错误但不会最终通过身份验证。后者仍将通过奇怪的声明“System.Security.Claims.Claim..ctor”错误

修改

找出ctor错误发生的原因。用户对象返回时没有ID,因此默认UserManager感到不安。修复了这个并使用了现在不再抛出错误的默认UserManager,但仍然没有记录用户。它返回的身份对象看起来不错。

7 个答案:

答案 0 :(得分:79)

我在过去遇到了同样的错误,但只有在我使用Entity Framework Migration Tool创建用户时才会出错。在创建用户并使用网站签名时,我没有错误。

我的错误是我没有提供迁移的 SecurityStamp

SecurityStamp = Guid.NewGuid().ToString()

此属性设置,一切正常。

答案 1 :(得分:13)

我有类似的问题。解决方案是设置User-Entity的SecurityStamp-Property。

背景:客户希望在数据库中拥有带密码的管理员/超级用户帐户以及一大堆其他用户 - 他们应该能够在没有密码的情况下登录 - 在XML文件中...

所以我从Entity Framework UserStore继承,覆盖FindByIdAsync和FindByNameAsync,为用户搜索XML文件并返回一个新的User-Entity。 (如果默认实现没有找到用户)

在创建ClaimsIdentity时,我和Jon有相同的例外。

经过一番挖掘后,我发现我新创建的用户实体没有SecurityStamp。而asp.net默认的UserManager需要一个SecurityStamp,并希望在ClaimsIdentity中将其设置为一个Claim。

为该属性设置一个值后 - 我使用了一个包含前缀和用户名的字符串 - 一切都适合我。

答案 2 :(得分:2)

我做了与@ user3347549相同的事情。

我花了一段时间才弄清楚错误实际来自哪里,对于dotPeek赞不绝口!

我正在使用自己的UserManager和UserStore实现,因为我希望Guid类型(MSSQL中的uniqueidentifier)作为键,而不是字符串(尽管它们只是Guids的占位符)

感谢this链接,特别是这个答案,我已将其包含在链接消失的情况下作为参考,由HaoK(@Hao Kung在此处):

  

你应该随机地给安全标记种子,比如新的guid工作。

我实现了自己的ClaimsIdentityFactory(看起来与我在dotPeek中收集的内容完全一样)并且只是在CreateAsync方法中改变了一行

public class ClaimsIdentityFactory<TUser, TKey> : IClaimsIdentityFactory<TUser, TKey>
    where TUser : class, IUser<TKey>
    where TKey : IEquatable<TKey>
{
    /// <summary>
    /// Claim type used for role claims
    /// </summary>
    public string RoleClaimType { get; set; }

    /// <summary>
    /// Claim type used for the user name
    /// </summary>
    public string UserNameClaimType { get; set; }

    /// <summary>
    /// Claim type used for the user id
    /// </summary>
    public string UserIdClaimType { get; set; }

    /// <summary>
    /// Claim type used for the user security stamp
    /// </summary>
    public string SecurityStampClaimType { get; set; }

    /// <summary>
    /// Constructor
    /// </summary>
    public ClaimsIdentityFactory()
    {
        RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
        UserIdClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
        UserNameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
        SecurityStampClaimType = "AspNet.Identity.SecurityStamp";
    }

    /// <summary>
    /// Create a ClaimsIdentity from a user
    /// </summary>
    /// <param name="manager">
    /// </param>
    /// <param name="user">
    /// </param>
    /// <param name="authenticationType">
    /// </param>
    /// <returns>
    /// </returns>
    public virtual async Task<ClaimsIdentity> CreateAsync(UserManager<TUser, TKey> manager, TUser user, string authenticationType)
    {
        if (manager == null)
            throw new ArgumentNullException("manager");
        if (user == null)
            throw new ArgumentNullException("user");

        var id = new ClaimsIdentity(authenticationType, UserNameClaimType, RoleClaimType);
        id.AddClaim(new Claim(UserIdClaimType, ConvertIdToString(user.Id), "http://www.w3.org/2001/XMLSchema#string"));
        id.AddClaim(new Claim(UserNameClaimType, user.UserName, "http://www.w3.org/2001/XMLSchema#string"));
        id.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"));
        if (manager.SupportsUserSecurityStamp)
        {
            ClaimsIdentity claimsIdentity1 = id;
            string securityStampClaimType = SecurityStampClaimType;
            ClaimsIdentity claimsIdentity2 = claimsIdentity1;
            string str = await manager.GetSecurityStampAsync(user.Id).ConfigureAwait(false);
            Claim claim = new Claim(securityStampClaimType, str ?? Guid.NewGuid().ToString());
            claimsIdentity2.AddClaim(claim);
        }
        if (manager.SupportsUserRole)
        {
            IList<string> roles = await manager.GetRolesAsync(user.Id).ConfigureAwait(false);
            foreach (string str in roles)
                id.AddClaim(new Claim(RoleClaimType, str, "http://www.w3.org/2001/XMLSchema#string"));
        }
        if (manager.SupportsUserClaim)
            id.AddClaims(await manager.GetClaimsAsync(user.Id).ConfigureAwait(false));
        return id;
    }

    /// <summary>
    /// Convert the key to a string, by default just calls .ToString()
    /// </summary>
    /// <param name="key">
    /// </param>
    /// <returns>
    /// </returns>
    protected virtual string ConvertIdToString(TKey key)
    {
        if ((object)key == null)
            throw new ArgumentNullException("key");
        else
            return key.ToString();
    }
}

我改变的行来自

Claim claim = new Claim(securityStampClaimType, str);

Claim claim = new Claim(securityStampClaimType, str ?? Guid.NewGuid().ToString());

我还没弄清楚这意味着什么,但至少它现在有效,我可以继续测试我的应用程序。我假设出现此错误,因为我还没有完全实现Identity堆栈的某些部分。要使用这个新工厂,只需在UserManager构造函数中输入:

ClaimsIdentityFactory = new ClaimsIdentityFactory<TUser, Guid>();

答案 3 :(得分:2)

当密码少于6个字符时,我遇到了这个问题。大于此的值,我不会收到此错误。

答案 4 :(得分:1)

默认UserManager将尝试获取声明并添加/删除声明,即使您尚未实现声明。如果您不需要声明,我找到的解决方案是在UserManager中实施您自己的UserStore或实施“不执行任何操作”方法。

public Task AddClaimAsync(TUser user, Claim claim)
{
    return Task.FromResult<int>(0);
}

public Task<IList<Claim>> GetClaimsAsync(TUser user)
{
    return Task.FromResult<IList<Claim>>(new List<Claim>());
}

public Task RemoveClaimAsync(TUser user, Claim claim)
{
    return Task.FromResult<int>(0);
}

答案 5 :(得分:0)

我必须实现ClaimsIdentityFactory并设置UserManager.ClaimsIdentityFactory属性,即在AccountController类中。

答案 6 :(得分:0)

在我的情况下,这是完全不同的东西。这是Owin启动代码排序的问题

我的错误代码:

public void ConfigureAuth(IAppBuilder app)
{

   //...

   app.CreatePerOwinContext(ApplicationDbContext.Create);
   app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
   app.CreatePerOwinContext<AppSignInManager>(AppSignInManager.Create);
   app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);

   //...

}

事实证明,AppSignInManager尝试初始化AppUserManager始终为null,因为它尚未添加到Owin。

通过简单地将它们交换在一起,一切都像一个魅力

public void ConfigureAuth(IAppBuilder app)
{

   //...

   app.CreatePerOwinContext(ApplicationDbContext.Create);
   app.CreatePerOwinContext<AppSignInManager>(AppSignInManager.Create);
   app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
   app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);

   //...

}
相关问题