在 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();
    }
}