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

보호기를 만들 때 하나 이상의 용도 문자열을 제공해야 합니다. 용도 문자열은 소비자 간에 격리를 제공합니다. 예를 들어 “녹색”의 용도 문자열로 만든 보호기는 “자주색” 용도의 보호기에서 제공하는 데이터를 보호 해제할 수 없습니다.

IDataProtectionProviderIDataProtector의 인스턴스는 여러 호출자에 대해 스레드로부터 안전합니다. 구성 요소가 CreateProtector에 대한 호출을 통해 IDataProtector에 대한 참조를 가져오면 ProtectUnprotect에 대한 여러 호출에 해당 참조를 사용합니다.

Unprotect에 대한 호출은 보호된 페이로드를 확인하거나 해독할 수 없는 경우 CryptographicException을 throw합니다. 일부 구성 요소는 보호 해제 작업 중에 오류를 무시하려고 할 수 있습니다. 인증 cookie를 읽는 구성 요소는 이 오류를 처리하고 요청을 완전히 실패하는 대신 cookie가 전혀 없는 것처럼 처리할 수 있습니다. 이 동작을 원하는 구성 요소는 모든 예외를 발생시키는 대신 특별히 CryptographicException을 catch해야 합니다.

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