MVS5サイト(VS2012)でOAuthをセットアップしようとしています。
Fluent NHibernateを使用しています。独自のユーザーストアをセットアップし、リポジトリオブジェクトを渡して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
with:
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」エラーを介して
[〜#〜] edit [〜#〜]
Ctorエラーが発生した理由を見つけました。ユーザーオブジェクトはIDなしで戻ってきたため、デフォルトのUserManager
が動揺していました。これを修正し、デフォルトのUserManager
を使用しました。これにより、エラーはスローされなくなりましたが、それでもユーザーはログインしません。
過去にも同じエラーが発生しましたが、Entity Framework Migration Toolでユーザーを作成した場合にのみ発生しました。ユーザーを作成してWebサイトに署名するときに、エラーはありませんでした。
私のエラーは、移行時にSecurityStampを提供していなかったことです。
SecurityStamp = Guid.NewGuid().ToString()
このプロパティセット、すべてが機能しました。
同様の問題がありました。解決策は、ユーザーエンティティのSecurityStamp-Propertyを設定することでした。
背景:顧客は、データベースにパスワードを持つ管理者/スーパーユーザーアカウントと、パスワードなしでログインできる多数の追加ユーザーをXMLファイルで保持したい...
そのため、Entity Framework UserStoreを継承し、FindByIdAsyncとFindByNameAsyncをオーバーライドし、ユーザーのXMLファイルを検索して、新しいUser-Entityを返します。 (デフォルトの実装でユーザーが見つからなかった場合)
ClaimsIdentityを作成するときに、Jonと同じ例外がありました。
掘り下げた後、新しく作成したユーザーエンティティにはSecurityStampがないことがわかりました。そして、asp.netのデフォルトのUserManagerはSecurityStampを予期しており、それをClaimsIdentityのClaimとして設定しようとしています。
そのプロパティに値を設定した後-プレフィックスとユーザー名を含む文字列を使用しました-すべてがうまく動作します。
@ user3347549と同じことをしました。
エラーが実際にどこから来たのかを理解するのに時間がかかりました。
UserManagerとUserStoreの独自の実装を使用しています。これは、Guid型(MSSQLの一意識別子)をキーとして使用し、文字列ではなく(Guidの単なるプレースホルダーであるにもかかわらず)
this linkのおかげで、特にこの回答は、リンクがなくなった場合の参照用にHaoK(@Hao KungはSOで)に含めました:
新しいスタンプのような、ランダムなものでセキュリティスタンプをシードする必要があります。
独自のClaimsIdentityFactory(dotPeekで収集したものとまったく同じに見える)を実装し、CreateAsyncメソッドの1行を変更しただけです。
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
に "do nothing"メソッドを実装することです。
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);
}
私の場合、それはまったく違うものでした。それは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
を初期化しようとしていましたが、これは常にOwinに追加されていないため、常にnull
です。
単に交換するだけで、すべてが魅力のように機能しました
public void ConfigureAuth(IAppBuilder app)
{
//...
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<AppSignInManager>(AppSignInManager.Create);
app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);
//...
}
ClaimsIdentityFactoryを実装し、UserManager.ClaimsIdentityFactoryプロパティ、つまりAccountControllerクラスに設定する必要がありました。