Cenários sem reconhecimento de DI para a Proteção de Dados no ASP.NET Core

De Rick Anderson

O sistema de proteção de dados do ASP.NET Core normalmente é adicionado a um contêiner de serviço e consumido por componentes dependentes por meio de DI (injeção de dependência). No entanto, há casos em que isso não é viável ou desejado, especialmente ao importar o sistema para um aplicativo existente.

Para dar suporte a esses cenários, o pacote Microsoft.AspNetCore.DataProtection.Extensions fornece um tipo concreto, DataProtectionProvider, que oferece uma maneira simples de usar a proteção de dados sem depender da DI. O tipo DataProtectionProvider implementa IDataProtectionProvider. A construção de DataProtectionProvider requer apenas o fornecimento de uma instância DirectoryInfo para indicar onde as chaves criptográficas do provedor devem ser armazenadas, conforme visto no exemplo de código a seguir:

using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;

public class Program
{
    public static void Main(string[] args)
    {
        // Get the path to %LOCALAPPDATA%\myapp-keys
        var destFolder = Path.Combine(
            System.Environment.GetEnvironmentVariable("LOCALAPPDATA"),
            "myapp-keys");

        // Instantiate the data protection system at this folder
        var dataProtectionProvider = DataProtectionProvider.Create(
            new DirectoryInfo(destFolder));

        var protector = dataProtectionProvider.CreateProtector("Program.No-DI");
        Console.Write("Enter input: ");
        var input = Console.ReadLine();

        // Protect the payload
        var protectedPayload = protector.Protect(input);
        Console.WriteLine($"Protect returned: {protectedPayload}");

        // Unprotect the payload
        var unprotectedPayload = protector.Unprotect(protectedPayload);
        Console.WriteLine($"Unprotect returned: {unprotectedPayload}");

        Console.WriteLine();
        Console.WriteLine("Press any key...");
        Console.ReadKey();
    }
}

/*
 * SAMPLE OUTPUT
 *
 * Enter input: Hello world!
 * Protect returned: CfDJ8FWbAn6...ch3hAPm1NJA
 * Unprotect returned: Hello world!
 *
 * Press any key...
*/

Por padrão, o tipo concreto DataProtectionProvider não criptografa a matéria-prima da chave antes de mantê-la no sistema de arquivos. Isso é para dar suporte a cenários em que o desenvolvedor aponta para um compartilhamento de rede e o sistema de proteção de dados não consegue deduzir automaticamente um mecanismo de criptografia de chave em rest apropriado.

Além disso, o tipo concreto DataProtectionProvider não isola aplicativos por padrão. Todos os aplicativos que usam o mesmo diretório de chave podem compartilhar conteúdo desde que os parâmetros de finalidade correspondam.

O construtor DataProtectionProvider aceita um retorno de chamada de configuração opcional que pode ser usado para ajustar os comportamentos do sistema. O exemplo abaixo demonstra a restauração do isolamento com uma chamada explícita para SetApplicationName. O exemplo também demonstra como configurar o sistema para criptografar automaticamente as chaves persistentes usando o DPAPI do Windows. Se o diretório apontar para um compartilhamento UNC, talvez você queira distribuir um certificado compartilhado em todos os computadores relevantes e configurar o sistema para usar a criptografia baseada em certificado com uma chamada para ProtectKeysWithCertificate.

using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;

public class Program
{
    public static void Main(string[] args)
    {
        // Get the path to %LOCALAPPDATA%\myapp-keys
        var destFolder = Path.Combine(
            System.Environment.GetEnvironmentVariable("LOCALAPPDATA"),
            "myapp-keys");

        // Instantiate the data protection system at this folder
        var dataProtectionProvider = DataProtectionProvider.Create(
            new DirectoryInfo(destFolder),
            configuration =>
            {
                configuration.SetApplicationName("my app name");
                configuration.ProtectKeysWithDpapi();
            });

        var protector = dataProtectionProvider.CreateProtector("Program.No-DI");
        Console.Write("Enter input: ");
        var input = Console.ReadLine();

        // Protect the payload
        var protectedPayload = protector.Protect(input);
        Console.WriteLine($"Protect returned: {protectedPayload}");

        // Unprotect the payload
        var unprotectedPayload = protector.Unprotect(protectedPayload);
        Console.WriteLine($"Unprotect returned: {unprotectedPayload}");

        Console.WriteLine();
        Console.WriteLine("Press any key...");
        Console.ReadKey();
    }
}

Dica

Instâncias do tipo concreto DataProtectionProvider são caras de criar. Se um aplicativo mantiver várias instâncias desse tipo e se todas estiverem usando o mesmo diretório de armazenamento de chaves, o desempenho do aplicativo poderá ser prejudicado. Se você usar o tipo DataProtectionProvider, recomendamos que crie esse tipo uma vez e reutilize-o o máximo possível. O tipo DataProtectionProvider e todas as instâncias IDataProtector criadas com base nele são thread-safe para vários chamadores.