Introduzione alle API di protezione dei dati in ASP.NET Core

In pratica, la protezione dei dati è costituita dai passaggi seguenti:

  1. Creare una protezione dati da un provider di protezione dati.
  2. Chiamare il Protect metodo con i dati da proteggere.
  3. Chiamare il Unprotect metodo con i dati da ripristinare in testo normale.

La maggior parte dei framework e dei modelli di app, ad esempio ASP.NET Core o SignalR, configura già il sistema di protezione dei dati e lo aggiunge a un contenitore di servizi a cui si accede tramite l'inserimento delle dipendenze. L'esempio seguente illustra:

  • Configurazione di un contenitore del servizio per l'inserimento delle dipendenze e la registrazione dello stack di protezione dei dati.
  • Ricezione del provider di protezione dei dati tramite inserimento delle dipendenze.
  • Creazione di una protezione.
  • Protezione dei dati che quindi non proteggono.
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!
 */

Quando si crea una protezione, è necessario specificare una o più stringhe di scopo. Una stringa di scopo fornisce l'isolamento tra i consumer. Ad esempio, una protezione creata con una stringa di scopo "verde" non sarebbe in grado di rimuovere la protezione dei dati forniti da una protezione con lo scopo "viola".

Suggerimento

Le istanze di IDataProtectionProvider e IDataProtector sono thread-safe per più chiamanti. È previsto che una volta che un componente ottiene un riferimento a un oggetto IDataProtector tramite una chiamata a CreateProtector, userà tale riferimento per più chiamate a Protect e Unprotect.

Una chiamata a Unprotect genererà CryptographicException se il payload protetto non può essere verificato o decifrato. Alcuni componenti potrebbero voler ignorare gli errori durante le operazioni di rimozione della protezione; un componente che legge l'autenticazione cookiepotrebbe gestire questo errore e considerare la richiesta come se non avesse affatto esito cookie negativo, anziché interrompere la richiesta. I componenti che vogliono questo comportamento devono intercettare in modo specifico CryptographicException anziché ingoiare tutte le eccezioni.

Usare AddOptions per configurare un repository personalizzato

Si consideri il codice seguente che usa un provider di servizi perché l'implementazione di IXmlRepository ha una dipendenza da un servizio singleton:

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

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

Il codice precedente registra l'avviso seguente:

La chiamata a 'BuildServiceProvider' dal codice dell'applicazione comporta la creazione di una copia aggiuntiva dei servizi singleton. Prendere in considerazione alternative, ad esempio l'inserimento di servizi come parametri in 'Configure'.

Il codice seguente fornisce l'implementazione IXmlRepository senza dover compilare il provider di servizi e quindi eseguire copie aggiuntive dei servizi 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();
}

Il codice precedente rimuove la chiamata a GetService e nasconde IConfigureOptions<T>.

Il codice seguente illustra il repository XML personalizzato:

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

Il codice seguente illustra la classe XmlKey:

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

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