Bien démarrer avec les API de protection des données dans ASP.NET Core

Fondamentalement, la protection des données comprend les étapes suivantes :

  1. Créez un protecteur de données à partir d’un fournisseur de protection des données.
  2. Appelez la méthode Protect avec les données que vous souhaitez protéger.
  3. Appelez la méthode Unprotect avec les données que vous souhaitez convertir en texte brut.

La plupart des infrastructures et des modèles d’application, tels que ASP.NET Core ou SignalR, configurent déjà le système de protection des données et l’ajoutent à un conteneur de service accessible via l’injection de dépendances. L’exemple suivant illustre :

  • Configuration d’un conteneur de service pour l’injection de dépendances et l’inscription de la pile de protection des données.
  • Réception du fournisseur de protection des données via l’injonction de dépendances.
  • Création d’un protecteur.
  • Protéger puis supprimer la protection des données.
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!
 */

Lorsque vous créez un protecteur, vous devez fournir une ou plusieurs Chaînes d’objectif. Une chaîne d’objectif fournit une isolation entre les contrôles serveurs consommateurs. Par exemple, un logiciel de protection créé avec une chaîne d’objectif « verte » ne serait pas en mesure de supprimer la protection des données fournies par un logiciel de protection avec un objectif « violet ».

Conseil

Les instances de IDataProtectionProvider et IDataProtector sont thread-safe pour plusieurs appelants. Il est prévu qu’une fois qu’un composant obtient une référence à un IDataProtector via un appel vers CreateProtector, il utilise cette référence pour plusieurs appels vers Protect et Unprotect.

Un appel vers Unprotect lève CryptographicException si la charge utile protégée ne peut pas être vérifiée ou déchiffrée. Certains composants peuvent souhaiter d’ignorer les erreurs lors des opérations non-protégée ; un composant qui lit des authentifications cookiepeut gérer cette erreur et traiter la requête comme s’il n’y avait pas de cookie du tout au lieu d’échouer la demande. Les composants qui voudraient adopter ce comportement devraient spécifiquement intercepter les CryptographicException au lieu d'avaler toutes les exceptions.

Utiliser AddOptions pour configurer un référentiel personnalisé

Considérez le code suivant qui utilise un fournisseur de services, car l’implémentation de IXmlRepository a une dépendance sur un service singleton :

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

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

Le code précédent consigne l’avertissement suivant :

L’appel de « BuildServiceProvider » à partir du code d’application entraîne la création d’une copie supplémentaire des services singleton. Envisagez des alternatives telles que l’injection de dépendances de services en tant que paramètres pour « Configurer ».

Le code suivant fournit l’implémentation IXmlRepository sans avoir à créer le fournisseur de services et donc à effectuer des copies supplémentaires des services 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();
}

Le code précédent supprime l’appel vers GetService et masque IConfigureOptions<T>.

Le code suivant montre le référentiel XML personnalisé :

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

Le code suivant montre la classe XmlKey :

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

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