У меня есть приложение ASP.NET Core
2.2, работающее в нескольких экземплярах в веб-приложении Azure; он использует EF Core 2.2
и ASP.NET Identity
.
Все работает отлично, за исключением потока сброса пароля, где пользователь получает ссылку с токеном по электронной почте и должен выбрать новый пароль, нажав на эту ссылку. Он отлично работает локально, но в Azure он всегда завершается с ошибкой "Invalid Token".
Токены HTML кодируются и декодируются по мере необходимости; и у меня есть проверки, чтобы убедиться, что они совпадают с данными в базе данных; Кодировка URL не является проблемой.
Я настроил DataProtection
для хранения ключей в хранилище DataProtection
Azure, но безрезультатно. Все ключи хранятся в хранилище 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 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;
}
Как я заявляю в комментариях к коду, токен, полученный в запросе, совпадает с токеном, сгенерированным в базе данных, но ResetPasswordAsync
выполняет собственную проверку токена, и это не удается.
Любая помощь все равно будет оценена