我正在尝试为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
,但仍然没有记录用户。它返回的身份对象看起来不错。
答案 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);
//...
}