Protegendo Configurações no ASP.NET

Quando construímos uma aplicação, temos diversos itens para realizar a configuração. Entre estes itens, estão configurações que guiam a execução da aplicação, o gerenciamento de algum recurso que foi incorporado a ela ou simplesmente valores que são necessários para o negócio para qual a mesma está sendo construída.

Entre estas configurações, temos strings de conexões com base de dados, endereços de servidor SMTP para envio de e-mails, chave de identificação para serviços de autenticação (Google, Facebook, etc.), etc. Não é comum manter estes valores em hard-code, pois pode mudar a qualquer momento e variar de ambiente para ambiente, e precisamos ter a flexibilidade de alterar sem a necessidade de recompilar a aplicação.

Isso nos obriga a manter a chave em um arquivo de configuração. Até então trabalhamos com o arquivo chamado web.config, que serve justamente para incluir diversas configurações da aplicação. A nova versão do ASP.NET (5) muda isso, disponibilizando um novo sistema de configuração mais simples e não menos poderoso. Apesar de continuar dando suporte ao formato XML, a versão mais recente adotou como padrão o formato JSON.

O problema desse modelo é a exposição das configurações. Quando trabalhamos com projetos e que podemos expor o repositório do código publicamente (através do GitHub ou qualquer outro gerenciador de código fonte), deixar armazenado neste arquivo informações sigilosas e, consequentemente, torna-las públicas, é algo que nem sempre desejamos. O ASP.NET 5 resolve isso através de um recurso chamado de User Secrets.

Esta funcionalidade permite externalizar certas configurações em um arquivo que fica de fora do controle de versão do código fonte. Sendo assim, para as chaves que são colocadas no arquivo de configuração da aplicação (config.json) e que precisam ser protegidas, podemos recorrer à este recurso, que basicamente consiste em criar um novo arquivo (chamado de secrets.json) e que será armazenado, no Windows, no seguinte endereço: %APPDATA%microsoftUserSecrets<userSecretsId>secrets.json.

É importante dizer que o local deste arquivo varia de acordo com o sistema operacional onde a aplicação está sendo desenvolvida, e não é aconselhável tornar a aplicação dependente dele; futuramente a Microsoft se dá o direito de mudar (como, por exemplo, criptografar o seu conteúdo), e a aplicação corre o risco de parar de funcionar. O <userSecretsId> é substituído pela chave que é colocada no arquivo project.json, e dentro deste diretório teremos o arquivo secrets.json, conforme comentado acima. É importante notar também que precisamos adicionar a dependência para usar este recurso, e para isso, devemos incluir o pacote chamado Microsoft.Framework.Configuration.UserSecrets:

{
  “commands”: {
    “web”: “Microsoft.AspNet.Hosting –config hosting.ini”
  },
  “dependencies”: {
    “Microsoft.AspNet.Server.IIS”: “1.0.0-beta5”,
    “Microsoft.AspNet.Server.WebListener”: “1.0.0-beta5”,
    “Microsoft.Framework.Configuration.UserSecrets”: “1.0.0-beta5”
  },
  “userSecretsId”: “WebApplication1-12345”,
  “version”: “1.0.0-*”,
  “webroot”: “wwwroot”
}

Com essa configuração realizada, podemos adicionar no arquivo config.json todas as configurações necessárias para o funcionamento da aplicação e incluir no arquivo secrets.json somente as configurações que queremos proteger.

[config.json]
{
  “Data”: {
    “DefaultConnection”: {
      “ConnectionString”: “Server=(localdb)\MSSQLLocalDB;Database=Teste;Trusted_Connection=True;”
    }
  },
  “Certificado”:  “123”
}

[secrets.json]
{
  “Certificado”:  “128372478278473248378”
}

Opcionalmente você pode recorrer à uma opção que existe se clicar com o botão direito sobre o projeto e, em seguida, em “Manage User Secrets“, e assim ter acesso ao arquivo secrets.json sem a necessidade de saber onde o Visual Studio está armazenando-o. Mas isso não basta para o ASP.NET entender que ele deve, também, considerar a busca no arquivo secrets.json. Para isso precisamos indicar que à ele que vamos utilizar o recurso de User Secrets, conforme podemos ver abaixo:

public class Startup
{
    public void Configure(IApplicationBuilder app, IApplicationEnvironment appEnv)
    {
        this.Configuration =
            new ConfigurationBuilder(appEnv.ApplicationBasePath)
                .AddJsonFile(“config.json”)
                .AddUserSecrets()
                .Build();

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync(this.Configuration[“Certificado”]);
        });
    }

    public IConfiguration Configuration { get; set; }
}

A ordem em que invocamos o método é importante, ou seja, neste caso se ele encontrar a chave “Certificado” no arquivo secrets.json o seu valor será apresentado. Caso o valor não exista, então ele extrairá o valor do arquivo config.json.

Configuração dinâmica no WCF

Existem duas formas de configurar um serviço WCF: imperativa e declarativa. A primeira opção consiste em criar explicitamente a classe ServiceHost, configurá-la e gerenciar a sua execução, e tudo isso é feito diretamente através da linguagem (C# e VB.NET). Já no modelo declarativo, recorremos ao arquivo de configuração (App.config ou Web.config), onde tudo é configurado dentro deles, o que te dá uma maior flexibilidade, já que te permite alterar mesmo depois de instalado no servidor.

Quando queremos algum dinamismo na configuração do serviço, podemos recorrer a primeira opção. Mas quando estamos hospedando o serviço em um projeto Web, através de um arquivo *.svc, a construção da classe ServiceHost é criada automaticamente pelo WCF/ASP.NET.

Nestes casos, para conseguir atingir o nosso objetivo, devemos construir uma factory customizada, e para isso, precisamos criar uma classe herdando de ServiceHostFactory, sobrescrever o método CreateServiceHost, onde dentro dele, criaremos o ServiceHost de acordo com nossa necessidade e, finalmente, acoplamos  à execução através da diretiva @ServiceHost no arquivo *.svc. Mais detalhes aqui.

Na versão 4.5 do WCF, a Microsoft facilitou isso, sem a necessidade de fazer várias customizações e configurações. Basicamente colocamos na classe que representa o serviço, um método estático chamado Configure, que receberá como parâmetro a instância da classe ServiceConfiguration, e como o próprio nome diz, corresponde as configurações daquele serviço, e que será aplicado ao ServiceHost. O código abaixo ilustra este novo recurso. Isso irá eliminar tudo aquilo que fazíamos até então, para conseguir customizar e/ou gerar a configuração para os serviços que são hospedados no IIS.

public class Servico : IContrato
{
    public static void Configure(ServiceConfiguration config)
    {
        config.Description.Behaviors.Add(
            new ServiceMetadataBehavior() { HttpGetEnabled = true });

        config.Description.Behaviors.Add(
            new ServiceDebugBehavior() { IncludeExceptionDetailInFaults = true });
    }

    public string Ping(string value)
    {
        return value + ” ping”;
    }
}

SingleWSDL no WCF

Como o WCF é fortemente baseado em SOAP, ele fornece várias funcionalidades para lidar com os documentos WSDL, que são documentos que descrevem as funcionalidades e os tipos de dados utilizados por um determinado serviço. Com este arquivo, podemos referenciá-lo nas aplicações clientes, para que elas possam construir toda a infraestrutura necessária (proxy) para realizar a comunicação.

Só que o documento WSDL gerado pelo WCF contém em seu interior, os detalhes referente as funcionalidades que são expostas pelo serviço. Já as operações podem receber e/ou retornar vários tipos de dados, que devem também estar contemplados no documento. Só que ao invés disso, toda a estrutura dos tipos utilizada pelas operações do serviço, é colocada em arquivos externos (XSD), e o WSDL apenas os referencia.

Ao consumir estes tipos de documentos dentro do próprio .NET, não há problemas, pois ele consegue facilmente importar o documento WSDL e resolver essas dependências. O problema começa a aparecer quando consumimos este WSDL em outras tecnologias, que exigem que todas as definições do serviço estejam dentro de um mesmo arquivo, não lidando com essa modularização. Por estas questões de interoperabilidade, temos que recorrer à algumas implementações customizadas para gerar o WSDL neste formato. Uma das opções mais conhecidas é o FlatWSDL, criada pelo Christian Weyer, já há algum tempo.

Já na versão 4.5 do WCF, a Microsoft já trará isso nativamente, ou seja, a criação do serviço continua a mesma, só que podemos acessar o endereço que expõe o WSDL, anexando na querystring o sufixo ?singleWsdl, o que fará com que o documento WSDL gerado, incluia toda a informação necessária, sem referências para arquivos externos. Abaixo temos uma imagem quando acessamos este serviço no navegador. Note que há um link na página que já aponta para este novo recurso.

Nome da Aplicação na ConnectionString

Muitas vezes temos várias aplicações que consomem o mesmo banco de dados. Cada uma dessas aplicações atuam em uma porção de dados, realizando uma tarefa específica. Nestes casos, é muito comum eles precisarem dos mesmos dados, e não há nada errado nisso.

Como há várias aplicações penduradas em cima de uma mesma base de dados, utilizando a mesma string de conexão, e na maioria das vezes, o mesmo usuário e senha, é difícil monitorar as requisições que estão sendo executadas no SQL Server, pois fica difícil identificar qual a origem (aplicação) da consulta/comando.

O Activity Monitor do SQL Server, ferramenta que nos permite monitorar em tempo real as conexões que estão abertas no momento, fornece várias colunas que detalham as informações de um processo específico. Entre essas colunas, temos uma chamada Application. Essa coluna, na configuração padrão da string de conexão para o SQL Server, sempre exibirá .Net Sql Client Data Provider.

Você pode customizar isso, através do atributo Application Name, que pode ser colocado na própria string de conexão, assim como podemos visualizar no código abaixo. Isso pode variar de acordo com cada aplicação, descrevendo para o monitor qual a aplicação que está aguardando ou executando o comando/consulta, independentemente se estiver utilizando um ORM ou não.

Initial Catalog=DBTeste;Data Source=.;Integrated Security=true;Application Name=Aplicacao01

Finalmente, depois dessa configuração devidamente realizada, se começarmos a realizar chamadas para o banco de dados SQL Server, essa configuração será levada até ele, e ao abrir a console de monitoramento, podemos visualizar tal informação, e rapidamente identificar qual é a aplicação que está realizando-a. A imagem abaixo ilustra a configuração em ação: