Подтвердить что ты не робот

MVC5 (VS2012) Identity CreateIdentityAsync - значение не может быть нулевым

Я пытаюсь настроить OAuth для сайта MVC5 (в 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; }
}

Я могу включить свой код userstore, если захочу: я не помещал его в него как большой файл и мог бы отвлечься от проблемы.

Я не уверен, почему он даже пытается создать объект претензии и почему он взрывается. Поскольку у меня только 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 info я попробовал как переопределить 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. Пользовательский объект возвращался без идентификатора, поэтому по умолчанию UserManager расстраивался. Исправлено это и использовалось значение по умолчанию UserManager, которое теперь больше не выдает ошибку, но все равно не регистрирует пользователя. Объект идентификации, который он возвращает, выглядит хорошо из того, что я могу сказать.

4b9b3361

Ответ 1

У меня была такая же ошибка в прошлом, но только тогда, когда я создал пользователя с инструментом миграции объектов Entity Framework. При создании пользователя и подписании его с сайтом у меня не было ошибок.

Моя ошибка заключалась в том, что я не выполнял миграцию SecurityStamp.

SecurityStamp = Guid.NewGuid().ToString()

Это свойство установлено, все сработало.

Ответ 2

У меня была аналогичная проблема. Решение состояло в том, чтобы установить свойство SecurityStamp-User объекта-пользователя.

Предпосылки: клиент хочет иметь учетные записи администратора/суперпользователя с паролями в базе данных и группу дополнительных пользователей, которые могут войти в систему без пароля, в файле XML...

Итак, я наследую Entity Framework UserStore, переопределяю FindByIdAsync и FindByNameAsync, просматриваю файл XML для пользователя и возвращаю новый User-Entity. (если пользователь не был найден по умолчанию)

У меня было такое же Исключение, как и Джон при создании ClaimsIdentity.

После некоторого рытья я обнаружил, что у моих недавно созданных пользовательских объектов не было SecurityStamp. И UserManager по умолчанию asp.net ожидает SecurityStamp и хочет установить его как претензию в ClaimsIdentity.

После установки значения для этого свойства - я использовал строку, содержащую префикс и имя пользователя - все отлично работает для меня.

Ответ 3

Я сделал то же самое, что и @user3347549.

Мне потребовалось некоторое время, чтобы выяснить, откуда на самом деле была ошибка: для этого вам пригодится dotPeek!

Я использую собственную реализацию UserManager и UserStore, потому что мне нужны типы Guid (uniqueidentifier в MSSQL) в качестве ключей, а не строка (хотя они просто заполнители для гидов)

Благодаря этой ссылке и, в частности, этому ответу, который я включил для ссылки в случае, если ссылка уходит, HaoK (@Hao Kung здесь на SO):

Вы должны запечатать штамп безопасности чем-то случайным, например, новым руководством.

Я реализовал свой собственный 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. Чтобы использовать этот новый factory просто введите это в конструкторе UserManager:

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

Ответ 4

По умолчанию 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

Мне пришлось реализовать ClaimsIdentityFactory и установить свойство UserManager.ClaimsIdentityFactory, то есть в классе AccountController.

Ответ 6

В моем случае это было совсем другое. Это был вопрос о заказе кода запуска 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);

   //...

}