简体   繁体   English

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

[英]UserManager.FindAsync returns null after password reset

Following the official documentation ( https://github.com/rustd/AspnetIdentitySample ) and NuGet package, I'm having issues with logging in after a password reset for my MVC5 application. 按照官方文档( https://github.com/rustd/AspnetIdentitySample )和NuGet程序包,在为MVC5应用程序重置密码后,我在登录时遇到问题。 It seems as though Entity Framework doesn't refresh its context in the process, it's only after I restart my application that I can login with the correct credentials. 似乎Entity Framework在此过程中并未刷新其上下文,只有在重新启动应用程序后,我才能使用正确的凭据登录。

As far as I can work out, I've done everything that the code samples have done as well. 据我所知,我已经完成了代码示例所做的一切。 Only I have much more code and settings (eg Unity). 只有我有更多的代码和设置(例如Unity)。

This is the problem area: 这是问题区域:

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);
}

This part works perfectly when I log on for the first time. 当我第一次登录时,此部分工作正常。 However, after I have reset my password, logging in with the new credentials isn't possible (it still takes the old version). 但是,重置密码后,无法使用新凭据登录(仍然使用旧版本)。

Here is my configuration: 这是我的配置:

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
}

This is what I've configured during Startup: 这是我在启动期间配置的:

// 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 { });

Ultimately, after a few screens, here is where the user ultimately ends up to create a new password: 最终,在经过几个屏幕之后,最终用户可以在此处创建新密码:

[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();
    }
}

No errors here either, it stores the new hashed value and security stamp in the database. 这里也没有错误,它将新的哈希值和安全标记存储在数据库中。 I'm thinking of some caching, cookies or dbContext that isn't refreshed at the time the password is reset. 我在考虑一些缓存,cookie或dbContext,它们在重置密码时不会刷新。

Does anyone have any ideas? 有人有什么想法吗?

Ok so I have finally found the reason for this odd behavior. 好的,所以我终于找到了这种奇怪行为的原因。 I had the following DbConfiguration: 我有以下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));
       };
    }
}

Commenting out the callback did the trick, which sounds logical as I replaced the standard DbProviderServices with second-level caching (as provided by https://efcache.codeplex.com/ ) 注释掉回调可以解决问题,当我用第二级缓存(由https://efcache.codeplex.com/提供)替换标准DbProviderServices时,听起来很合理。

Update: 更新:

It's not necessary to entirely remove the second-level caching. 不必完全删除二级缓存。 Instead, by adding a caching provider, I can choose which tables to cache (and for how long). 相反,通过添加缓存提供程序,我可以选择要缓存的表(以及多长时间)。 Here is the updated code: 这是更新的代码:

 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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM