Erste Schritte mit den Datenschutz-APIs in ASP.NET Core

Der Schutz von Daten umfasst im Wesentlichen die folgenden Schritte:

  1. Erstellen Sie anhand eines Datenschutzanbieters eine Datenschutzvorrichtung.
  2. Rufen Sie die Methode Protect mit den Daten auf, die Sie schützen möchten.
  3. Rufen Sie die Methode Unprotect mit den Daten auf, die Sie wieder in Klartext konvertieren möchten.

Die meisten Frameworks und App-Modelle, wie ASP.NET Core oder SignalR, konfigurieren das Datenschutzsystem bereits und fügen es einem Dienstcontainer hinzu, auf den per Abhängigkeitsinjektion zugegriffen wird. Das nachstehende Beispiel veranschaulicht Folgendes:

  • Konfigurieren eines Dienstcontainers für Abhängigkeitsinjektion und Registrieren des Datenschutzstapels
  • Empfang des Datenschutzanbieters über die Abhängigkeitsinjektion (Dependency Injection, DI)
  • Erstellen einer Schutzvorrichtung
  • Schützen und Aufheben des Schutzes von Daten
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!
 */

Wenn Sie eine Schutzvorrichtung erstellen, müssen Sie mindestens eine Zweckzeichenfolge angeben. Eine Zweckzeichenfolge sorgt für eine Isolation zwischen den Consumern. Eine Schutzvorrichtung, die mit der Zweckzeichenfolge „green“ erstellt wurde, kann beispielsweise nicht den Schutz von Daten aufheben, die von einer Schutzvorrichtung mit der Zweckzeichenfolge „purple“ bereitgestellt wurden.

Tipp

Die Instanzen von IDataProtectionProvider und IDataProtector sind threadsicher für mehrere Aufrufer. Es ist beabsichtigt, dass eine Komponente, die über einen Aufruf von CreateProtector einen Verweis auf einen IDataProtector erhält, diesen Verweis für mehrere Aufrufe von Protect und Unprotect verwendet.

Ein Aufruf von Unprotect führt zu einer CryptographicException, wenn die geschützten Nutzdaten nicht verifiziert oder entschlüsselt werden können. Für einige Komponenten kann es sinnvoll sein, Fehler beim Aufheben des Schutzes zu ignorieren. Eine Komponente, die Authentifizierungs-cookies liest, könnte diesen Fehler umgehen, indem die Anforderung so behandelt wird, als ob sie gar keine cookies enthielte, anstatt die Anforderung ganz abzulehnen. Komponenten, für die dieses Verhalten gewünscht wird, sollten speziell CryptographicExceptions abfangen, anstatt alle Ausnahmen zu verarbeiten.

Verwenden von AddOptions zum Konfigurieren eines benutzerdefinierten Repositorys

Betrachten Sie den folgenden Code, der einen Dienstanbieter verwendet, weil die Implementierung von IXmlRepository eine Abhängigkeit von einem Singletondienst aufweist:

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

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

Der vorstehende Code protokolliert die folgende Warnung:

Der Aufruf von 'BuildServiceProvider' aus dem Anwendungscode führt dazu, dass eine zusätzliche Kopie der Singletondienste erstellt wird. Verwenden Sie Alternativen wie Dependency-Injection-Dienste als Parameter für Configure.

Der folgende Code stellt die IXmlRepository-Implementierung bereit, ohne dass Sie den Dienstanbieter erstellen und somit zusätzliche Kopien von Singletondiensten erstellen müssen:

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

Der vorstehende Code entfernt den Aufruf von GetService und blendet IConfigureOptions<T> aus.

Der folgende Code zeigt das benutzerdefinierte XML-Repository:

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

Der folgende Code zeigt die XmlKey-Klasse:

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

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