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!
 */

プロテクターを作成する場合は、1 つ以上の目的文字列を指定する必要があります。 目的文字列により、コンシューマー間が分離されます。 たとえば、"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();
    }
}