【问题标题】:MVC5 (VS2012) Identity CreateIdentityAsync - Value cannot be nullMVC5(VS2012)身份 CreateIdentityAsync - 值不能为空
【发布时间】:2014-03-21 23:56:48
【问题描述】:

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

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

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

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

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,它现在不再引发错误,但仍然不会让用户登录。据我所知,它返回的身份对象看起来不错。

【问题讨论】:

  • 我使用了内置的用户管理器...尝试使用 resharper 对其进行反编译,但没有任何 readbale 出现在另一端。

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


【解决方案1】:

我过去也遇到过同样的错误,但只是在我使用实体框架迁移工具创建用户时。创建用户并在网站上签名时,我没有出错。

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

SecurityStamp = Guid.NewGuid().ToString()

这个属性集,一切正常。

【讨论】:

  • 我需要在通过 EF 创建用户而不使用管理方法后执行此操作。
  • 对我也很好。
  • 这很棒。我一直在关注 CodePlex 上的 WebApi Basic Authentication 示例,这是缺失的部分。
  • 身份不应该为您填充所需的字段吗?我正在使用 EF,我只是将 Key 重写为 int 类型,我也收到了这个错误。
  • 然而这确实奏效了,我非常感谢你,我已经在这个问题上拖了好几个小时了。
【解决方案2】:

我遇到了类似的问题。解决方案是设置用户实体的 SecurityStamp-Property。

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

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

在创建 ClaimsIdentity 时,我遇到了与 Jon 相同的异常。

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

在为该属性设置值后 - 我使用了一个包含前缀和用户名的字符串 - 对我来说一切正常。

【讨论】:

  • 解决了我的问题。赋予安全印章一个价值神奇地使它起作用。
【解决方案3】:

当密码少于 6 个字符时,我遇到了这个问题。任何比这更大的东西,我都不会收到这个错误。

6 个字符是它的最小默认设置。

当然你可以改变这种行为。

services.Configure<IdentityOptions>(options =>
                {
                    options.Password.RequiredLength = 6;
                    options.Password.RequireUppercase = false;
                    options.Password.RequireLowercase = false;
                    options.Password.RequireDigit = false;
                    options.Password.RequireNonAlphanumeric = false;
                });

【讨论】:

    【解决方案4】:

    我做了和@user3347549一样的事情。

    我花了一段时间才弄清楚错误实际上来自哪里,为此感谢 dotPeek!

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

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

    您应该在安全标记上添加一些随机的内容,例如新的 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());
    

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

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

    【讨论】:

    • 这已经为我解决了
    【解决方案5】:

    默认的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);
    }
    

    【讨论】:

    • 用您的反馈更新帖子,不再出现错误,但不会验证/显示为已登录
    • @Jon 这些方法在UserStore 中吗?那是他们去的地方,而不是UserManager
    • 我的用户存储中没有这些方法,我没有实现 IUserClaimStore,我认为如果没有实现它就不会爆炸
    • 没有看清楚你的帖子,(错过了“或”)。我尝试了覆盖 UserManager
    • @Jon 你可以使用默认的UserManager 并实现你自己的UserStore。无论哪种方式,您都需要自定义 UserStore
    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

      就我而言,情况完全不同。这是 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);
      
         //...
      
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-09-25
        • 2021-06-08
        • 2016-04-07
        • 1970-01-01
        • 1970-01-01
        • 2020-08-03
        • 2020-05-22
        • 2014-04-27
        相关资源
        最近更新 更多