ASP.NET Core 2.2 - 密码重置在 Azure 上不起作用(无效令牌)

Posted

技术标签:

【中文标题】ASP.NET Core 2.2 - 密码重置在 Azure 上不起作用(无效令牌)【英文标题】:ASP.NET Core 2.2 - Password reset not working on Azure (Invalid token) 【发布时间】:2019-10-07 19:10:49 【问题描述】:

我有一个ASP.NET Core 2.2 应用程序在 Azure Web 应用程序的多个实例上运行;它使用EF Core 2.2ASP.NET Identity

除了密码重置流程之外,一切正常,用户在每封电子邮件中都会收到一个带有令牌的链接,并且需要通过单击该链接来选择新密码。它在本地完美运行,但在 Azure 上总是失败并出现“无效令牌”错误

令牌根据需要进行 html 编码和解码;我已经进行了检查以确保它们与数据库中的匹配; URL 编码不是问题。

我已将 DataProtection 配置为将密钥存储到 Azure Blob 存储,但无济于事。 密钥存储在 blob 存储中没有问题,但我仍然收到“Invalid Token”错误

这是我在 Startup.cs 上的设置:

public void ConfigureServices(IServiceCollection services)

    // This needs to happen before "AddMvc"
    // Code for this method shown below
    AddDataProtecion(services);

    services.AddDbContext<MissDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    var sp = services.BuildServiceProvider();

    services.ConfigureApplicationCookie(x =>
    
        x.Cookie.Name = ".MISS.SharedCookie";
        x.ExpireTimeSpan = TimeSpan.FromHours(8);
        // We need to set the cookie's DataProtectionProvider to ensure it will get stored in the azure blob storage
        x.DataProtectionProvider = sp.GetService<IDataProtectionProvider>();
    );

    services.AddIdentity<ApplicationUser, ApplicationRole>()
        .AddEntityFrameworkStores<MissDbContext>()
        .AddDefaultTokenProviders();


    // https://tech.trailmax.info/2017/07/user-impersonation-in-asp-net-core/
    services.Configure<SecurityStampValidatorOptions>(options => 
    
        options.ValidationInterval = TimeSpan.FromMinutes(10);
        options.OnRefreshingPrincipal = context =>
        
            var originalUserIdClaim = context.CurrentPrincipal.FindFirst("OriginalUserId");
            var isImpersonatingClaim = context.CurrentPrincipal.FindFirst("IsImpersonating");
            if (isImpersonatingClaim?.Value == "true" && originalUserIdClaim != null)
            
                context.NewPrincipal.Identities.First().AddClaim(originalUserIdClaim);
                context.NewPrincipal.Identities.First().AddClaim(isImpersonatingClaim);
            
            return Task.FromResult(0);
        ;
    );

     // some more initialisations here

这里是AddDataProtection 方法:

/// <summary>
/// Add Data Protection so that cookies don't get invalidated when swapping slots.
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
void AddDataProtecion(IServiceCollection services)

    var sasUrl = Configuration.GetValue<string>("DataProtection:SaSUrl");
    var containerName = Configuration.GetValue<string>("DataProtection:ContainerName");
    var applicationName = Configuration.GetValue<string>("DataProtection:ApplicationName");
    var blobName = Configuration.GetValue<string>("DataProtection:BlobName");
    var keyIdentifier = Configuration.GetValue<string>("DataProtection:KeyVaultIdentifier");

    if (sasUrl == null || containerName == null || applicationName == null || blobName == null)
        return;

    var storageUri = new Uri($"sasUrl");

    var blobClient = new CloudBlobClient(storageUri);

    var container = blobClient.GetContainerReference(containerName);
    container.CreateIfNotExistsAsync().GetAwaiter().GetResult();

    applicationName = $"applicationName-Environment.EnvironmentName";
    blobName = $"applicationName-blobName";

    services.AddDataProtection()
        .SetApplicationName(applicationName)
        .PersistKeysToAzureBlobStorage(container, blobName);

我也尝试将密钥持久化到 DbContext,但结果是相同的:密钥已存储,但在尝试重置密码时,我仍然收到 Invalid token 消息,Every。单身的。时间。

请求密码重置方法

public async Task RequestPasswordReset(string emailAddress, string ip, Request httpRequest) 

    var user = await _userManager.FindByEmailAsync(emailAddress);

    var resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);

    var resetRequest = new PasswordResetRequest
    
        CreationDate = DateTime.Now,
        ExpirationDate = DateTime.Now.AddDays(1),
        UserId = user.Id,
        Token = resetToken,
        IP = ip
    ;

    _context.PasswordResetRequests.Add(resetRequest);
    await _context.SaveChangesAsync();

    await SendPasswordResetEmail(user, resetRequest, httpRequest);

重置密码方法

一旦用户请求重置密码,他们会收到一封包含链接和令牌的电子邮件;以下是我在用户点击该链接后尝试重置用户密码的方式:

public async Task<IdentityResult> ResetPassword(string token, string password) 

    // NO PROBLEM HERE - The received token matches with the one in the Db
    var resetRequest = await _context.PasswordResetRequests
        .AsNoTracking()
        .FirstOrDefaultAsync(x => x.Token == token);

    var user = await _userManager.FindByIdAsync(resetRequest.UserId);

    // PROBLEM - This method returns "Invalid Token"
    var result = await _userManager.ResetPasswordAsync(user, resetRequest.Token, password);

    if (result.Succeeded)
        await SendPasswordChangedEmail(user);

    return result;

正如我在代码 cmets 中所述,请求中收到的令牌与数据库中生成的令牌相匹配,但 ResetPasswordAsync 自己进行令牌验证,但失败了。

任何帮助仍将不胜感激

【问题讨论】:

您是否尝试过使用支持多实例的方法来显式保护密钥?我用Azure Key Vault没有问题,不过应该还有其他方法,见here。 嗨@FedericoDipuma,感谢您的建议。我已尝试创建 Key Vault,但我的 Azure 帐户中的 AD 似乎存在问题,我无法创建一个。我开始怀疑这是否都是相互关联的…… @FedericoDipuma,您可以使用密钥库分享您的代码吗?我终于设法解决了我的 Azure 帐户问题并创建了一个 Key Vault 您找到解决方案了吗?我想我可能有同样的问题。 【参考方案1】:

这表明您的令牌是用不同的方式生成的。 你能试试这个吗? 生成新令牌:

var code = await UserManager.GeneratePasswordResetTokenAsync(resetRequest.UserId);

并重置密码:

var resetResult = await userManager.ResetPasswordAsync(resetRequest.UserId, code, password);

另一种情况是令牌的HTML编码不正确:

token = HttpUtility.UrlDecode(token) ;

下一种情况是 userManager 对于每个请求都必须是单例的(或者至少是 tokenProvider 类)。

这是指向源代码的链接 https://github.com/aspnet/Identity/blob/rel/2.0.0/src/Microsoft.Extensions.Identity.Core/UserManager.cs#L29

手动令牌处理,以防令牌提供者因将令牌存储到私有变量中而出现不同的实例:

private readonly Dictionary<string, IUserTwoFactorTokenProvider<TUser>> _tokenProviders =
            new Dictionary<string, IUserTwoFactorTokenProvider<TUser>>();

下一个代码可能会实现:

  public override async Task<bool> VerifyUserTokenAsync(TUser user, string tokenProvider, string purpose, string token)
        
            ThrowIfDisposed();
            if (user == null)
            
                throw new ArgumentNullException(nameof(user));
            
            if (tokenProvider == null)
            
                throw new ArgumentNullException(nameof(tokenProvider));
            
//should be overriden
// if (!_tokenProviders.ContainsKey(tokenProvider))
//           
//              throw new 
//NotSupportedException(string.Format(CultureInfo.CurrentCulture, 
//Resources.NoTokenProvider, tokenProvider));
//          
// Make sure the token is valid
//        var result = await _tokenProviders[tokenProvider].ValidateAsync(purpose, token, this, user);

  //          if (!result)
  //        
  //          Logger.LogWarning(9, "VerifyUserTokenAsync() failed with //purpose: purpose for user userId.", purpose, await GetUserIdAsync(user));
       //    
var resetRequest = await _context.PasswordResetRequests
        .AsNoTracking()
        .FirstOrDefaultAsync(x => x.Token == token);
            if (resetRequest == null )
            
                return IdentityResult.Failed(ErrorDescriber.InvalidToken());
            

            // Make sure the token is valid
            var result = resetRequest.IsValid();

            if (!result)
            
                Logger.LogWarning(9, "VerifyUserTokenAsync() failed with purpose: purpose for user userId.", purpose, await GetUserIdAsync(user));
            
            return result;
        

【讨论】:

嗨奥列格,这就是我已经在做的事情;我有一个方法叫RequestPasswordReset,我叫var resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);。然后,在我的问题中显示的方法上,我处理了用户点击他们通过电子邮件收到的密码重置链接的请求。 UserTokenProvider 的 TokenLifespan 值是多少? 您还可以在使用之前使用 VerifyUserTokenAsync 验证您的令牌吗?实际上这个方法抛出异常 await VerifyUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider, ResetPasswordTokenPurpose, token) 令牌生命周期设置为默认值(1 天)。当然,我会尝试VeryfyUserTokenAsync,但无论如何都会在userManager.ResetPasswordAsync 内部调用该方法,当然,这可能是它失败的地方。 编码 url 数据似乎有问题 - 可以解码它 HttpUtility.UrlDecode(token) 吗?

以上是关于ASP.NET Core 2.2 - 密码重置在 Azure 上不起作用(无效令牌)的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET CORE Identity DataProtectionTokenProviderOptions 密码重置令牌无效

ASP.NET Core Identity - 使令牌(电子邮件确认、密码重置等)在不同服务器上有效?

跨 Asp.NET Core 和 Framework 的数据保护提供程序(生成密码重置链接)

ASP.NET Core 打造一个简单的图书馆管理系统密码修改以及密码重置

ASP.NET Core 打造一个简单的图书馆管理系统密码修改以及密码重置

如何使用 Asp.Net Core 2.2 / IdentityServer4 / SP.NET Core Identity 手动散列密码