【问题标题】:Asp.Net Identity Generate Password Reset Token from one Application Pool Identity and Verify on anotherAsp.Net Identity 从一个应用程序池标识生成密码重置令牌并在另一个应用程序池标识上进行验证
【发布时间】:2019-04-22 04:59:41
【问题描述】:

我们有一个面向客户的网站和一个用于创建用户的后台。在我们的开发人员机器上的 IIS Express 上运行这两个应用程序时,使用带有密码重置的欢迎电子邮件创建新用户可以完美地工作。但是,当我们部署应用程序并且应用程序托管在具有不同应用程序池标识的不同 IIS 服务器上时,它会停止工作。

我们已经能够在同一台服务器上离线复制错误,但使用不同的应用程序池标识。如果我们切换以便应用程序在 IIS 中使用相同的应用程序池标识,那么一切都会重新开始工作。

后台:

applicationDbContext = new ApplicationDbContext();
userManager = new ApplicationUserManager(new ApplicationUserStore(applicationDbContext), applicationDbContext);
var createdUser = userManager.FindByEmail(newUser.Email);
var provider = new DpapiDataProtectionProvider("Application.Project");
userManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, int>(provider.Create("ASP.NET Identity"));
var token = userManager.GeneratePasswordResetToken(createdUser.Id);

客户门户:

var applicationDbContext = new ApplicationDbContext();
userManager = new ApplicationUserManager(new ApplicationUserStore(applicationDbContext), applicationDbContext);

var user = await userManager.FindByEmailAsync(model.Email);
if (user == null)
{
    return GetErrorResult(IdentityResult.Failed());
}

var provider = new DpapiDataProtectionProvider("Application.Project");
userManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, int>(provider.Create("ASP.NET Identity"));

//This code fails with different Application Pool Identities
if (!await userManager.UserTokenProvider.ValidateAsync("ResetPassword", model.Token, userManager, user))
{
    return GetErrorResult(IdentityResult.Failed());
}

var result = await userManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword);

IdentityResult 显示Succeeded false 但没有错误代码。无论如何,还是我们需要自己实现令牌生成和验证?

【问题讨论】:

    标签: c# asp.net asp.net-identity asp.net-identity-2


    【解决方案1】:

    事实证明这有点棘手。找到了一些参考资料,但他们在同一台服务器上使用了MachineKey。我希望它完全跨不同的服务器和用户。

    Data Protection provider across Asp.NET Core and Framework (generate password reset link)

    由于我没有收到错误代码,我开始在 DataProtectionTokenProvider.cs 的帮助下实现自己的 ValidateAsync 以获取 ASP.NET Core Identity。这堂课真的帮我找到了解决办法。

    https://github.com/aspnet/Identity/blob/master/src/Identity/DataProtectionTokenProvider.cs

    我最终遇到了以下错误:

    密钥在指定状态下无效。

    使用DataProtectorTokenProvider&lt;TUser, TKey&gt; 时,令牌是从SecurityStamp 生成的,但很难深入挖掘。但是,鉴于在单个服务器上更改 Application Pool Identity 时验证失败,因此实际的保护机制看起来像这样:

    System.Security.Cryptography.ProtectedData.Protect(userData, entropy, DataProtectionScope.CurrentUser);
    

    如果所有站点都使用相同的Application Pool Identity,它也可以工作。也可以是DataProtectionProviderprotectionDescriptor "LOCAL=user"

    new DataProtectionProvider("LOCAL=user")
    

    https://docs.microsoft.com/en-us/previous-versions/aspnet/dn613280(v%3dvs.108)

    https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.dataprotector?view=netframework-4.7.2

    https://docs.microsoft.com/en-us/uwp/api/windows.security.cryptography.dataprotection.dataprotectionprovider

    当阅读DpapiDataProtectionProvider(DPAPI 代表数据保护应用程序编程接口)时,描述说:

    用于提供源自于 数据保护 API。是您数据保护的最佳选择 应用程序不是由 ASP.NET 托管的,所有进程都运行为 相同的域身份

    Create 方法的用途描述为:

    用于确保受保护数据的附加熵只能 出于正确目的而不受保护。

    https://docs.microsoft.com/en-us/previous-versions/aspnet/dn253784(v%3dvs.113)

    鉴于此信息,我认为尝试使用Microsoft 提供的普通类没有任何进展。

    我最终实现了自己的 IUserTokenProvider&lt;TUser, TKey&gt;IDataProtectionProviderIDataProtector 来改正它。

    我选择使用证书实现IDataProtector,因为我可以相对容易地在服务器之间传输这些证书。我还可以使用运行网站的Application Pool IdentityX509Store 获取它,因此应用程序本身中不会存储任何密钥。

    public class CertificateProtectorTokenProvider<TUser, TKey> : IUserTokenProvider<TUser, TKey>
        where TUser : class, IUser<TKey>
        where TKey : IEquatable<TKey>
    {
        private IDataProtector protector;
    
        public CertificateProtectorTokenProvider(IDataProtector protector)
        {
            this.protector = protector;
        }
        public virtual async Task<string> GenerateAsync(string purpose, UserManager<TUser, TKey> manager, TUser user)
        {
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }
            var ms = new MemoryStream();
            using (var writer = new BinaryWriter(ms, new UTF8Encoding(false, true), true))
            {
                writer.Write(DateTimeOffset.UtcNow.UtcTicks);
                writer.Write(Convert.ToInt32(user.Id));
                writer.Write(purpose ?? "");
                string stamp = null;
                if (manager.SupportsUserSecurityStamp)
                {
                    stamp = await manager.GetSecurityStampAsync(user.Id);
                }
                writer.Write(stamp ?? "");
            }
            var protectedBytes = protector.Protect(ms.ToArray());
            return Convert.ToBase64String(protectedBytes);
        }
    
        public virtual async Task<bool> ValidateAsync(string purpose, string token, UserManager<TUser, TKey> manager, TUser user)
        {
            try
            {
                var unprotectedData = protector.Unprotect(Convert.FromBase64String(token));
                var ms = new MemoryStream(unprotectedData);
                using (var reader = new BinaryReader(ms, new UTF8Encoding(false, true), true))
                {
                    var creationTime = new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero);
                    var expirationTime = creationTime + TimeSpan.FromDays(1);
                    if (expirationTime < DateTimeOffset.UtcNow)
                    {
                        return false;
                    }
    
                    var userId = reader.ReadInt32();
                    var actualUser = await manager.FindByIdAsync(user.Id);
                    var actualUserId = Convert.ToInt32(actualUser.Id);
                    if (userId != actualUserId)
                    {
                        return false;
                    }
                    var purp = reader.ReadString();
                    if (!string.Equals(purp, purpose))
                    {
                        return false;
                    }
                    var stamp = reader.ReadString();
                    if (reader.PeekChar() != -1)
                    {
                        return false;
                    }
    
                    if (manager.SupportsUserSecurityStamp)
                    {
                        return stamp == await manager.GetSecurityStampAsync(user.Id);
                    }
                    return stamp == "";
                }
            }
            catch (Exception e)
            {
                // Do not leak exception
            }
            return false;
        }
    
        public Task NotifyAsync(string token, UserManager<TUser, TKey> manager, TUser user)
        {
            throw new NotImplementedException();
        }
    
        public Task<bool> IsValidProviderForUserAsync(UserManager<TUser, TKey> manager, TUser user)
        {
            throw new NotImplementedException();
        }
    }
    
    public class CertificateProtectionProvider : IDataProtectionProvider
    {
        public IDataProtector Create(params string[] purposes)
        {
            return new CertificateDataProtector(purposes);
        }
    }
    
    public class CertificateDataProtector : IDataProtector
    {
        private readonly string[] _purposes;
    
        private X509Certificate2 cert;
    
        public CertificateDataProtector(string[] purposes)
        {
            _purposes = purposes;
            X509Store store = null;
    
            store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
    
            var certificateThumbprint = ConfigurationManager.AppSettings["CertificateThumbprint"].ToUpper();
    
            cert = store.Certificates.Cast<X509Certificate2>()
                .FirstOrDefault(x => x.GetCertHashString()
                    .Equals(certificateThumbprint, StringComparison.InvariantCultureIgnoreCase));
        }
    
        public byte[] Protect(byte[] userData)
        {
            using (RSA rsa = cert.GetRSAPrivateKey())
            {
                // OAEP allows for multiple hashing algorithms, what was formermly just "OAEP" is
                // now OAEP-SHA1.
                return rsa.Encrypt(userData, RSAEncryptionPadding.OaepSHA1);
            }
        }
    
        public byte[] Unprotect(byte[] protectedData)
        {
            // GetRSAPrivateKey returns an object with an independent lifetime, so it should be
            // handled via a using statement.
            using (RSA rsa = cert.GetRSAPrivateKey())
            {
                return rsa.Decrypt(protectedData, RSAEncryptionPadding.OaepSHA1);
            }
        }
    }
    

    客户网站重置:

    var provider = new CertificateProtectionProvider();
    var protector = provider.Create("ResetPassword");
    
    userManager.UserTokenProvider = new CertificateProtectorTokenProvider<ApplicationUser, int>(protector);
    
    if (!await userManager.UserTokenProvider.ValidateAsync("ResetPassword", model.Token, UserManager, user))
    {
        return GetErrorResult(IdentityResult.Failed());
    }
    
    var result = await userManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword);
    

    后台:

    var createdUser = userManager.FindByEmail(newUser.Email);
    
    var provider = new CertificateProtectionProvider();
    var protector = provider.Create("ResetPassword");
    
    userManager.UserTokenProvider = new CertificateProtectorTokenProvider<ApplicationUser, int>(protector);
    var token = userManager.GeneratePasswordResetToken(createdUser.Id);
    

    更多关于普通DataProtectorTokenProvider&lt;TUser, TKey&gt; 工作原理的信息:

    https://stackoverflow.com/a/53390287/3850405

    【讨论】:

      猜你喜欢
      • 2013-02-19
      • 1970-01-01
      • 1970-01-01
      • 2012-03-29
      • 2013-04-09
      • 1970-01-01
      • 2014-11-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多