ASP.NET Core 中的数据保护 API 入门

数据保护过程主要包括以下步骤:

  1. 从数据保护提供程序创建数据保护程序。
  2. 对要保护的数据调用 Protect 方法。
  3. 对要恢复为纯文本的数据调用 Unprotect 方法。

大多数框架和应用模型(例如 ASP.NET Core 或 SignalR)已经配置了数据保护系统,并将其添加到通过依赖项注入访问的服务容器。 以下示例演示:

  • 为依赖项注入配置服务容器并注册数据保护堆栈。
  • 通过 DI 接收数据保护提供程序。
  • 创建保护程序。
  • 保护然后取消保护数据。
using System;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;

public class Program
{
    public static void Main(string[] args)
    {
        // add data protection services
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddDataProtection();
        var services = serviceCollection.BuildServiceProvider();

        // create an instance of MyClass using the service provider
        var instance = ActivatorUtilities.CreateInstance<MyClass>(services);
        instance.RunSample();
    }

    public class MyClass
    {
        IDataProtector _protector;

        // the 'provider' parameter is provided by DI
        public MyClass(IDataProtectionProvider provider)
        {
            _protector = provider.CreateProtector("Contoso.MyClass.v1");
        }

        public void RunSample()
        {
            Console.Write("Enter input: ");
            string input = Console.ReadLine();

            // protect the payload
            string protectedPayload = _protector.Protect(input);
            Console.WriteLine($"Protect returned: {protectedPayload}");

            // unprotect the payload
            string unprotectedPayload = _protector.Unprotect(protectedPayload);
            Console.WriteLine($"Unprotect returned: {unprotectedPayload}");
        }
    }
}

/*
 * SAMPLE OUTPUT
 *
 * Enter input: Hello world!
 * Protect returned: CfDJ8ICcgQwZZhlAlTZT...OdfH66i1PnGmpCR5e441xQ
 * Unprotect returned: Hello world!
 */

创建保护程序时,必须提供一个或多个目标字符串。 目标字符串可隔离使用者。 例如,使用目标字符串“green”创建的保护程序将无法取消保护目标为“purple”的保护程序提供的数据。

提示

IDataProtectionProviderIDataProtector 的实例对于多个调用方来说是线程安全的。 其目的是,一旦组件通过调用 CreateProtector 获得对 IDataProtector 的引用,它将在多次调用 ProtectUnprotect 时使用该引用。

如果无法验证或解密受保护的有效负载,对 Unprotect 的调用将引发 CryptographicException。 某些组件可能希望在取消保护操作期间忽略错误;读取身份验证 cookie 的组件可能会处理此错误,并将请求视为根本没有 cookie,而不是让请求彻底失败。 需要此行为的组件应该专门捕获 CryptographicException,而不是吞并所有异常。

使用 AddOptions 配置自定义存储库

考虑以下使用服务提供程序的代码,因为 IXmlRepository 的实现依赖于单一实例服务:

public void ConfigureServices(IServiceCollection services)
{
    // ...

    var sp = services.BuildServiceProvider();
    services.AddDataProtection()
      .AddKeyManagementOptions(o => o.XmlRepository = sp.GetService<IXmlRepository>());
}

上述代码记录了以下警告:

从应用程序代码调用“BuildServiceProvider”会导致创建单一实例服务的其他副本。 考虑依赖项注入服务等替代项作为“Configure”的参数。

以下代码提供了 IXmlRepository 实现,而无需生成服务提供程序并因此创建单一实例服务的其他副本:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<DataProtectionDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));

    // Register XmlRepository for data protection.
    services.AddOptions<KeyManagementOptions>()
    .Configure<IServiceScopeFactory>((options, factory) =>
    {
        options.XmlRepository = new CustomXmlRepository(factory);
    });

    services.AddRazorPages();
}

上述代码删除了对 GetService 的调用并隐藏了 IConfigureOptions<T>

以下代码展示了自定义 XML 存储库:

using CustomXMLrepo.Data;
using Microsoft.AspNetCore.DataProtection.Repositories;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

public class CustomXmlRepository : IXmlRepository
{
    private readonly IServiceScopeFactory factory;

    public CustomXmlRepository(IServiceScopeFactory factory)
    {
        this.factory = factory;
    }

    public IReadOnlyCollection<XElement> GetAllElements()
    {
        using (var scope = factory.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<DataProtectionDbContext>();
            var keys = context.XmlKeys.ToList()
                .Select(x => XElement.Parse(x.Xml))
                .ToList();
            return keys;
        }
    }

    public void StoreElement(XElement element, string friendlyName)
    {
        var key = new XmlKey
        {
            Xml = element.ToString(SaveOptions.DisableFormatting)
        };

        using (var scope = factory.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<DataProtectionDbContext>();
            context.XmlKeys.Add(key);
            context.SaveChanges();
        }
    }
}

以下代码展示了 XmlKey 类:

public class XmlKey
{
    public Guid Id { get; set; }
    public string Xml { get; set; }

    public XmlKey()
    {
        this.Id = Guid.NewGuid();
    }
}