我在Azure Web应用程序上的多个实例上有一个ASP.NET Core
2.2应用程序;它使用EF Core 2.2
和ASP.NET Identity
。
除了密码重置流外,用户每本电子邮件都会收到带有令牌的链接,并且需要通过单击该链接来选择新密码。它在本地工作非常完美,但是在Azure上它总是失败,而"无效令牌"错误。
代币根据需要进行HTML编码和解码;而且我有检查,以确保它们匹配数据库中的这些功能;URL编码不是问题。
我已经配置了DataProtection
将密钥存储到Azure Blob存储中,但无济于事。键存储在Blob Store中,但我仍然得到一个"无效的令牌"错误。
这是我在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
消息。单身的。时间。
请求密码重置方法
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
可以自己的令牌验证,并且失败了。
任何帮助仍然将不胜感激
它以不同的方式生成您的令牌。你能尝试一下吗?生成新令牌:
var code = await UserManager.GeneratePasswordResetTokenAsync(resetRequest.UserId);
和重置密码:
var resetResult = await userManager.ResetPasswordAsync(resetRequest.UserId, code, password);
另一种情况是对令牌的HTML编码不正确:
token = HttpUtility.UrlDecode(token) ;
下一个情况是每个请求必须是singlemanager(或至少是tokenprovider类(。
这是源代码的链接https://github.com/aspnet/indentity/blob/rel/2.0.0/src/microsoft.extensions.indentity.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;
}