跨Asp.NET Core和Framework的数据保护提供程序(生成密码重置链接)

时间:2018-02-19 12:21:54

标签: c# asp.net asp.net-core asp.net-identity data-protection

我遇到了与DataProtectionProvider相关的问题,首先我们只有2个.NET Framework项目,现在添加了一个.NET Core项目,这让我感到困惑,如何执行以下操作:生成密码重置链接来自.NET Framework项目,并在.NET Core项目中使用它。两者都使用相同的数据库和用户表,这些表已经相互兼容。 .NET Framework仍然是Code-First数据库生成的主要项目。

在两个.NET框架项目中,我使用一个共享代码库,其代码如下:

//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);
    }
}

然后在User Repository中:

    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表示微软。

1 个答案:

答案 0 :(得分:8)

Data Protection 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={text}1.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