跨 Asp.NET Core 和 Framework 的数据保护提供程序(生成密码重置链接)
Posted
技术标签:
【中文标题】跨 Asp.NET Core 和 Framework 的数据保护提供程序(生成密码重置链接)【英文标题】:Data Protection provider across Asp.NET Core and Framework (generate password reset link) 【发布时间】:2018-07-29 16:28:52 【问题描述】:我遇到了与 DataProtectionProvider 相关的问题,最初我们只有 2 个 .NET Framework 项目,现在添加了一个 .NET Core 项目,这让我很困惑如何执行以下操作:从.NET Framework 项目,并在 .NET Core 项目中使用它。两者都使用相同的数据库和用户表,它们已经相互兼容。 .NET Framework 仍然是 Code-First 数据库生成的领先项目。
在这两个 .NET Frameworks 项目中,我使用了一个共享代码库,其代码如下:
//not sure where I got this from but it is part of the solution for solving
//password link generating and using in two different applications.
public class MachineKeyProtectionProvider : IDataProtectionProvider
public IDataProtector Create(params string[] purposes)
return new MachineKeyDataProtector(purposes);
public class MachineKeyDataProtector : IDataProtector
private readonly string[] _purposes;
public MachineKeyDataProtector(string[] purposes)
_purposes = purposes;
public byte[] Protect(byte[] userData)
return MachineKey.Protect(userData, _purposes);
public byte[] Unprotect(byte[] protectedData)
return MachineKey.Unprotect(protectedData, _purposes);
然后在用户存储库中:
private readonly UserManager<ApplicationUser> _userManager = null;
private readonly RoleManager<IdentityRole> _roleManager = null;
internal static IDataProtectionProvider DataProtectionProvider get; private set;
public UserRepository(DatabaseContext dbContext)
_userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(dbContext));
_roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(dbContext));
_userManager.UserValidator = new UserValidator<ApplicationUser>(_userManager) AllowOnlyAlphanumericUserNames = false ;
if (DataProtectionProvider == null)
DataProtectionProvider = new MachineKeyProtectionProvider();
_userManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, string>(DataProtectionProvider.Create("Identity"));
在两个 .NET Framework 项目中,我都有一个 <machineKey>
集。之后我可以简单地使用:
public string GeneratePasswordResetCode(string userId)
return _userManager.GeneratePasswordResetToken(userId);
public void ChangeUserPassword(string oldPassword, string newPassword)
string id = InfrastructureUserHelper.User.GetUserId();
IdentityResult result = _userManager.ChangePassword(id, oldPassword, newPassword);
...
所以,现在添加了一个 .NET Core 项目,它已经拥有自己的密码重置机制,但所有自动化作业都是从 .NET Framework 项目之一发送的。原因是用户应该为自动创建的帐户设置密码。
我该怎么做? 我一直在看这个: https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?tabs=aspnetcore2x 还有这个:How to implement machineKey in ASP.NET Core 2.0
但我无法真正弄清楚简单易行的解决方案是什么。我宁愿避免创建额外的 redis 服务器。类似于机器密钥的东西可以完成这项工作。 我尝试从 this documentation 开始,但我无法真正弄清楚 .NET Core 项目中的哪些部分以及 .NET Framework 项目中的哪些部分。
到目前为止,我已经尝试过玩这部分但没有运气:
public void ConfigureServices(IServiceCollection services)
services.AddDataProtection().SetApplicationName("Identity")
.SetDefaultKeyLifetime(TimeSpan.FromDays(60))
.ProtectKeysWithDpapi();
services.AddIdentity<ApplicationUser, ApplicationRole>().AddDefaultTokenProviders();
编辑: 看到这个页面,我最终可以使用我们的 Blob 存储: https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?tabs=aspnetcore2x
添加这似乎有点帮助,两个应用程序都运行良好,但仍然在 .NET Framework 项目中没有使用正确的 DataProtectionProvider。 UserManager 找不到它(没有 ITokenProvider 错误)。
最终我有点放弃并将令牌存储在用户数据库中,这完全不理想,但我花了很多时间试图解决一些没有记录的问题。 -1 代表微软。
【问题讨论】:
像“no ITokenProvider”这样的错误听起来更像是一个没有为依赖注入正确注册的服务。 【参考方案1】:数据保护 API 在 .NET Framework 和 .NET Core 中的工作方式相同。
使用 X509 证书而不是旧的机器密钥方法对密钥进行加密,而不是机器密钥。自行生成的证书很好,因为加密仅供内部使用。
重要提示:如果您控制服务器,则证书必须安装在证书存储中。使用接受 X509 证书实例的 ProtectWithCertificate
重载有一个荒谬且未记录的问题:它只能使用该实例来加密。如果证书不在商店中,则解密失败。微软声称这是“底层框架”的一些限制,不管这意味着什么,但可以使用变通方法(而且并不复杂)。我在 this 上使用了一个变体,并且证书被序列化到 Azure Key Vault,这样我们就不必接触每个单独的服务器。
您还需要指定 DPAPI 持久性选项之一,以确保所有服务器都可以访问数据。由于我们使用的是 Azure,我下面的代码使用 blob 存储,但如果您运行自己的服务器,这可能是通过 PersistKeysToFileSystem
进行的网络共享。
我在ConfigureServices
中的设置如下所示:
var x509 = GetDpApiCert(); // library utility
var container = GetBlobStorageRef(); // library
services.AddDataProtection()
.SetApplicationName(appconfig["DpapiSiteName"])
.ProtectKeysWithProvidedCertificate(x509)
.PersistKeysToAzureBlobStorage(container, appconfig["DpapiFileName"]);
这是我生成证书的 Powershell 脚本:
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$password = "",
[Parameter(Mandatory=$true)][string]$rootDomain = ""
)
$cwd = Convert-Path .
$CerFile = "$cwd\aspnet_dpapi.cer"
$PfxFile = "$cwd\aspnet_dpapi.pfx"
# abort if files exist
if((Test-Path($PfxFile)) -or (Test-Path($CerFile)))
Write-Warning "Failed, aspnet_dpapi already exists in $cwd"
Exit
$cert = New-SelfSignedCertificate `
-Subject $rootDomain `
-DnsName $rootDomain `
-FriendlyName "ASP.NET Data Protection $rootDomain" `
-NotBefore (Get-Date) `
-NotAfter (Get-Date).AddYears(10) `
-CertStoreLocation "cert:CurrentUser\My" `
-KeyAlgorithm RSA `
-Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" `
-KeyLength 2048 `
-KeyUsage KeyEncipherment, DataEncipherment
# -HashAlgorithm SHA256 `
# -Type Custom,DocumentEncryptionCert `
# -TextExtension @("2.5.29.37=text1.3.6.1.5.5.7.3.1")
$store = 'Cert:\CurrentUser\My\' + ($cert.ThumbPrint)
$securePass = ConvertTo-SecureString -String $password -Force -AsPlainText
Export-Certificate -Cert $store -FilePath $CerFile
Export-PfxCertificate -Cert $store -FilePath $PfxFile -Password $securePass
【讨论】:
我不控制服务器,它们是 Azure 中的应用服务。我已经尝试将它与 Blob 存储一起使用,该存储更容易且可运行,但 .NET Framework 仍然无法使用该数据保护提供程序(请参阅更新)。另外我想知道“目的”在哪里设置。 “目的”是您传递给SetApplicationName
的内容。将您的证书序列化到密钥库很容易,我在this 答案中展示了如何检索它们,并且在同一问题上接受的答案展示了如何存储它们。 Key Vault 非常便宜,但您需要缓存证书。
更新:ASP.NET Core 2.1.0 将更正不使用提供的证书进行解密的问题。同时,我一直在使用带有 2.0 的 this 的变体来实现相同的目标。以上是关于跨 Asp.NET Core 和 Framework 的数据保护提供程序(生成密码重置链接)的主要内容,如果未能解决你的问题,请参考以下文章
2021-06-22 .NET高级班 60-ASP.NET Core 解决跨域问题(工具版)
无法在 ASP.NET Core 1.0 中启用跨域请求 (CORS)
asp.net core 系列之允许跨域访问2之测试跨域(Enable Cross-Origin Requests:CORS)