Custom ASP.NET Identity 2.0 UserStore - Is impleme

2019-03-17 20:31发布

I've created a custom IUserStore<TUser,int> for my application. I've implemented the interfaces I need,

   IUserStore<TUser, int>,
   IUserRoleStore<TUser, int>,
   IUserLockoutStore<TUser, int>,
   IUserPasswordStore<TUser, int>

but when I call

var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);

I get an exception saying

Store does not implement IUserTwoFactorStore<TUser>.

I'm not using two factor authentication anywhere in my application. Why does it expect me to implement that interface? Is it required that I implement all of these interfaces, even if I don't actually use them?

3条回答
ら.Afraid
2楼-- · 2019-03-17 21:00

Actually the IUserTwoFactorStore interface is really simple, so far my implementation (I don't use two factor auth either) is this:

 ....
 public Task<bool> GetTwoFactorEnabledAsync(User user)
 {
     return Task.FromResult(false);
 }

 public Task SetTwoFactorEnabledAsync(User user, bool enabled)
 {
     throw new NotImplementedException();
 }

It works, although I just did it couple minutes ago and didn't test whole app thoroughly.

查看更多
手持菜刀,她持情操
3楼-- · 2019-03-17 21:03

I had the same problem. For the moment, as the SignInManager.SignInOrTwoFactor method blindly checks for the GetTwoFactorAuthentication it throws an exception when the UserStore doesn't implement the IUserTwoFactorStore.

I believe Microsoft intended that the SignInManager PasswordSignInAsync method must be overriden in a custom SignInManager class. Unfortunately I couldn't find any documentation or samples pointing so.

Here is the SignInManager wrapper class I implemented to solve this issue:

public class EnhancedSignInManager<TUser, TKey> : SignInManager<TUser, TKey>
    where TUser : class, IUser<TKey>
    where TKey : IEquatable<TKey>
{
    public EnhancedSignInManager(
        UserManager<TUser, TKey> userManager, 
        IAuthenticationManager authenticationManager)
        : base(userManager, authenticationManager)
    {
    }

    public override async Task SignInAsync(
        TUser user, 
        bool isPersistent, 
        bool rememberBrowser)
    {
        var userIdentity = await CreateUserIdentityAsync(user).WithCurrentCulture();

        // Clear any partial cookies from external or two factor partial sign ins
        AuthenticationManager.SignOut(
            DefaultAuthenticationTypes.ExternalCookie, 
            DefaultAuthenticationTypes.TwoFactorCookie);

        if (rememberBrowser)
        {
            var rememberBrowserIdentity = AuthenticationManager
                .CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id));

            AuthenticationManager.SignIn(
                new AuthenticationProperties { IsPersistent = isPersistent }, 
                userIdentity, 
                rememberBrowserIdentity);
        }
        else
        {
            AuthenticationManager.SignIn(
                new AuthenticationProperties { IsPersistent = isPersistent }, 
                userIdentity);
        }
    }

    private async Task<SignInStatus> SignInOrTwoFactor(TUser user, bool isPersistent)
    {
        var id = Convert.ToString(user.Id);

        if (UserManager.SupportsUserTwoFactor 
            && await UserManager.GetTwoFactorEnabledAsync(user.Id)
                                .WithCurrentCulture()
            && (await UserManager.GetValidTwoFactorProvidersAsync(user.Id)
                                 .WithCurrentCulture()).Count > 0
                && !await AuthenticationManager.TwoFactorBrowserRememberedAsync(id)
                                               .WithCurrentCulture())
        {
            var identity = new ClaimsIdentity(
                DefaultAuthenticationTypes.TwoFactorCookie);

            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id));

            AuthenticationManager.SignIn(identity);

            return SignInStatus.RequiresVerification;
        }
        await SignInAsync(user, isPersistent, false).WithCurrentCulture();
        return SignInStatus.Success;
    }

    public override async Task<SignInStatus> PasswordSignInAsync(
        string userName, 
        string password, 
        bool isPersistent, 
        bool shouldLockout)
    {
        if (UserManager == null)
        {
            return SignInStatus.Failure;
        }

        var user = await UserManager.FindByNameAsync(userName).WithCurrentCulture();
        if (user == null)
        {
            return SignInStatus.Failure;
        }

        if (UserManager.SupportsUserLockout 
            && await UserManager.IsLockedOutAsync(user.Id).WithCurrentCulture())
        {
            return SignInStatus.LockedOut;
        }

        if (UserManager.SupportsUserPassword 
            && await UserManager.CheckPasswordAsync(user, password)
                                .WithCurrentCulture())
        {
            return await SignInOrTwoFactor(user, isPersistent).WithCurrentCulture();
        }
        if (shouldLockout && UserManager.SupportsUserLockout)
        {
            // If lockout is requested, increment access failed count
            // which might lock out the user
            await UserManager.AccessFailedAsync(user.Id).WithCurrentCulture();
            if (await UserManager.IsLockedOutAsync(user.Id).WithCurrentCulture())
            {
                return SignInStatus.LockedOut;
            }
        }
        return SignInStatus.Failure;
    }
}

I hope it helps. Cheers

查看更多
萌系小妹纸
4楼-- · 2019-03-17 21:05

I had the same problem and I don't want to implement the IUserTwoFactorStore<TUser, TKey> just to say that I don't implement it. But I also don't want to go back and muck around if I end up wanting to implement it (which I anticipate I will). So what I consider a future proof (and reusable) solution would be: (inspired by @gjsduarte's answer)

public class SafeUserManager<TUser, TKey> : UserManager<TUser, TKey>
{
    public override Task<bool> GetTwoFactorEnabledAsync(TKey userId)
    {
        return Store is IUserTwoFactorStore<TUser, TKey>
            ? base.GetTwoFactorEnabledAsync(userId)
            : Task.FromResult(false);
    }
}

It would probably be a good idea to do the same for the other Get[feature]EnabledAsync(TKey userId) methods.

查看更多
登录 后发表回答