密码重置后,UserManager.FindAsync返回null

时间:2015-09-07 19:00:33

标签: c# asp.net asp.net-mvc asp.net-identity-2

遵循官方文档(https://github.com/rustd/AspnetIdentitySample)和NuGet包,我在为MVC5应用程序重置密码后遇到登录问题。似乎实体框架不会在此过程中刷新其上下文,只有在我重新启动应用程序后才能使用正确的凭据登录。

据我所知,我已经完成了代码示例所做的所有事情。只有我有更多的代码和设置(例如Unity)。

这是问题所在:

public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    try
    {
        if (ModelState.IsValid)
        {
            ApplicationUser user = await UserManager.FindAsync(model.UserName, model.Password);
            if (user != null)
            {
                await this.SignInAsync(user, false);
                return RedirectToLocal(returnUrl);
            }
            else
            {
                model.State = ViewModelState.Error;
                model.Messages = new List<string>() { "No access buddy!" };
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);

    }
    catch (Exception ex)
    {
        throw;
    }
}
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);

    ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
    AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

这部分在我第一次登录时非常有效。但是,在我重置密码后,无法使用新凭据登录(它仍然需要旧版本)。

这是我的配置:

public class ApplicationUserManager : UserManager<ApplicationUser>
{
    #region Constructor

    public ApplicationUserManager(IUserStore<ApplicationUser> store)
        : base(store)
    {
        this.UserTokenProvider = new TotpSecurityStampBasedTokenProvider<ApplicationUser, string>();

    }
    #endregion Constructor

    #region Methods

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
    {
        ApplicationUserManager manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<SecurityDbContext>()));
        manager.UserValidator = new UserValidator<ApplicationUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };

        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = true,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };

        // Configure user lockout defaults
        manager.UserLockoutEnabledByDefault = true;
        manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
        manager.MaxFailedAccessAttemptsBeforeLockout = 5;

        // Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
        // You can write your own provider and plug it in here.
        manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser>
        {
            MessageFormat = "Your security code is {0}"
        });

        manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser>
        {
            Subject = "Security Code",
            BodyFormat = "Your security code is {0}"
        });

        manager.EmailService = new EmailService();
        manager.SmsService = new SmsService();
        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
        }
        return manager;
    }

    #endregion Methods
}

这是我在启动期间配置的:

// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(SecurityDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        // Enables the application to validate the security stamp when the user logs in.
        // This is a security feature which is used when you change a password or add an external login to your account.
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
            validateInterval: TimeSpan.FromMinutes(30),
            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    }
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });

最终,在几个屏幕之后,用户最终会在这里创建一个新密码:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    ApplicationUser user = await UserManager.FindByEmailAsync(model.Email);
    if (user == null)
    {
        // Don't reveal that the user does not exist
        return RedirectToAction("ResetPasswordConfirmation", "Account");
    }

    IdentityResult result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
    if (result.Succeeded)
    {
        return RedirectToAction("ResetPasswordConfirmation", "Account");
    }
    else
    {
        AddErrors(result);
        return View();
    }
}

此处也没有错误,它将新的散列值和安全标记存储在数据库中。我正在考虑一些在重置密码时没有刷新的缓存,cookie或dbContext。

有没有人有任何想法?

1 个答案:

答案 0 :(得分:1)

好的,所以我终于找到了这种奇怪行为的原因。我有以下DbConfiguration:

 public class Configuration : DbConfiguration
{
    public Configuration()
    {
        CacheTransactionHandler transactionHandler = new CacheTransactionHandler(new InMemoryCache());
        this.AddInterceptor(transactionHandler);

        Loaded += (sender, args) =>
       {
           args.ReplaceService<DbProviderServices>((s, _) => new CachingProviderServices(s, transactionHandler));
       };
    }
}

注释回调做了诀窍,这听起来合乎逻辑,因为我用二级缓存替换标准DbProviderServices(由https://efcache.codeplex.com/提供)

更新

没有必要完全删除第二级缓存。相反,通过添加缓存提供程序,我可以选择要缓存的表(以及缓存时间)。这是更新的代码:

 public class Configuration : DbConfiguration
{
    public Configuration()
    {
        CacheTransactionHandler transactionHandler = new CacheTransactionHandler(new InMemoryCache());
        this.AddInterceptor(transactionHandler);

        MyCachingPolicy cachingPolicy = new MyCachingPolicy();         
        Loaded += (sender, args) =>
       {
           args.ReplaceService<DbProviderServices>((s, _) => new CachingProviderServices(s, transactionHandler, cachingPolicy));
       };
    }
}

internal class MyCachingPolicy : CachingPolicy
{
    #region Constructor

    internal MyCachingPolicy()
    {
        this.NonCachableTables = new List<string>()
        {
            "AspNetUsers",
            "Resource",
            "Task",
            "Appointment"
        };
    }

    #endregion Constructor

    #region Properties

    private List<string> NonCachableTables { get; set; }

    #endregion Properties

    #region Methods

    #endregion Methods

    protected override bool CanBeCached(ReadOnlyCollection<EntitySetBase> affectedEntitySets, string sql, IEnumerable<KeyValuePair<string, object>> parameters)
    {
        return !affectedEntitySets.Select(e => e.Table ?? e.Name).Any(tableName => this.NonCachableTables.Contains(tableName));
    }

    protected override void GetCacheableRows(ReadOnlyCollection<EntitySetBase> affectedEntitySets, out int minCacheableRows, out int maxCacheableRows)
    {
        base.GetCacheableRows(affectedEntitySets, out minCacheableRows, out maxCacheableRows);
    }

    protected override void GetExpirationTimeout(ReadOnlyCollection<EntitySetBase> affectedEntitySets, out TimeSpan slidingExpiration, out DateTimeOffset absoluteExpiration)
    {
        base.GetExpirationTimeout(affectedEntitySets, out slidingExpiration, out absoluteExpiration);
    }
}