web-dev-qa-db-ja.com

ASP.NET 5 ID-カスタムSignInManager

MVC 6プロジェクト(vNext)があり、ASP.NET Identityで遊んでいます。私の場合、EFを使用する組み込みのもの(SignInManager、UserManager、UserStore)は使用したくありません。外部データベースがあり、ユーザー名/パスワードを検索して有効なCookieを返したいだけです。それで、私は自分のクラスを書き始めました。

public class MyUser
{
    public string Id { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public string PasswordHash { get; set; }
}

public class MyUserStore : IUserStore<MyUser>, IUserPasswordStore<MyUser>
{
    ...
}

MyUserStoreクラスでは、ハードコード化されたユーザーのリストをストアとして使用しています(テスト目的のみ)。そして、ハードコードされたストアからデータを返すためだけにいくつかのメソッドをオーバーライドしました。

public class MyUserManager : UserManager<MyUser>
{
    public MyUserManager(
        IUserStore<MyUser> store,
        IOptions<IdentityOptions> optionsAccessor,
        IPasswordHasher<MyUser> passwordHasher,
        IEnumerable<IUserValidator<MyUser>> userValidators,
        IEnumerable<IPasswordValidator<MyUser>> passwordValidators,
        ILookupNormalizer keyNormalizer,
        IdentityErrorDescriber errors,
        IEnumerable<IUserTokenProvider<MyUser>> tokenProviders,
        ILoggerFactory logger,
        IHttpContextAccessor contextAccessor) :
        base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, tokenProviders, logger, contextAccessor)
    {
    }
}

ここでは、テスト用にそれぞれCheckPasswordAsyncPasswordVerificationResult.Successを返すメソッドVerifyPasswordAsynctrueを作成しました。

public class MyClaimsPrincipleFactory : IUserClaimsPrincipalFactory<MyUser>
{
    public Task<ClaimsPrincipal> CreateAsync(MyUser user)
    {
        return Task.Factory.StartNew(() =>
        {
            var identity = new ClaimsIdentity();
            identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
            var principle = new ClaimsPrincipal(identity);

            return principle;
        });
    }
}

public class MySignInManager : SignInManager<MyUser>
{
    public MySignInManager(MyUserManager userManager, IHttpContextAccessor contextAccessor, IUserClaimsPrincipalFactory<MyUser> claimsFactory, IOptions<IdentityOptions> optionsAccessor = null, ILoggerFactory logger = null)
            : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger)
    {
    }

    public override Task<SignInResult> PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout)
    {
        // here goes the external username and password look up

        if (userName.ToLower() == "username" && password.ToLower() == "password")
        {
            return base.PasswordSignInAsync(userName, password, isPersistent, shouldLockout);
        }
        else
        {
            return Task.FromResult(SignInResult.Failed);
        }
    }
}

そして、すべてがStartupクラスに次のように接続されます:

services.AddIdentity<MyUser, MyRole>()
            .AddUserStore<MyUserStore>()
            .AddUserManager<MyUserManager>()
            .AddDefaultTokenProviders();

そして、MySignInManagerコード内にStartupオブジェクトを作成してDIに追加することができなかったため(後でコントローラーとビューに挿入するため)、 MyAccountController

public MyAccountController(IHttpContextAccessor httpContextAccessor, UserManager<MyUser> userManager, IOptions<IdentityOptions> optionsAccessor, ILoggerFactory logger)
{
    SignInManager = new MySignInManager(userManager as MyUserManager, httpContextAccessor, new MyClaimsPrincipleFactory(), optionsAccessor, logger);
}

MyLoginコントローラーのMyAccountアクションでPasswordSignInAsyncを呼び出していますが、エンコードされたクレームを含むCookieを取得していることがわかります(MyClaimsPrincipleFactory)。 AuthorizeAttributeを指定して他のアクションを呼び出そうとすると、Cookieがリクエストヘッダーにあることがわかりますが、不正です(より正確には、組み込みの既定ASPを削除しなかったためです)。 Visual StudioサンプルテンプレートからのNET Identity Authentication、代わりにAccount/Loginにリダイレクトされます)。

これはASP.NET IDをカスタマイズする正しい方法ですか?ここで何が欠けていますか?

27
Dilyan Dimitrov

カスタムSignInManagerを使用しようとしても問題があり、結局は実装が非常に簡単であることがわかりました。

Startup.csで、services.Identityのデフォルト実装の後

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

組み込みのDIに次を注入するだけです。

services.AddScoped<SignInManager<MyApplicationUser>, MySignInManager>();

デフォルトのSignInManagerはカスタムで上書きされます。

10
MissRaphie

私のプロジェクトでは、EFを使用せずにIDを実際に実装しています。必要以上に実装していると思います。 UserManagerとSignInManagerはEFに関連付けられていません。必要に応じてこれらを実装できますが、EFから逃げる必要はありません。テストのためにハードコードされたパスワードを検証する必要がある場合にのみ、UserStoreとRoleStoreを実装する必要があり、おそらくPasswordHasherを実装する必要があるだけです。

services.TryAdd(ServiceDescriptor.Scoped<IUserStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserPasswordStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserEmailStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserLoginStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserRoleStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserClaimStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserPhoneNumberStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserLockoutStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserTwoFactorStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IRoleStore<SiteRole>, RoleStore<SiteRole>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserClaimsPrincipalFactory<SiteUser>, SiteUserClaimsPrincipalFactory<SiteUser, SiteRole>>());
services.TryAdd(ServiceDescriptor.Transient<IPasswordHasher<SiteUser>, SitePasswordHasher<SiteUser>>());
services.AddIdentity<SiteUser, SiteRole>();

上記は、Entity Frameworkをバイパスするために実装しているアイテムを示しています。たとえば、AccountControllerは

    UserManager<SiteUser> userManager,
            SignInManager<SiteUser> signInManager

これらは、DIサービスでセットアップする必要がなかった標準ID UserManagerおよびSignInManagerです。これらは、次の行で登録されています。

services.AddIdentity<SiteUser, SiteRole>();

その拡張メソッドのコードを参照 。 EFIdentityの一部ではなく、Identityの一部です。 IUserClaimsPrincipalFactoryも実装していることがわかります。私が実装した唯一の理由は、カスタムクレームを追加することでした。EFから逃れるためにそれを行う必要はありませんでした。

6
Joe Audette

ここでの問題は、AuthenticationTypeオブジェクトのClaimsIdentityを提供しなかったことです。 このブログ投稿 助けてくれました。

IsAuthenticatedをtrueに設定するには、ctorで認証タイプを指定する必要があります。

var id = new ClaimsIdentity(claims、“ Custom”);

4
Dilyan Dimitrov

次のようにIdentityBuilder.AddSignInManagerメソッド内で Startup.ConfigureServices メソッドを使用することにより、依存性注入のカスタムSignInManagerを登録できます。

services.AddIdentity<MyUser, IdentityRole<int>>()
    .AddUserStore<UserStore<MyUser, IdentityRole<int>, SqlContext, int>>()
    .AddRoleStore<RoleStore<IdentityRole<int>, SqlContext, int>>()
    .AddSignInManager<SignInManager<MyUser>>()
    .AddDefaultTokenProviders();

実装したSignInManagerを同じ方法でDIに登録できない理由はありません。

3
CalC