代码之家  ›  专栏  ›  技术社区  ›  Ogglas

Asp.Net标识从一个应用程序池标识生成密码重置令牌,并在另一个应用程序池标识上进行验证

  •  4
  • Ogglas  · 技术社区  · 7 年前

    我们有一个面向客户的网站和一个创建用户的后台办公室。在我们的开发人员机器上的IIS Express上运行这两个应用程序时,使用带有密码重置的欢迎电子邮件创建一个新用户可以完美地工作。但是,当我们部署应用程序并且应用程序托管在具有不同应用程序池标识的不同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 错误,但没有错误代码。我们是否需要自己实现令牌生成和验证?

    enter image description here

    1 回复  |  直到 7 年前
        1
  •  5
  •   Ogglas    7 年前

    MachineKey 在同一台服务器上。我希望它能够跨越不同的服务器和用户。

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

    ValidateAsync 借助于 DataProtectionTokenProvider.cs 用于ASP.NET核心标识。这门课真的帮我找到了解决办法。

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

    我最后犯了以下错误:

    密钥在指定状态下无效,无法使用。

    enter image description here

    SecurityStamp 使用时 DataProtectorTokenProvider<TUser, TKey> Application Pool Identity 在单个服务器上的更改表示实际的保护机制如下所示:

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

    如果所有站点都使用相同的 应用程序池标识 他也指出了这一点。也可能是 DataProtectionProvider 具有 protectionDescriptor "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托管的,所有进程都是 作为 .

    创建方法的目的如下所述:

    用于确保受保护数据的附加熵可能仅限于 为了正确的目的而不受保护。

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

    鉴于这些信息,我认为在尝试使用 Microsoft .

    我最终实现了我自己的 IUserTokenProvider<TUser, TKey> , IDataProtectionProvider IDataProtector

    我选择实施 IDataProtector 使用证书,因为我可以相对轻松地在服务器之间传输这些证书。我也可以从网上取 X509Store 它运行网站,因此应用程序本身不存储任何密钥。

    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<TUser,TKey> 作品:

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