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
, которое теперь больше не выдает ошибку, но все равно не регистрирует пользователя. Объект идентификации, который он возвращает, выглядит хорошо из того, что я могу сказать.
Ответы
Ответ 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);
//...
}