Introducción a las API de protección de datos en ASP.NET Core

Básicamente, la protección de datos consta de los pasos siguientes:

  1. Crear un protector de datos a partir de un proveedor de protección de datos.
  2. Llamar al método Protect con los datos que desea proteger.
  3. Llamar al método Unprotect con los datos que desea volver a convertir en texto sin formato.

La mayoría de los marcos y los modelos de aplicaciones, como ASP.NET Core o SignalR, ya configuran el sistema de protección de datos y lo agregan a un contenedor de servicios al que se accede a través de la inserción de dependencias. El ejemplo siguiente muestra:

  • Configurar un contenedor de servicios para la inserción de dependencias y registrar la pila de protección de datos.
  • Recibir el proveedor de protección de datos a través de la inserción de dependencias.
  • Crear un protector.
  • Proteger y desproteger datos.
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!
 */

Al crear un protector, debe proporcionar una o varias cadenas de propósito. Una cadena de propósito proporciona aislamiento entre los consumidores. Por ejemplo, un protector creado con una cadena de propósito "verde" no podría desproteger los datos proporcionados por un protector con el propósito "púrpura".

Sugerencia

Las instancias de IDataProtectionProvider y IDataProtector son seguras para subprocesos para varios autores de llamada. La intención es que una vez que un componente obtiene una referencia a un IDataProtector a través de una llamada a CreateProtector, utilizará esa referencia para múltiples llamadas a Protect y Unprotect.

Una llamada a Unprotect iniciará CryptographicException si la carga protegida no se puede comprobar ni descifrar. Algunos componentes pueden querer ignorar los errores durante las operaciones de desprotección; un componente que lee cookies de autenticación podría manejar este error y tratar la solicitud como si no tuviera cookie en lugar de rechazar la solicitud directamente. Los componentes que quieran este comportamiento deberían atrapar específicamente CryptographicException en lugar de tragarse todas las excepciones.

Usar AddOptions para configurar el repositorio personalizado

Tenga en cuenta el código siguiente que usa un proveedor de servicios porque la implementación de IXmlRepository tiene una dependencia en un servicio singleton:

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

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

El código anterior registra la siguiente advertencia:

Al llamar a "BuildServiceProvider" desde el código de aplicación, se crea una copia adicional de los servicios singleton. Considere alternativas como los servicios de inserción de dependencias como parámetros para "Configurar".

El código siguiente proporciona la implementación IXmlRepository sin tener que compilar el proveedor de servicios y, por tanto, realizar copias adicionales de servicios singleton:

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

El código anterior quita la llamada a GetService y oculta IConfigureOptions<T>.

El código siguiente muestra el repositorio XML personalizado:

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

En el código siguiente se muestra la clase XmlKey:

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

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