【问题标题】:ResetPasswordAsync returns 'Invalid Token' when token is generated inside a WebJob在 WebJob 中生成令牌时,ResetPasswordAsync 返回“无效令牌”
【发布时间】:2024-01-19 07:25:01
【问题描述】:

我有一个计划的 WebJob,它每天运行并检查我数据库中所有用户的密码到期日期。如果密码到期日是今天,它将生成一个密码重置令牌并通过电子邮件发送给用户。然后用户点击电子邮件中的 url 并被带到一个网站,在那里他们输入新密码。

我设法在我的 WebJob 中生成了一个令牌并通过电子邮件发送它。但是,当通过我的 Asp.NET 网站重置密码时,我得到 Invalid Token。我不知道为什么。我认为它一定与我的 WebJob 中的令牌提供者有关。

1) 我的 Asp.NET 网站。自定义 UserManager:

public class CustomUserManager : UserManager<ApplicationUser> {
    public CustomUserManager(IUserStore<ApplicationUser> store) : base(store) { }          

    public static CustomUserManager Create(IdentityFactoryOptions<CustomUserManager> options, IOwinContext context) {
        var db = context.Get<DataContext>();
        var manager = new CustomUserManager(new UserStore<ApplicationUser>(db));
        // [...]          
        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null) {
            manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
        }
        // [...]   
        return manager;
    }
}

这样使用:

userManager = HttpContext.GetOwinContext().Get<CustomUserManager>();
// [...]
await userManager.ResetPasswordAsync(model.Id, model.Token, model.ConfirmPassword); // token here is invalid (although the string looks like a proper token)

2) 我的WebJob功能:

public static async void CheckPasswords([QueueTrigger("checkpasswords")] string message) {
    using (var db = new DataContext())
    using (var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(db))) {
        var provider = new DpapiDataProtectionProvider("MyApp");
        userManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("PasswordReset"));

        var users = await queryHandler.Run(new UserPasswordExpiryQuery());
        foreach (var user in users) {
            var days = new DateCalculations().DaysFromNow(user.PasswordExpiryDate);
            // if password expired today
            if (days == 0) {
                var token = await userManager.GeneratePasswordResetTokenAsync(user.Id);
                var url = string.Format("{0}/resetpass?user={1}&token={2}", settings.BaseUrl, user.Id, HttpUtility.UrlEncode(token));
                // [...] send email logic here
            }
        }
    }
}

稍后编辑

我想我可能已经想通了。我在我的 Asp.NET 应用程序中替换了令牌提供程序:

旧代码:

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

新代码:

 var provider = new DpapiDataProtectionProvider("MyApp");
 manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("ASP.NET Identity"));

稍后会做一些进一步的测试。

【问题讨论】:

    标签: c# asp.net asp.net-identity azure-web-app-service azure-webjobs


    【解决方案1】:

    您正在运行的逻辑可能违反了某些sandbox 限制。

    如果您直接或indirectly 分享您的网络应用名称,以及一次此类失败的 UTC 时间,我可能会确认这一点。

    【讨论】:

    • 谢谢,大卫。我想我最终想通了,我用潜在的解决方案编辑了我的问题(乍一看似乎工作正常)。我了解密码重置令牌是使用 SecurityStamp 生成的。我还假设它们没有存储在任何地方,而是使用某种加密算法生成的。所以我认为问题在于我的 2 个应用程序(Webjob 和网站)使用了 2 种不同的加密算法?因此令牌不匹配。
    • 啊,是的,可能是这样。沙盒是一个疯狂的猜测,显然是错误的。
    • 我认为您对沙盒的看法可能是对的,大卫。它只在本地运行良好,部署后停止工作。结果发现 Dpapi 在 Azure 上不起作用。因此,除非有更好的方法,否则我可能会在我的 ApplicationUser 表 (ResetPasswordToken) 中添加另一列,并在从我的 WebJob 内部生成令牌时更新该列。然后在重置密码过程中,我将 url 中的令牌与数据库中的令牌进行比较。
    • @Toonsylvania 我正在努力解决同样的问题。你找到解决办法了吗?