我正在尝试为MVC5站点设置OAuth(在VS2012中)。
我正在使用Fluent NHibernate。我已经设置了自己的Userstore,并传入一个存储库对象来访问NHibernate会话对象。我将我的存储传递给默认的aspnet用户管理器提供程序。这最终适用于本地注册和登录。我并没有试图设置与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 DriveDevelopmentGoalManagementWebControllersAccountController.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 DriveDevelopmentGoalManagementWebControllersAccountController.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.Commands.claim..ctor"错误
编辑
找出发生ctor错误的原因。用户对象返回时没有ID,因此默认的UserManager
变得不正常。修复了这个问题,并使用了默认的UserManager
,它现在不再抛出错误,但仍然不会让用户登录。据我所知,它返回的标识对象看起来不错。
我过去也遇到过同样的错误,但只是在使用实体框架迁移工具创建用户时。创建用户并在网站内签名时,我没有出错。
我的错误是没有为迁移提供SecurityStamp。
SecurityStamp = Guid.NewGuid().ToString()
这套房产,一切正常。
我也遇到了类似的问题。解决方案是设置用户实体的SecurityStamp属性。
背景:客户希望在数据库中拥有带密码的管理员/超级用户帐户,并在XML文件中拥有一批额外的用户,这些用户可以在没有密码的情况下登录。。。
因此,我从实体框架UserStore继承,覆盖FindByIdAsync和FindByNameAsync,在XML文件中搜索用户并返回一个新的用户实体。(如果默认实现没有找到用户)
在创建ClaimsIdentity时,我遇到了与Jon相同的异常。
经过一番挖掘,我发现我新创建的用户实体没有SecurityStamp。asp.net默认UserManager需要一个SecurityStamp,并希望将其设置为ClaimsIdentity中的Claim。
在为该属性设置了一个值之后——我使用了一个包含前缀和用户名的字符串——一切对我来说都很好
当密码少于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;
});
我做了与@user3347549相同的事情。
我花了一段时间才弄清楚错误的真正来源,为此向dotPeek致敬!
我使用自己的UserManager和UserStore实现,因为我想要Guid类型(MSSQL中的uniqueidentifier)作为密钥,而不是字符串(尽管它们只是Guid的占位符)
感谢这个链接,特别是这个答案,我把它包括在内,以备链接消失时参考
您应该用一些随机的东西来播种安全戳,比如一个新的guid作品。
我实现了自己的ClaimsIdentity Factory(从我在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>();
默认的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);
}
我必须实现ClaimsIdentity Factory并设置UserManager.ClaimsIdentity Factory属性,即在AccountController类中。
在我的情况下,情况完全不同。这是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);
//...
}