ASP.NET Page Modules

O Paulo Morgado desenvolveu um recurso bastante interessante que ele chamou de Page Modules. Trata-se de uma forma de interceptar o ciclo de vida de uma página ASP.NET e fazer algumas manipulações a nível de página.

O que achei mais interessante (e acredito que foi esse o motivo que levou o Paulo a construí-lo) é que o Page Modules não é limitado quando utilizamos uma forma diferente de redirecionamento/execução (Server.Transfer e Server.Execute) da página que, por várias vezes, somos obrigados a utilizar o Response.Redirect justamente por causa dessa “deficiencia”.

Performance, Segurança e Boas Práticas

Recentemente um amigo me procurou para dar algumas dicas se segurança e performance para melhorar de uma aplicação Web. Depois da ajuda, eu decidi disponibilizar essa listagem aqui no blog. Compilei algumas dicas e disponibilizei abaixo essa listagem, com algumas guidelines que julgo necessárias para quando for desenvolver e disponibilizar a aplicação em um servidor Web. É importante dizer que isso pode ou não ser importante, dependendo de qual o cenário da aplicação/empresa.

  • Performance
    • Desabilite a Session nas páginas que você não precisa dela. Isso pode ser realizado através do atributo EnableSessionState da diretiva @Page. Se não for utilizar na aplicação como um tudo, então desabilite a nível de aplicação, através do elemento enableSessionState no elemento do arquivo Web.Config.
    • Sempre defina para false a propriedade EnableViewState para quando não precisar do ViewState na página Web. Através do atributo maxPageStateFieldlength definido dentro do elemento , você tem a possibilidade de “quebrar” o campo __VIEWSTATE. Mas isso não traz melhora em termos de performance. Essa configuração é útil para quando alguns proxies e firewalls barram o acesso a determinadas páginas, quando ela, por sua vez, contém campos ocultos com um grande conteúdo.
    • Defina o atributo debug do elemento compilation para false. Quando definido como true em ambiente de produção, é um dos grandes vilões de performance. Se desejar que todas as aplicações do servidor Web obrigatoriamente estejam com este mesmo comportamento, voce pode definir para true o valor do atributo retail do elemento deployment do arquivo Machine.config do servidor Web.
    • Considere a utilização de OutputCache. Para facilitar a manutenção, utilize o CacheProfile.
    • Remova todos os módulos que não estão sendo utilizados pela aplicação.
    • Reduza a quantidade de round-trips entre o cliente e o servidor. Para isso, utilize sempre que possível o método Server.Transfer.
    • A compactação da resposta a ser enviada para o cliente é sempre uma boa opção. Além disso, remova caracteres desnecessários, como espaços em branco e TABs.
    • Utilize sempre DataReader para ter uma forma eficiente de carga de dados.
    • Paginar os resultados é quase sempre uma necessidade para exibição de dados.
    • Defina para true o atributo enableKernelOutputCache do elemento httpRuntime (acredito que isso já seja o padrão). Isso tem um benefício a nível do IIS, mas atente-se que, em alguns cenários, essa configuração pode trazer resultados inesperados.
    • Remova todos os HTTP Headers desnecessários. Quando você instala o .NET Framework, é criado um valor por padrão dentro do IIS, que é enviado como resposta para todos os clientes que consomem a aplicação. Geralmente é algo como: “X-Powered by ASP.NET”. Você pode remover diretamente do IIS ou via atributo enableVersionHeader do elemento httpRuntime.
    • Considere a utilização de páginas assíncronas quando possível.
  • Segurança
    • Toda aplicação Web roda sempre em FullTrust. Considere utilizar Medium (partial trust) já que ela atende a maioria dos casos, inclusive na versão 2.0 do .NET Framework, esse nível já concede acesso ao SqlClientPermission, responsável por gerenciar o acesso à servidores SQL Server. Para alterar esse nível, você pode utilizar o atributo level do elemento trust no arquivo Web.Config da aplicação ou do servidor Web, se desejar que todas as aplicações que estão rodando ali sejam definidas como medium trust.
    • Não permita que outros desenvolvedores sobrescrevam a sua policy. Para isso, defina o atributo allowOverride como false.
    • Sempre crie uma página padrão de erros para que os usuários seja redirecionados para ela quando um problema ocorrer. Para isso, configure a seção do arquivo Web.Config. Neste mesmo local, evite que as exceções sejam enviadas para os usuários, definindo o atributo mode para RemoteOnly. Isso evitará exibir detalhes da exceção que ocorreu, qual pode comprometer a sua aplicação.
    • Todos os parâmetros fornecidos pelo usuário (Forms, QueryStrings, etc.) devem ser validados por tamanho, tipo e formato.
    • Páginas importantes, que podem sofrer alguma espécie de ataque, podemos configurar a sua propriedade ViewStateUserKey para um valor qualquer (Session.SessionID), evitando assim, ataques de “one-click”.
    • Nunca armazene informações sigilosas em QueryStrings ou ViewState. Se necessitar armazenar algo importante no ViewState, então criptografe-o. Para isso defina o valor “Auto” para o atributo viewStateEncryptionMode do elemento e invoque o método RegisterRequiresViewStateEncryption na página em que deseja armazenar o conteúdo sigiloso no ViewState.
    • Use SSL somente em páginas que realmente precisam deste nível de segurança.
    • Criptografe a seção de connectionStrings.
    • Criptografe a seção de appSettings quando ele possui informações confidenciais.
    • Quando for conectar com uma base de dados qualquer, utilize um usuário que só tenha permissão necessária para manipular os objetos que estão sendo utilizados pela aplicação, nada mais.
    • Sempre armazena password “hasheados” na sua base de dados. Quando utiliza a arquitetura do Membership, ele já fornece isso intrinsicamente.
    • Defina para true o atributo httpOnlyCookies do elemento httpCookies no arquivo Web.Config. Esse atributo permite ou não que código client-side acessem o cookie.
  • Boas Práticas
    • Sempre extraia strings de arquivos de Resources (*.resx). Mais cedo ou mais tarde, alguém sempre pede para você globalizar a aplicação. 🙂
    • Defina o nome da aplicação no atributo applicationName quando utilizar o Membership/RoleProvider/Profile.
    • Se estiver utilizando o tratamento global de exceções, habilite o Health Monitoring para logar as exceções e efetuar uma posterior análise.
    • Desabilite o Trace em produção. Quando precisar habilitá-lo para poder monitorar alguma requisição, então define o atributo localOnly como true, para que o output possa ser somente visualizado no próprio computador onde a aplicação está sendo executada.

Bem, de momento o que me veio a cabeça é isso. É muito provável que existe outros detalhes que devo ter esquecido ao colocar aqui.

SandBoxing em ASP.NET

Como já havia mencionado aqui na seção de segurança, uma boa prática é não permitir que aplicações Web sejam executadas em FullTrust (padrão). Esse nível concede a aplicação Web, acesso a todos os recursos do servidor, tais como, acesso a sistema de arquivos, banco de dados, acesso ao Registry, execução de código não gerenciado, etc.. São permissões que, muitas vezes, não são necessárias e, se concedidas à alguém com más intenções, poderá comprometer a segurança do servidor e da rede.

Para evitar isso, voce pode configurar este nível de segurança tanto no arquivo Web.Config da aplicação ou, se desejar, no arquivo Web.Config que está a nível de servidor. Se optar por alterar o segundo, todas as aplicações ASP.NET que correm naquele servidor, utilizarão esse nível. E ainda, neste segundo (servidor), é importante que voce também altere o atributo allowOverride para false, justamente para que isso não possa ser sobrescrito pelas aplicações. Abaixo um exemplo de como alterar o nível de segurança apenas em uma aplicação:

<configuration>
    <system.web>
      <trust level=”Medium” />
    </system.web>
</configuration>

Com isso, voce terá restrições ao rodar o seu código. Um exemplo é com relação ao sistema de arquivos. Com o nível Medium, voce apenas poderá ler e escrever arquivos no diretório da aplicação e nada mais. Se desejar ler ou salvar um arquivo em C:Temp uma exceção será atirada.

Uma vez negado o acesso a todo o sistema de arquivos, pode ser necessário que a aplicação necessite acessar o diretório C:Temp. Com essa necessidade, temos duas técnicas que podemos recorrer para conceder o acesso que acabamos de negar. Essas técnicas estão descritas abaixo:

  1. Modificar o arquivo de segurança: essa técnica consiste na alteração do arquivo de configuração que está relacionado com o nível de segurança especificado. Como definimos Medium, as permissões que são concedidas, estão contidas dentro do arquivo web_mediumtrust.config, no diretório de configuração do .NET Framework 2.0.
  2. Utilizar a técnica de SandBoxing: SandBoxing (tema do post) consiste em voce fatorar, ou melhor, isolar o seu código em um outro Assembly, concedendo a este, permissões específicas para o que ele deseja fazer (no nosso caso é ler/salvar arquivos no diretório C:Temp).

O isolamento consiste basicamente em gerar uma DLL (projeto Class Library) e assiná-la com um strong-name. Uma vez criada essa DLL voce deve adicioná-la no GAC que, por padrão, Assemblies lá colocados ganham FullTrust. Se o GAC não estiver acessível, então voce deverá colocar o Assembly no diretório Bin da aplicação e ainda mudar o arquivo de configuração do nível de segurança, concendendo mais privilégios para o Assembly, baseando-se no publicador ou strong-name. É importante dizer que para ambos os casos é necessário que voce tenha privilégios administrativos no servidor.

Ainda há mais um detalhe na criação da DLL. É extremamente necessário que voce defina o atributo AllowPartiallyTrustedCallers (System.Security) no arquivo AssemblyInfo. Esse atributo permitirá que o componente seja chamado através de aplicações (código) parcialmente confiáveis (partial trust).

Finalmente voce executa a aplicação Web, fazendo chamada para este componente e, quando tenta criar um arquivo texto no C:Temp, uma exceção do tipo SecurityException é atirada dizendo que voce – ainda – não tem permissões para isso (FileIOPermission). Isso ocorre justamente porque, momentos antes que o componente vai salvar o arquivo no disco, ele verifica as permissões de toda a stack; esse processo é chamado do stack walk e é onde ele percorre todas as chamadas para ver se todo mundo possui a permissão necessária para efetuar a tarefa.

No nosso caso, como adicionamos o componente no GAC, o componente possui a permissão, pois é atribuído a ele FullTrust; mas como o stack walk também verificará a aplicação Web que chama o componente, essa por sua vez, não terá a permissão necessária e, consequentemente, a exceção será atirada. Para permitir que tudo corra bem, então é necessário que voce modifique a stack, aplicando um Assert (cuidado com esse método) a permissão que está sendo requisitada. Esse método apenas verificará se o componente tem permissão, não percorrendo todos os chamadores. Sendo assim, o código do componente tem uma ligeira mudança:

public void WriteFile(string file, string content)
{
    new FileIOPermission(FileIOPermissionAccess.AllAccess, file).Assert();
    using (StreamWriter sw = new StreamWriter(file))
        sw.Write(content);
}

Obviamente que isso é apenas um exemplo de teste. No mundo real, isso é bastante útil, já que muitas pessoas não se preocupam, e sempre deixam a configuração padrão do .NET que, em muitos casos, não é necessária e, neste caso, pode comprometer a segurança do servidor, principalmente, se ele for público.

mscorcfg.msc

Algumas ferramentas que são parte integrante do SDK do .NET Framework não estão disponíveis quando instalamos a versão do .NET Framework 2.0 (Redistributable Package (x86)), como é o caso do mscorcfg.msc. Essa ferramenta permite configurarmos o Runtime Security Policy (um caspol.exe visual) e também gerenciarmos os Assemblies que estão contidos dentro do GAC.

Apesar de somente a versão 1.x do .NET Framework disponibilizar essa ferramenta quando instalamos o Redistributable Package, há uma forma de habilitarmos na máquina. É importante dizer que quando fazemos essa configuração em uma máquina de desenvolvimento, é permitido que voce crie um arquivo do tipo *.msi, contendo a configuração necessário (de Enterprise, Machine e User) para que voce possa embutir durante a instalação do software nas máquinas clientes que exigem essa configuração de segurança.

Cuidados com o EventValidation

EventValidation é uma técnica que foi adicionada na versão 2.0 do ASP.NET e que permite validar o evento que se originou no cliente e que foi renderizado por alguma controle ASP.NET.

É muito comum em aplicações Web habilitar ou desabilitar um determinado controle do formulário. Isso pode as vezes acontecer por causa de uma condicional que, na maioria das vezes, é por causa das permissões do usuário corrente, pois ele não tem acesso aquela determinada funcionalidade. Com isso, imagine que tenhamos um botão no formulário e que as vezes estará habilitado, as vezes não. Como a página é renderizada no cliente, ele tem total acesso ao código fonte gerado; isso quer dizer que ele pode facilmente manipular as informações de postback para o servidor/página de onde originou a resposta e, conseqüentemente, ter acesso ao botão que, no momento, está desabilitado.

Para exemplo, imagine que o botão tenha sido ocultado (Visible = False). Agora, voce pode manipular a página (HTML), adicionar o botão, salvar e página e, finalmente, clicar no botão que foi adicionado. Se a propriedade EnableEventValidation da diretiva da página estiver definda como True (o padrão), uma exceção do tipo ArgumentException será disparada:

Invalid postback or callback argument.  Event validation is enabled using <pages enableEventValidation=”true”/> in configuration or <%@ Page EnableEventValidation=”true” %> in a page.  For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them.  If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.

Agora, se por algum descuido voce desabilitou o EventValidation, o evento Click (server-side) deste botão será disparado, mesmo que voce o ocultou quando a página foi processada e enviada para o cliente. Mesmo que voce não adicione o HTML responsável pelo botão na página, voce poderia facilmente montar uma requisição via objetos HttpWebRequest/HttpWebResponse e no body da requisição, definir os campos, inclusive o campo que foi ocultado e, para isso, podemos utilizar o Fiddler para interceptar a requisição/resposta e entender como montá-la.

Forms authentication failed for the request. Reason: The ticket supplied has expired.

 administrador da rede onde trabalho me perguntou porque o Event Log do servidor Web está com várias entradas (do tipo Information), sempre com a mesma mensagem: Event message: Forms authentication failed for the request. Reason: The ticket supplied has expired.

Esta mensagem dá-se quando temos o recurso de Membership do ASP.NET 2.0 habilitado na aplicação. Há a possibilidade do cookie do FormsAuthentication ser persistido no cliente (“Lembrar Senha”), evitando assim, de que toda vez que o usuário entrar a aplicação, precisar redigitar o login e senha. Quando o usuário deseja fazer isso, o cookie é criado e, toda vez que o mesmo acessar a aplicação, o cookie é enviado do cliente para o servidor. Se o cookie já tiver expirado e o Health Monitoring estiver habilitado, automaticamente essa mensagem é logada no Event Log.

Isso é facilmente identificado quando você utiliza o Reflector, mais precisamente dentro do método privado ExtractTicketFromCookie da classe (módulo) FormsAuthenticationModule.

Query Dinâmica

Durante os treinamentos que ministro, principalmente no curso de ADO.NET (2389) eu sempre oriento o pessoal a utilizar firmemente os Parameters ao invés de utilizar queries concatenadas com valores que são passados via formulários, QueryStrings, ou qualquer outra forma onde é o usuário que informa os dados.

Mas é necessário uma atenção especial quando se utiliza queries dinâmicas dentro do SQL Server. Mesmo utilizando parâmetros pois, neste caso, nem mesmo os parâmetros conseguem evitar o SQL Attack. É muito comum ver o pessoal utilizando essa técnica (eu também já utilizei) para suprir algumas limitações que existem com o SQL Server 2000 e que melhorou no 2005. Imaginem a Stored Procedure abaixo:

CREATE PROCEDURE RecuperaUsuarios
 @Nome As Varchar(50) AS

DECLARE @Query As Varchar(1000)
SET @Query = ‘SELECT * FROM Usuarios WHERE Nome = ”’ + @Nome + ””
EXEC(@Query)

E agora o código .NET:

SqlCommand cmd = new SqlCommand(“RecuperaUsuarios”, conn);
cmd.CommandType = CommandType.StoredProcedure;

SqlParameter param = new SqlParameter(“@Nome”, SqlDbType.VarChar);
param.Value = “‘; TRUNCATE TABLE Usuarios–“;
cmd.Parameters.Add(param);

Reparem que estou fixando a propriedade Value do objeto SqlParameter, mas o valor poderia ser proveniente de algum formulário que o usuário digitasse. Ao executar esse código, digam adeus aos seus registros.

Mas é importante dizer que muito se utiliza essa técnica (queries dinâmicas) quando utilizamos SQL Server 2000 (e que foi melhorado no 2005) e precisamos passar para a Stored Procedure a quantidade (TOP) de registros que queremos recuperar ou até mesmo quando precisamos efetuar uma paginação de resultados.

Para evitar problemas deste tipo precisamos ter como premissa que tudo que o usuário informa para a aplicação é extremamente perigoso. Isso nos levará a validar todas as informações, tanto do lado do cliente, quando do servidor, ou como fazem alguns bancos: só do lado do servidor. E quando eu digo validação, me refiro a tamanho, range, formato e tipo.

Client Application Services

A nova versão do Visual Studio (codename “Orcas”) traz uma série de novas funcionalidades a nível de linguagens e de aplicações. No caso das linguagens, podemos citar algumas técnicas que já foram abordadas neste outro artigo quando falamos sobre o LINQ. Quando falamos a nível de aplicações, muitas funcionalidades foram criadas e, uma delas, que é chamada de Client Application Services, é tema deste artigo.

Client Application Services trata-se de um novo conjunto de funcionalidades que habilita os mais diversos tipos de aplicações a autenticar usuários, recuperar suas respectivas roles e ainda persistir informações de cada um desses usuários em um servidor qualquer. A principal vantagem do Client Application Services é que ele trabalha em conjunto com uma aplicação Web, ou seja, você pode ter uma aplicação Web que disponibiliza todos esses serviços para os usuários que a acessam e também disponibilizar essa mesma infra-estrutura para que, por exemplo, uma aplicação Windows Forms possa fazer o uso disso, centralizando todo o acesso e gerenciamento de usuários em um único local.

É importante dizer que este artigo está baseado na versão Beta 1 do “Orcas” (versão 3.5 do .NET Framework) e está sujeito a mudanças até que a versão final seja lançada. A maior parte de todas as classes que precisamos manipular para habilitar esse recurso estão contidas dentro do namespace System.Web.Extensions (System.Web.Extensions.dll) que também possui toda a funcionalidade server-side do ASP.NET AJAX e que, por padrão, é automaticamente adicionada ao GAC (Global Assembly Cache) durante a instalação do .NET Framework.

As classes que iremos utilizar no decorrer do artigo estão (grande parte delas) divididas entre três principais namespaces, a saber: System.Web.ApplicationServices, System.Web.ClientServices e System.Web.ClientServices.Providers. O primeiro deles, System.Web.ApplicationServices, contém classes que são utilizadas do lado do servidor e que são a “ponte” entre a requisição e o recurso solicitado; já o namespace System.Web.ClientServices possui duas classes que representam uma principal e uma identity para as aplicações que fazem o uso do Client Application Services e, além delas, há uma classes para trabalho offline que veremos mais tarde, ainda neste artigo. Finalmente, temos o namespaceSystem.Web.ClientServices.Providers que, por sua vez, além de possuir várias classes importantes para o runtime fornece os providers para que o cliente possa especificar na aplicação. Isso é algo bem semelhante ao que já acontece com a configuração do membership em uma aplicação Web, com exceção de que a requisição é encaminhada para o servidor que centraliza esses dados. A imagem abaixo ilustra todas as classes que compõem cada um desses namespaces:

Figura 1 – Classes que compõem os namespaces que citamos acima.

Para que possamos disponibilizar essa funcionalidade, devemos primeiramente configurar a aplicação Web, ou seja, definindo os providers de membership, roles e profile. Neste passo não há nenhum segredo em relação ao que já fazíamos na versão 2.0 do ASP.NET. Sendo assim, eu irei assumir que você já tenha os devidos conhecimentos com relação a isso e, caso não possua, aconselho ler este artigo antes de prosseguir com esta leitura.

Já para o nosso exemplo eu irei utilizar os providers que se baseiam em um banco de dados SQL Server, a saber: SqlMembershipProvider, SqlRoleProvider e SqlProfileProvider. O artigo faz uso de uma base de dados chamada aspnetdb.mdf que faz o uso do SQL Server 2005 Express e foi executado o utilitário aspnet_regsql.exe para criar toda a infra-estrutura necessária dentro da base de dados para suportar todas essas funcionalidades. É importante dizer também que você não verá explicitamente definido cada um dos providers no arquivo Web.Config pois, como estou utilizando as configurações padrões, todos eles já estão definidos “em nível de máquina”, ou melhor, no arquivo machine.config.

Quando criamos uma aplicação Web dentro do “Orcas”, uma nova seção é embutida por padrão dentro do arquivo Web.Config da aplicação. Esta seção é chamada de system.web.extensions e possui alguns elementos que iremos utilizar neste artigo para que seja possível habilitar as funcionalidades (profile, membership e role) para o Client Application Services. Entre esses atributos, temos:

Elemento Descrição
profileService Permite habilitar o profile para que as aplicações o utilizem. Esse elemento possui um atributo chamado enabled que recebe um valor booleano indicando se o mesmo está ou não ativo. Além deste atributo, ele ainda possui dois outros, não menos importantes, que são listados abaixo:

  • readAccessProperties: Recebe todas as propriedades, separadas por vírgula, que somente terão permissão para leitura. Essas propriedades devem estar especificadas no profile da aplicação Web.
  • writeAccessProperties: Recebe todas as propriedades, separadas por vírgula, que somente terão permissão para escrita. Essas propriedades devem estar especificadas no profile da aplicação Web.
authenticationService Esse elemento também contém um atributo chamado enabled que indica se o serviço de autenticação está ou não ativo.
roleService Assim como os elementos anteriores, o roleService também conta com um atributo chamado enabled que indica se o serviço de roles está ou não ativo.

Formas de Acesso

Esses serviços já estão disponíveis desde as primeiras versões do Atlas que, por sua vez, fornece um proxy que habilita as aplicações baseadas em browser (que também é uma aplicação cliente) acessar esses recursos sem a necessidade de causar postbacks. Se analisarmos os internals, veremos que o trabalho de executar o processamento destes serviços está contido dentro do módulo chamado ScriptModule (namespace System.Web.Handlers). Veremos mais detalhes sobre essa forma de acesso um pouco mais adiante, quando falaremos sobre o consumo dessas funcionalidades em aplicações Windows. Além da forma de acesso anterior, também é possível acessá-los através do WCF (Windows Communication Foundation) onde você pode, via endpoints, expor essas funcionalidades.

Independente de qual seja a forma de acesso que você estiver utilizando, há classes dentro do namespace System.Web.ApplicationServices que contém toda a infra-estrutura para tratar tais requisições. Essas classes são AuthenticationService, RoleService e ProfileService, que já estão devidamente decorados com o atributo ServiceContract para suportar a exposição via WCF. Essas classes delegam a cada um dos providers concretos o trabalho de autenticação, autorização e gerenciamento dos profiles, verificando antes se o recurso está ou não habilitado, verificação que é feita baseando-se nos atributos enabled de cada um dos elementos da tabela acima.

O código abaixo ilustra o arquivo Web.Config da aplicação Web que irá disponibilizar os serviços para que os clientes possam consumí-los; mas vale lembrar que alguns trecho do arquivo foram suprimidos para poupar espaço. Se repararmos, há uma seção chamada system.web.extensions que contém todas as configurações que habilitam cada um dos serviços que desejamos disponibilizar.

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="false">
      <assemblies>
        <add assembly="System.Core"/>
        <add assembly="System.Web.Extensions" />
      </assemblies>
    </compilation>
    <authentication mode="Forms" />
    <httpHandlers>
      <remove verb="*" path="*.asmx"/>
      <add
          verb="*"
          path="*.asmx"
          validate="false"
          type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions" />
      <add
          verb="*"
          path="*_AppService.axd"
          validate="false"
          type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions" />
      <add
          verb="GET,HEAD"
          path="ScriptResource.axd"
          type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions" />
    </httpHandlers>
    <httpModules>
      <add
          name="ScriptModule"
          type="System.Web.Handlers.ScriptModule, System.Web.Extensions" />
    </httpModules>
    <anonymousIdentification enabled="true"/>
    <profile enabled="true">
      <properties>
        <add
            name="Parametro"
            type="System.String"
            allowAnonymous="true"/>
      </properties>
    </profile>
  </system.web>
  <system.web.extensions>
    <scripting>
      <webServices>
        <profileService
            enabled="true"
            readAccessProperties="Parametro"
            writeAccessProperties="Parametro" />
        <authenticationService
            enabled="true"
            requireSSL="false" />
        <roleService
            enabled="true"/>        
      </webServices>
    </scripting>
  </system.web.extensions>
</configuration>

Depois dessa configuração, agora já podemos utilizar os recursos habilitados em nossas aplicações clientes. O “Orcas” acrescentou nas propriedades de vários projetos, entre eles Windows Forms e WPF, uma aba chamada Services. É dentro desta aba que vamos habilitar a utilização dos recursos que queremos acessar em nossa aplicação Windows. A imagem abaixo ilustra essa nova tela:

Figura 2 – Aba Services em projetos cliente.

Como podemos reparar, há um CheckBox que habilita a utilização do Client Application Services. Quando habilitado, todos os campos que estão abaixo ficam disponíveis para que você possa especificar a URL de cada Web site onde está cada uma das respectivas funcionalidades (authentication, authorization e profile). Como no caso do exemplo todas as funcionalidades estão contidas dentro de um único site, a mesma URL é especificada em todos os campos. Uma vez que você salvar as informações, um arquivo App.Config é criado com a especificação dos providers, assim como uma aplicação Web. O código abaixo mostra o arquivo App.Config criado depois de definido os application services:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add
      key="ClientSettingsProvider.ServiceUri"
      value="http://localhost/Beta.WebUI/Profile_JSON_AppService.axd" />
  </appSettings>
  <system.web>
    <membership defaultProvider="ClientAuthenticationMembershipProvider">
      <providers>
        <add
          name="ClientAuthenticationMembershipProvider"
          type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, 
             System.Web.Extensions"
          serviceUri="http://localhost/Beta.WebUI/Authentication_JSON_AppService.axd"
          credentialsProvider="Beta.WinUI.Form1, Beta.WinUI" />
      </providers>
    </membership>
    <roleManager defaultProvider="ClientRoleProvider" enabled="true">
      <providers>
        <add
          name="ClientRoleProvider"
          type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions"
          serviceUri="http://localhost/Beta.WebUI/Role_JSON_AppService.axd"
          cacheTimeout="86400" />
      </providers>
    </roleManager>
  </system.web>
</configuration>

Analisando o código acima, temos uma key chamada ClientSettingsProvider.ServiceUri que armazena o endereço até o local onde o profile está sendo disponibilizado. Em seguida, temos uma infra-estrutura de elementos muito semelhante ao que já existe atualmente em uma aplicação Web, que são os elementos membership e roleMananger. A diferença aqui é que eles não utilizam um provider como SqlMembershipProvider ou o SqlRoleProvider e sim os providers que estão definidos dentro do namespace System.Web.ClientServices.Providers. Como optamos pela autenticação via FormsAuthentication, estaremos utilizando no exemplo o provider ClientFormsAuthenticationMembershipProvider e também o ClientRoleProvider para o gerenciamento das roles. A principal diferença nestes providers é que eles invocam o método remotamente, e a forma de acesso dependerá de como você disponibilizou essas funcionalidades na aplicação Web, ou seja, da forma “tradicional”, que é através de uma requisição HTTP (via classes HttpWebRequest e HttpWebResponse), serializando os dados utilizando JSON ou, se desejar, via WCF. É importante mencionar que é necessário que você faça referência aos Assemblies System.Web.dll e System.Web.Extensions.dll na aplicação cliente.

Em ambos os casos, membership e roleManager, há um atributo chamado serviceUri que especifica o URL até a aplicação que disponibiliza o serviço. São os valores definidos para este atributo que o runtime determina para onde e como fazer a requisição. Se o endereço finalizar com *.svc (que identifica um endpoint WCF) a requisição é realizada através do WCF; caso contrário, a requisição é feita via requisição HTTP/JSON, e o que determinará qual será o serviço a ser executado (membership, roles ou provider) é o “nome do recurso” solicitado: Authentication_JSON_AppService.axd, Role_JSON_AppService.axd ou Profile_JSON_AppService.axd.

No caso do elemento membership há um atributo que merece ser comentado. Trata-se do atributo credentialsProvider, que também pode ser definido a partir da aba Service que vimos um pouco acima. Esse atributo permite-nos definir o Type de algum objeto da aplicação que fornecerá uma instância do tipo ClientFormsAuthenticationCredentials representando as credenciais do usuário, que será enviada para a aplicação Web quando o método de Login for invocado. O tipo que for definido obrigatoriamente deve implementar a Interface IClientFormsAuthenticationCredentialsProvider que possui apenas um método chamado GetCredentials que, muitas vezes, é utilizado no formulário de login da aplicação. O trecho de código abaixo ilustra a utilização dessa técnica em um formulário Windows Forms que implementa a Interface IClientFormsAuthenticationCredentialsProvider:

using System.Windows.Forms;
using System.Web.Security;
using System.Web.ClientServices.Providers;

public partial class Login : Form, IClientFormsAuthenticationCredentialsProvider
{
    private void btnLogin_Click(object sender, EventArgs e)
    {
        if(Membership.ValidateUser(string.Empty, string.Empty))
        {
            string[] roles = Roles.GetRolesForUser("IA");
            this.lblMensagem.Text = "Usuário válido";
        }
        else
        {
            this.lblMensagem.Text = "Usuário inválido";
        }
    }

    public ClientFormsAuthenticationCredentials GetCredentials()
    {
        return new ClientFormsAuthenticationCredentials(
            this.txtLogin.Text.Trim(), 
            this.txtPassword.Text.Trim(),
            false);
    }
}

Dentro do evento Click do botão btnLogin invocamos o método estático ValidateUser da classe Membership (System.Web.Security) que retorna um valor booleano indicando se as credenciais informadas são ou não válidas. O ponto curioso é que ao invés de passarmos o login e senha para este método, passamos os valores em branco. Isso acontece porque as credenciais serão extraídas automaticamente a partir do tipo especificado no atributo credentialsProvider do elemento membership.

Além disso, podemos reparar que fazemos a utilização das classes Membership e Roles, assim como já acontece em aplicações Web. A classe Membership trata-se de estática que possui um membro interno chamado s_Provider do tipo MembershipProvider (a classe base para as classes concretas de Membership, como por exemplo o SqlMembershipProvider), o qual receberá a instância da classe concreta. Essa inicialização acontece quando um método interno chamado Initialize desta mesma classe é executado. Ele se encarrega de extrair o provider especificado no arquivo Web.Config (que no nosso caso é o ClientFormsAuthenticationMembershipProvider) e instanciá-los para que, quando chamarmos os métodos e propriedades, já sejam efetivamente os métodos e propriedades da classe concreta que queremos utilizar. O funcionamento é também semelhante para a classe Roles.

Todos os providers específicos e que são utilizados pelo Client Application Services adotam a mesma hierarquia em relação aos outros providers que já conhecemos, ou seja, no caso do membership, as classes ClientFormsAuthenticationMembershipProvider e ClientWindowsAuthenticationMembershipProvider herdam de uma classe abstrata chamada MembershipProvider; já a classe ClientRoleProvider herda da classe abstrata RoleProvider e, finalmente, a classe ClientSettingsRoleProvider herda diretamente de SettingsProvider. Como podemos perceber, os providers que são prefixados com o valor “Client” são os providers utilizados pelas Client Applications e estão disponíveis dentro do namespace System.Web.ClientServices.Providers. A imagem abaixo ilustra a hierarquia destas classes:

Figura 3 – Hierarquia dos providers cliente.

A diferença entre os providers de autenticação (ClientFormsAuthenticationMembershipProvider e o ClientWindowsAuthenticationMembershipProvider) é que o primeiro deles, deve ser utilizado em aplicações cliente quando a forma de autenticação da aplicação Web é via FormsAuthentication; já a segunda é quando a forma de autenticação da aplicação Web se baseia na autenticação do Windows.

Importante: É importante dizer que apesar de vários métodos da classe Membership e Roles estarem disponíveis, muitos deles não são suportados “by design”. Sendo assim, atente-se em chamar os métodos destas classes e certifique-se de que eles realmente possuem a implementação da funcionalidade a qual cada um deles se destina a realizar.

Para finalizar a parte de autenticação e autorização, não podemos deixar de comentar sobre o ClientFormsIdentity e o ClientRolePrincipal. Essas duas classes implementam as Interfaces IIdentity e IPrincipal, respectivamente. O primeiro deles, ClientFormsIdentity, representa a identidade do usuário que efetuou o login na aplicação; já o ClientRolePrincipal refere aos direitos que o usuário possui na aplicação. A instância da classe ClientFormsIdentity é definida automaticamente para a propriedade CurrentPrincipal da classe Thread quando o login é efetuado com sucesso. Essas duas classes estão contidas dentro do namespace System.Web.ClientServices.

Através da Figura 2 podemos notar que o último campo que consta na aba Services é onde devemos informar a URL para o site que disponibiliza as informações de profile. O funcionamento interno para requisitar as informações remotas é bem parecido ao que já acontece com o membership e as roles. A novidade aqui é que, na aba Settings das propriedades do projeto, existe um novo botão chamado Load Web Settings que, através da URL informada anteriormente, irá requisitar as informações (inclusive metadados) e para cada propriedade que está sendo exposta pelo profile da aplicação Web requisitada, ele os criará na seção de Settings da aplicação.

Para todo item criado na seção Settings, independente se ele é ou não oriundo do profile de uma aplicação Web, é sempre criado uma propriedade com o mesmo nome e tipo em um arquivo (Visual C# ou Visual Basic .NET) de “code-behind”. O que difere entre as propriedades “locais” e as propriedades que foram importadas do projeto Web é que, neste segundo caso, ela é decorada com um atributo chamado SettingsProviderAttribute, passando para o seu construtor o provider onde essa informação será efetivamente armazenada que, no nosso caso, é o ClientSettingsProvider. A imagem exibe a aba Settings em que podemos importar as propriedades do profile da aplicação Web previamente especificada na aba Services.

Figura 4 – Importando as propriedades da aplicação Web para a aplicação cliente.

Com isso realizado, já posso acessar ou definir as informações nessas propriedades (dependendo do que foi definido na aplicação Web). Para isso, basta simplesmente acessar como já estávamos fazendo com uma propriedade simples. O exemplo abaixo está também baseado no arquivo Web.Config da aplicação Web que foi criada mais acima. Lá você poderá comprovar a existência da propriedade chamada Parametro que foi definida no profile.

Properties.Settings.Default.Parametro = "123";
MessageBox.Show(Properties.Settings.Default.Parametro);

Acessando via WCF

Como foi mencionado acima, é possível também expor as funcionalidades de membership, roles e profiles a partir de serviços WCF (*.svc). Isso exigirá uma ligeira mudança na aplicação Web que expõe essas funcionalidades, ou seja, será necessário adicionar a este mesmo projeto um arquivo com extensão *.svc para cada uma das funcionalidades que deseja disponibilizar.

Para fazer o mesmo exemplo que vimos acima, mas utilizando WCF, devemos adicionar três arquivos ao projeto Web. Chamaremos cada um desses arquivos de: AuthenticationService.svc, RoleService.svc e ProfileService.svc. Para cada um desses arquivos é necessário alterar o valor do atributo Service da diretiva do mesmo, informando o tipo que será exposto. Como cada um dos arquivos tem uma finalidade diferente, então a configuração para cada um deles ficará:

//Arquivo AuthenticationService.svc
<% @ServiceHost Language="C#" Debug="true"
        Service="System.Web.ApplicationServices.AuthenticationService" %>

//Arquivo RoleService.svc
<% @ServiceHost Language="C#" Debug="true"
        Service="System.Web.ApplicationServices.RoleService" %>

//Arquivo ProfileService.svc
<% @ServiceHost Language="C#" Debug="true"
        Service="System.Web.ApplicationServices.ProfileService" %>

A única diferença que muda no código acima para uma aplicação que está sendo desenvolvida em Visual Basic .NET é que o atributo Language deve ser definido com o valor “VB”. Neste cenário não precisamos nos preocupar com a infra-estrutura necessária (atributos ServiceContract e OperationContract) para expor uma classe via WCF, pois tudo isso já está definido nas classes AuthenticationService, RoleService e ProfileService.

Uma vez definidos os arquivos físicos, é necessário configurar os endpoints no arquivo Web.Config da mesma aplicação. Neste momento vamos definir para cada tipo de serviço o seu respectivo nome, binding e o contrato. Como o foco não é explicar cada um dos detalhes do WCF, vou assumir que você já conheça essas técnicas. Caso você ainda não esteja familiarizado com o WCF, então é necessário que você leia algum material a respeito para que possa entender o seu funcionamento e ajustar as configurações do mesmo de acordo com a sua necessidade.

O trecho de código abaixo ilustra todos os endpoints necessários para expor cada uma das funcionalidades que vimos até o momento. Reparem que o contrato que está definido para cada um desses endpoints reflete exatamente no nome da classe concreta que, como já sabemos, já expõe o contrato porque esta decorada com os atributos necessários.

<?xml version="1.0"?>
<configuration>
  <system.web>
    <authentication mode="Forms" />
    <anonymousIdentification enabled="true"/>
    <profile enabled="true">
      <properties>
        <add name="Parametro" type="System.String" allowAnonymous="true"/>
      </properties>
    </profile>
  </system.web>
  <system.web.extensions>
    <scripting>
      <webServices>
        <profileService
            enabled="true"
            readAccessProperties="Parametro"
            writeAccessProperties="Parametro" />
        <authenticationService
            enabled="true"
            requireSSL="false" />
        <roleService
            enabled="true"/>        
      </webServices>
    </scripting>
  </system.web.extensions>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <services>
      <service
        behaviorConfiguration="AuthenticationBehavior"
        name="System.Web.ApplicationServices.AuthenticationService">
        <endpoint
          binding="basicHttpBinding"
          name="AuthenticationService"
          contract="System.Web.ApplicationServices.AuthenticationService" />
      </service>
      <service
        behaviorConfiguration="RoleBehavior"
        name="System.Web.ApplicationServices.RoleService">
        <endpoint
          binding="basicHttpBinding"
          name="RoleService"
          contract="System.Web.ApplicationServices.RoleService" />
      </service>
      <service
        behaviorConfiguration="ProfileBehavior"
        name="System.Web.ApplicationServices.ProfileService">
        <endpoint
          binding="basicHttpBinding"
          name="ProfileService"
          contract="System.Web.ApplicationServices.ProfileService" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="AuthenticationBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
        <behavior name="RoleBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
        <behavior name="ProfileBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Importante: Mesmo fazendo o acesso via WCF, é necessário que as funcionalidades que vão ser disponibilizadas estejam devidamente habilitadas. Caso contrário, uma exceção do tipo InvalidOperationException é atirada para o cliente, informando que o recurso está desabilitado.

A configuração do Client Application Services quando estamos expondo as funcionalidades através de WCF é exatamente a mesma quando estamos falando do lado do cliente (quem consome essas funcionalidades) em relação ao exemplo anterior. Basicamente, a única diferença está na configuração do arquivo App.Config do lado do cliente onde, no atributo serviceUri, você deverá especificar a URL do serviço WCF (*.svc) ao invés do *.axd, que é colocado por padrão quando não é informado. Internamente, o runtime determina a forma de requisição analisando a extensão do recurso (arquivo) informado neste atributo.

Versão Beta 1 – Orcas

A configuração do Client Application Services utilizando WCF como forma de acesso infelizmente não está funcionando na versão Beta 1 do Visual Studio Orcas. Apesar da configuração externa ser o que vimos acima, internamente há alguns problemas com relação ao contrato dos serviços que impedem que a requisição seja efetuada com sucesso. Inclusive essa falha já foi reportada para a Microsoft e, provavelmente, será consertada nas próximas versões.

Se desejarmos, há uma alternativa onde podemos acessar os serviços WCF diretamente, assim como já fazemos. Essa técnica permitirá acessarmos as funcionalidades que estão sendo disponibilizadas pela aplicação Web. O ponto negativo que vejo aqui é que, desta forma, nós ficamos responsáveis por criar e definir a identity e a principal da thread que é encarregada pela execução da aplicação, o que acontece automaticamente quando utilizamos o ClientFormsAuthenticationMembershipProvider. Uma vez que referenciamos todos os serviços em um único cliente, temos:

Figura 5 – Serviços WCF instalados no cliente.

Com os serviços WCF devidamente referenciados na aplicação cliente, antes de consumí-los dentro da mesma é necessário abrir o arquivo App.Config desta mesma aplicação e definir o atributo allowCookies do binding especificado para true para que seja possível o WCF armazenar cookies no cliente e utilizá-los em futuras requisições. Depois disso, basta instanciarmos as classes que, na verdade, são os proxies de cada um dos serviços e chamar seus respectivos métodos. O trecho de código abaixo ilustra como devemos proceder para validar e autenticar um determinado usuário:

using (AuthenticationService.AuthenticationServiceClient auth =
    new AuthenticationService.AuthenticationServiceClient())
{
    if (auth.ValidateUser("Israel", "P@$$w0rd", string.Empty) &&
        auth.Login("Israel", "P@$$w0rd", string.Empty, true))
    {
        try
        {
            //Se o atributo allowCookies for definido como
            //False, o valor retornado pelo método IsLoggedIn
            //também será False.

            Console.WriteLine(auth.IsLoggedIn().ToString());
        }
        finally
        {
            auth.Logout();
        }
    }
}

Trabalhando em modo Offline

Um recurso que o Client Application Services também já possui é a possibilidade de trabalhar em modo offline. Esse recurso possibilita efetuar uma requisição para um serviço (seja ele membership, roles ou profile). Enquanto estiver no modo online, os dados relevantes são armazenados localmente para quando estiver offline. Quando você está em modo offline não haverá possibilidade de acessar um determinado recurso e, sendo assim, a sua aplicação poderia falhar. Felizmente o armazenamento local facilita isso, sem a necessidade de termos que escrever código para serializar/deserializar os objetos.

Há dois tipos de repositórios que são disponibilizados por esse recurso: sistema de arquivos ou um banco de dados local. Por padrão, será utilizado o sistema de arquivos, mas você pode determinar uma base de dados local (SQL Server Express) para armazenar essas informações. Para isso, você deve ir novamente até a aba Services do projeto cliente e, ao habilitar o Client Application Services, um botão chamado Advanced será habilitado e, ao clicar, você terá acesso a tela mostrada na figura abaixo, onde você pode marcar a opção User a database instead of the file system for local data storage. Ao marcá-la, o campo abaixo será disponibilizado para que você informe a connection string até o mesmo.

Figura 6 – Habilitando o armazenamento em um banco de dados.

Finalmente, o que vai determinar se a aplicação está ou não online é através da propriedade IsOffline da classe estática ConnectivityStatus que, por sua vez, está contida dentro do namespace System.Web.ClientServices. Quando desejar, basta você atribuir o valor True a esta propriedade, e assim os dados passarão a ser resgatados/atualizados localmente, dentro do repositório selecionado.

Conclusão: Uma das principais features que foram pedidas em fóruns e talvez à própria Microsoft era uma forma simples de conseguir integrar aplicações clientes, como Windows Forms, com toda a extensível infra-estrutura fornecida, inicialmente, para uma aplicação Web, que são os Provider Models. Felizmente isso foi acatado como uma necessidade pela Microsoft e nesta nova versão do .NET Framework/Visual Studio .NET já está disponível para a utilização e, no decorrer deste artigo, pudemos entender um pouco mais sobre suas configurações e também o seu comportamento interno.

WCF – Integrando MembershipProvider e RoleProvider

Por padrão, serviços WCF utilizam as identidades e grupos do Windows para autenticação e autorização, respectivamente. Um dos grandes problemas é quando temos isso sendo disponibilizado através de uma aplicação web, pois iria requerer que todos os clientes que acessam via web estivessem devidamente cadastrados dentro do Windows; além disso, há o problema com relação aos grupos de usuários, já que muitas vezes não temos acesso para cadastrá-los e, quando isso não é um problema, podemos ter um problema adicional quando estivermos rodando em culturas de servidores diferentes.

Com isso, dificilmente uma aplicação ou serviços que são expostos para a internet utilizam as contas e grupos do Windows. A solução é que felizmente o ASP.NET 2.0 fornece uma infraestrutura completa para o gerenciamento de autenticação e autorização, chamada de Provider Model. Ela nos dá uma enorme flexibilidade, onde podemos trocar a fonte de dados/persistência e a aplicação continua trabalhando normalmente. Este provider é um módulo do software que estamos desenvolvendo que fornece uma interface genérica para uma fonte de dados, onde abstraem a base de dados. Além disso ser flexível, a qualquer momento podemos trocar a base de dados (essa arquitetura também é extensível) ou seja, se mais tarde quisermos customizar algo, podemos fazer isso sem maiores problemas.

Com toda essa infraestrutura já bem desenhada e sendo utilizada largamente no mercado, a Microsoft decidiu, em uma das formas de segurança de serviços WCF, integrar com esse modelo disponibilizado pelo ASP.NET 2.0. Este artigo já assume que você tenha conhecimento suficiente nesta infraestrutura e, em alguns pontos, vamos falar de alguns detalhes mais superficialmente. Se você ainda não estiver a vontade nisso, eu aconselho extremamente ler o artigo Entendendo e Implementando Segurança no ASP.NET 2.0 antes de prosseguir.

O transporte da mensagem

Quando estamos em um cenário de internet, as mensagens são enviadas para o serviço através de puro HTTP, porém é extremamente importante proteger o corpo e as credenciais do usuário durante essa “viagem”. A solução para isso é criptografar a mensagem para garantir a integridade e privacidade, utilizando a senha do usuário mas, mais uma vez por questões de segurança, o WCF não utiliza essa forma por ser vulnerável, pois o usuário pode ter uma senha pouco complexa e qualquer um que esteja monitorando a comunicação poderá interceptar e, conseqüentemente, “quebrar” a criptografia.

Para proteger a mensagem, o WCF utiliza um certificado do tipo X.509, que fornece uma proteção extremamente confiável. Este tipo de certificado também autentica unicamente o serviço para o cliente, trabalhando com duas chaves: uma pública e outra privada. Quando criptografamos algo com a chave pública, somente quem tiver a chave privada poderá decriptografar o seu conteúdo e, neste nosso cenário, o detentor da chave pública é o serviço WCF que, por sua vez, deverá manter a chave privada armazenada em algum local seguro para que a mesma não caia em mãos erradas.

A chave pública fica disponível no servidor que expõe o serviço e, sendo assim, qualquer cliente pode acessar os endpoints do serviço WCF e obter a chave pública. Como já sabemos, essa chave pública será utilizada pelo cliente para criptografar todas as mensagens que serão enviadas para o serviço que, por sua vez, quando receber a requisição, irá decriptografá-la utilizando a chave privada e utilizará as credenciais fornecidas pelo usuário para autenticá-lo e, conseqüentemente, permitir o acesso ao serviço.

Para o teste que vamos realizar no decorrer deste artigo, necessitamos de um certificado X.509 para utilizarmos durante a criação do serviço. Como não tenho nenhum certicado disponível, podemos criar um para testes. Para isso, utilizaremos um utilitário chamado Makecert.exe que é fornecido junto com o .NET Framework e pode ser encontrado no seguinte endereço: C:Program FilesMicrosoft Visual Studio 8SDKv2.0Bin. Este utilitário cria um par de chave pública e privada e armazena em um arquivo de certificado. Para criar o certificado, abra o prompt de comando do Visual Studio .NET 2005 (isso para evitar o path completo do utilitário) e digite:

makecert -sr LocalMachine -ss My -sky exchange -pe -a sha1 -n "CN=IACertificate" IACertificate.cer

Basicamente, através da linha acima, criamos um certificado chamado IACertificate e armazenamos ele em um arquivo chamado IACertificate.cer. Mas é importante dizer sobre cada um das opções que são passadas para o utilitário Makecert.exe, opções que são detalhadas através da tabela abaixo:

Opção Descrição
-sr

Especifica o local onde o certificado será armazenado. Entre esses valores temos: currentuser ou localmachine.

-ss

Especifica o nome do certificado onde o output será armazenado.

-sky

Especifica o tipo do certificado, que deve ser signature ou exchange.

-pe

Define como será gerada a chave privada, o que permite que a mesma seja incluída no certificado.

-a

Indica qual será o algoritmo usado pelo certificado. Entre os algoritmos temos o md5 (padrão) ou o sha1.

-n

Especifica o nome do certificado, devendo estar em conformidade com o padrão X.500, que é especifcar o nome entre aspas e precedido por CN=.

Observações: Se quiser uma lista completa de todas as opções fornecidas pelo utilitário Makecert.exe, consulte este link. Depois de criado o certificado, se desejar visualizá-lo, vá até a opção Run do Windows e digite: mmc; em seguida, vá até o menu File, Add/Remove Snap-in…, terá um botão chamado Add no fim da janela; ao clicar nele um novo formulário será apresentado e na listagem terá um item chamado Certificates que, ao selecioná-lo, abrirá um wizard onde você deverá, na primeira tela apresentada, selecionar a opção Computer account. Isso fará com que todos os certificados do seu computador sejam exibidos na console de administração. Ao navegar até Personal, Certificates, verá ali o certificado recém criado.

Como o nosso foco será disponibilizar isso através de uma aplicação que irá correr dentro do IIS, é necessário concedermos direitos de leitura a chave privada do certificado em questão para as contas dos worker process do IIS. Para isso, temos que seguir dois passos que são mostrados abaixo:

  • Através do utilitário FindPrivateKey.exe, conseguimos identificar a chave privada, para que seja possível encontrá-la e, em seguida, conceder os direitos a mesma. Se não tiver o Microsoft SDK for .NET Framework 3.0 instalado, então voce pode baixar os exemplos de WCF da Microsoft que, dentro dele, existe um projeto chamado FindPrivateKey.sln, que é justamente o projeto que gera esse utilitário.

  • Depois de encontrada a chave, voce pode conceder os direitos através do utilitário cacls.exe (como é mostrado neste post) ou via Windows Explorer, através da aba Security.

Criação do contrato e configuração da autorização

Neste momento, vamos nos concentrar na criação e configuração do serviço WCF. Mais uma vez, vou considerar como conhecimento prévio o procedimento para a criação de serviço básico de WCF, principalmente no que diz respeito aos atributos que devem ser aplicados aos tipos para torná-lo um serviço WCF. Para o exemplo, vamos criar um contrato padrão, chamado de IServiceContract, que será implementado pelos serviços concretos. Esse contrato (uma Interface) terá dois métodos simples, chamados de BoasVindas e HoraAtual. Essa Interface está exibida abaixo:

using System;
using System.ServiceModel;

namespace DevMinds.ComponentCS
{
    [ServiceContract]
    public interface IServiceContract
    {
        [OperationContract]
        string BoasVindas();

        [OperationContract]
        DateTime DataAtual();
    }
}

Como estamos falando sobre a integração com a segurança do ASP.NET, esse serviço utilizará como hosting o IIS. Sendo assim, temos uma arquivo com extensão *.svc que implementa a Interface definida acima. A única exceção para um exemplo simples, é que os métodos são decorados com o atributo PrincipalPermissionAttribute. Esse atributo, contido dentro do namespace System.Security.Permissions, permite você configurar as informações de segurança, mais especificamente de autorização, utilizando o padrão declarativo e, como podemos notar no exemplo abaixo, cada um dos métodos pode ser invocado por um grupo específico:

using System;
using System.Threading;
using System.Security.Permissions;
using DevMinds.ComponentCS;

public class DefaultService : IServiceContract
{
    [PrincipalPermission(SecurityAction.Demand, Role = "User")]
    public string BoasVindas()
    {
        return string.Format("Ola {0}!",
            Thread.CurrentPrincipal.Identity.Name);
    }

    [PrincipalPermission(SecurityAction.Demand, Role = "Manager")]
    public DateTime DataAtual()
    {
        return DateTime.Now;
    }
}

Um detalhe para a autorização é que se um determinado método é permitido ser invocado por mais de um grupo, você pode adicionar múltiplos atributos PrincipalPermission. Além disso, esse atributo permite a definição de um nome de usuário que tem acesso ao membro. Segurança declarativa força definirmos o nome dos grupos e usuários em hardcode. Se esses valores são dinâmicos, o ideal é estar fazendo isso a partir da segurança imperativa, ou seja, via código. Além deste detalhe, a declaração imperativa permite uma melhor customização, pois podemos ter condicionais para determinar se a segurança será ou não aplicada.

A autorização imperativa ou declarativa não é exclusividade do Windows Communication Foundation ou mesmo da plataforma 2.0 do .NET, mas já existe desde a primeira versão e foi adequado ao sistema de autorização do WCF.

Configuração do serviço

Como já estou assumindo que você tenha os devidos conhecimentos sobre a configuração do Membership e RoleProvider, vou ocultar aqui a explicação deles, mas no final desta seção, mostrarei o Web.Config na íntegra. Basicamente teremos na seção connectionStrings a conexão com a base de dados SQL Server onde estarão as tabelas utilizadas pela infraestrutura do Membership e RoleProvider.

As configurações no serviço que devemos efetuar se resume às seções de bindings e behaviors, as quais iremos analisar individualmente cada um dos elementos e atributos que constituem essa configuração de segurança com integração ao ASP.NET 2.0.

Em um ambiente de internet, as mensagens devem ser transferidas com as credenciais (login e password) informadas pelo usuário. Sendo assim, neste caso utilizaremos o binding WSHttpBinding. Este, por sua vez, representa um binding que pode utilizar HTTP ou HTTPs para transporte, suportando transações distribuídas, confiabilidade e segurança nas mensagens. É bom lembrar que a classe BasicHttpBinding é utilizada para a comunicação com Web Services (ASMX) e não fornece segurança. Se desejarmos ter um canal seguro utilizando este binding, devemos recorrer ao HTTPs. Já o WSHttpBinding possui segurança a nível de mensagem e é isso que veremos a seguir, ou seja, como configurar essa segurança.

A classe WSHttpBinding possui uma propriedade chamada Security que expõe um objeto do tipo WSHttpSecurity. Este objeto possui duas propriedades importantes que utilizaremos na configuração do binding. A primeira delas é a propriedade Mode, que aceita uma das opções fornecidas pelo enumerador SecurityMode. As opções estão descritas na tabela abaixo:

Opção Descrição
Message

Nesta opção, a segurança é fornecida utilizando a segurança do protocolo SOAP.

None

A segurança está desabilitada.

Transport

A segurança será fornecida utilizando HTTPs.

TransportWithMessageCredential

Esta opção permite efetuar um mix das opções anteriores: utilizará um endpoint HTTPs para fornecer autenticação, integridade e confidenciabilidade, enquanto as credenciais do usuário continua sendo enviada através da mensagem SOAP (header) por questões de flexibilidade. Um dos benefícios desta técnica é que todo envelope SOAP é criptografado durante o transporte, incluindo os headers.

Já a segunda propriedade, chamada Message, é onde configuramos o nível de segurança para o transporte da mensagem. Essa propriedade expõe um objeto do tipo NonDualMessageSecurityOverHttp que possui uma propriedade chamada ClientCredentialType. Através desta propriedade especificamos o tipo da credencial do cliente que é utilizada para a autenticação do mesmo quando estamos utilizando o WsHttpBinding binding. Essa propriedade recebe uma das opções contidas no enumerador MessageCredentialType, que estão abaixo descritos:

Opção Descrição
Certificate

Especifica que a autenticação do cliente será efetuada utilizando um certificado.

IssuedToken

Especifica que a autenticação do cliente será efetuada utilizando um issued token.

None

Especifica a autenticação anônima.

UserName

Especifica que a autenticação do cliente será efetuada através de um username.

Windows

Especifica que a autenticação do cliente será efetuada através do Windows.

Como já conhecemos as opções disponíveis para a configuração do binding WSHttpBinding, é necessário neste momento analisarmos os behaviors que também são uma peça fundamental neste processo. Através dos behaviors do serviço, iremos especificar o certificado que criamos acima e, além disso, vamos determinar qual será a forma de autenticação (Membership Provider) e autorização (Role Provider) que o serviço irá utilizar.

Já falando de arquivo de configuração (Web.Config), através do elemento serviceBehaviors vamos customizar a autenticação e autorização. A autenticação (juntamente com o certificado) é realizada dentro do elemento serviceCredentials e a autorização através do elemento serviceAuthorization. O elemento serviceCredentials, responsável pela autenticação, possui dois sub-elementos, a saber: userNameAuthentication e serviceCertificate onde, no primeiro, especificamos a forma da autenticação e o segundo, informações relacionadas ao certificado que será utilizado.

Antes de dar maiores detalhes sobre a configuração da autenticação e autorização, vamos visualizar na íntegra o arquivo Web.Config da aplicação web que hosperará o serviço WCF:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add
      name="SqlServerConnString"
      connectionString="Data Source=.;Initial Catalog=DBWCF;Integrated Security=SSPI;"
      providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <system.serviceModel>
    <services>
      <service name="DefaultService" behaviorConfiguration="ServiceBehavior">
        <endpoint
          name="DevMindsEndpoint"
          contract="DevMinds.ComponentCS.IServiceContract"
          binding="wsHttpBinding"
          bindingConfiguration="ServiceBinding" />
      </service>
    </services>
    <bindings>
      <wsHttpBinding>
        <binding name="ServiceBinding">
          <security mode="Message">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehavior">
          <serviceCredentials>
            <userNameAuthentication
                userNamePasswordValidationMode="MembershipProvider"
                membershipProviderName="SqlMembershipProvider"/>
            <serviceCertificate
                storeLocation="LocalMachine"
                storeName="My"
                x509FindType="FindBySubjectName"
                findValue="IACertificate" />
          </serviceCredentials>
          <serviceAuthorization
              principalPermissionMode="UseAspNetRoles"
              roleProviderName="SqlRoleProvider" />
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
  <system.web>
    <membership
      defaultProvider="SqlMembershipProvider"
      userIsOnlineTimeWindow="15">
      <providers>
        <clear />
        <add
          name="SqlMembershipProvider"
          type="System.Web.Security.SqlMembershipProvider"
          connectionStringName="SqlServerConnString"
          applicationName="WCFApp" />
      </providers>
    </membership>
    <roleManager enabled="true" defaultProvider="SqlRoleProvider" >
      <providers>
        <add
          name ="SqlRoleProvider"
          type="System.Web.Security.SqlRoleProvider"
          connectionStringName="SqlServerConnString"
          applicationName="WCFApp"/>
      </providers>
    </roleManager>
  </system.web>
</configuration>

Como explicar a configuração dos providers está fora do escopo deste artigo, vamos abordar diretamente o elemento userNameAuthentication. Ele possui dois atributos: userNamePasswordValidationMode e membershipProviderName. O primeiro deles determina qual será a forma de validar o username/password. As opções possíveis são fornecidas através do enumerador UserNamePasswordValidationMode. As opções que ele fornece estão descritas na tabela abaixo:

Opção Descrição
Custom

A validação do usuário será baseada em uma forma customizada. Esse modo exige que você crie seu próprio “validador” e, para isso, herde a classe UsernamePasswordValidator e customize os membros necessários.

MembershipProvider

Especifica que a validação da autenticação do usuário será efetuada utilizando a estrutura de Membership Provider do ASP.NET 2.0.

Windows

Os userNames são mapeados para os usuários do Windows.

Como o foco do artigo é a integração de serviços WCF e a estrutura de autenticação do ASP.NET 2.0, o atributo userNamePasswordValidationMode será definido como MembershipProvider, o que nos obriga a definir o atributo membershipProviderName que, por sua vez, receberá uma string contendo o nome do membership provider que irá consistir a validação do usuário. Se reparar, ele recebe o mesmo valor definido no atributo name do elemento membership.

Antes de abordarmos a autorização, é necessário entender como configurar o certificado. Como falamos mais acima e você pode notar no Web.Config, existe um elemento chamado serviceCertificate, que é onde definimos o certificado que o serviço irá utilizar. Esse elemento possui basicamente quatro atributos que utilizamos para determinar o critério de busca pelo mesmo dentro do computador onde o serviço está rodando. Cada um desses atributos estão descritos na tabela abaixo:

Atributo Descrição
FindValue

Recebe uma string contendo o nome do certificado a ser procurado no repositório.

StoreLocation

Especifica o local do repositório onde o certificado está armazenado. Os possíveis valores para este campo são:

  • CurrentUser: especifica que a busca será realizada no repositório de certificados do usuário corrente.
  • LocalMachine: especifica que a busca será realizada no repositório de certificados da máquina local.
StoreName

Especifica o nome (tipo) do repositório. No nosso caso, utilizaremos a opção My, que indica que deve ser efetuada a busca no repositório de certificados pessoais.

X509FindType

Especifica por qual campo o valor informado na propriedade FindValue será procurado. No nosso caso, utilizaremos a opção FindBySubjectName que indica que a busca pelo certificado deverá ser realizada comparando o valor definido na propriedade FindValue com o subject name dos certificados armazenados no local especificado.

Com a autenticação devidamente configurada, chega o momento de analisarmos a autorização. Ainda dentro dos behaviors do serviço, temos um elemento chamado serviceAuthorization, que é utilizado para a configuração da autorização dentro do serviço WCF. Esse elemento possui dois atributos chamados principalPermissionMode e roleProviderName. O primeiro deles determina qual será o modo de autorização utilizado pelo serviço para controlar o acesso aos membros que utilizam a segurança declarativa (atributo PrincipalPermissionAttribute) ou imperativa. As opções possíveis são fornecidas através do enumerador PrincipalPermissionMode. As opções que ele fornece estão descritas na tabela abaixo:

Opção Descrição
Custom

Permite ao desenvolvedor especificar uma forma customizada de autorização, baseando-se em uma classe que implementa a interface IPrincipal, que deve ser vinculada à propriedade CurrentPrincipal.

None

CurrentPrincipal não definida.

UseAspNetRoles

Especifica que a autorização será baseada na Role Provider fornecida pelo ASP.NET 2.0.

UseWindowsGroups

A autorização será baseada nos grupos do Windows.

Como o foco do artigo é a integração de serviços WCF e a estrutura de autorização do ASP.NET 2.0, o atributo principalPermissionMode será definido como UseAspNetRoles, o que nos obriga a definir o atributo roleProviderName que, por sua vez, receberá uma string contendo o nome da role provider que irá consistir a autorização do usuário. Atente-se que ele recebe o mesmo valor definido no atributo name do elemento roleManager.

Observação: Quando habilitamos a segurança baseada em roles no ASP.NET 2.0, utilizando a arquitetura de Role Provider, o ASP.NET cria um objeto do tipo RolePrincipal e define o mesmo na propriedade CurrentPrincipal da classe Thread que está cuidando da execução do pedido. Quando o método IsInRole da classe RolePrincipal for invocado, ela ainda não possui conhecimentos das roles do usuário e invoca o método GetRolesForUser do provider, recuperando as roles do repositório para o usuário em questão e, em seguida, cria um cache internamente que evitará ir até o repositório toda vez que este método for chamado. Isso tem um comportamento ligeiramente diferente quando configuramos o serviço WCF para utilizar a arquitetura de roles do ASP.NET 2.0, pois o WCF utilizará uma classe customizada, chamada RoleProviderPrincipal, que está contida dentro do namespace System.ServiceModel.Security e implementa a Interface IPrincipal. Durante a execução de um serviço WCF, uma instância dessa classe é anexada à propriedade CurrentPrincipal da classe Thread. O grande problema com ela é que, ao invocar o método IsInRole a chamada é encaminhada para o método IsUserInRole do provider especificado. Isso quer dizer que, no caso do SqlRoleProvider, não será efetuado o cache das roles do usuário, aumentando assim o número de round-trips no banco de dados.

Com essa configuração relizada e o serviço já disponível no servidor (incluindo a instalação do certificado), o mesmo já pode ser invocado pelos clientes interessados em consumí-lo. Superficialmente, o que acontece nos bastidores durante a chamada ao serviço WCF, é que o cliente utilizará a chave pública (fornecida pelo serviço) para criptografar todas as mensagens que enviará ao serviço. Quando o serviço recebe essa mensagem, ele utiliza a chave privada para decriptografar a mensagem e, feito isso, o serviço WCF lê as credenciais do usuário e o autentica, permitindo assim, o acesso ao serviço.

Configuração do cliente

Assim como o serviço, também é necessário configurarmos o binding e o behavior do serviço WCF do lado do cliente e, além disso, necessitamos ter a chave pública do certificado exposta pelo serviço.

Para suportar a autenticação e proteção de mensagens mútuas, os serviços devem fornecer as credenciais para o chamador. Quando utilizamos a segurança a nível de transporte, as credenciais do serviço são negociadas através do protocolo de transporte. Quando utilizamos a segurança baseada em mensagens, podemos também especificar as credenciais do Windows ou um certificado, através do elemento serviceCredentials, como já vimos acima. Esse processo, chamado de negociação, pode ser realizado de forma out-of-band ou através de um handshake inicial.

Se desejar, você pode desabilitar a negociação das credenciais definindo para False o atributo negotiateServiceCredential do elemento message.

Para os tipos de credenciais diferentes de Windows, de alguma forma é necessário que o cliente tenha acesso à chave pública do certificado para que ele possa criptografar as requisições para o serviço. Isso é gerado automaticamente quando você adicionar uma referência para um serviço através do Visual Studio .NET 2005 (ou através do utilitário svcutil.exe) que, nas configurações do arquivo App.Config já está codificada uma versão pública do certificado, que é informada através do atributo encodedValue do elemento certificate que, por sua vez, está contido dentro da seção identity.

Depois da configuração do certificado, vamos falar sobre o binding que irá refletir exatamente a mesma configuração que fizemos no serviço do lado do servidor, ou seja, definiremos o atributo mode do elemento security para Message, e o atributo clientCredentialType do elemento message é definido também como UserName, ficando assim exatamente como a configuração estipulada no serviço.

Finalmente, temos a seção dos behaviors. Essa seção fornece um elemento chamado serviceCertificate. Esse elemento especifica o certificado que será utilizado pelo cliente. Esse elemento tem um sub-elemento chamado authentication que, por sua vez, possui um atributo chamado certificateValidationMode, o qual é extremamente importante na validação do certificado. Esse atributo recebe uma das opções fornecidas pelo enumerador X509CertificateValidationMode. Essas opções estão descritas na tabela abaixo:

Opção Descrição
ChainTrust

Esta opção instrui o WCF a confiar no certificado somente se ele foi emitido através de um orgão como a VeriSign.

Custom

O certificado é validado através de um validador customizado. Esse validador deve ser customizado pelo desenvolvedor, herdando o seu validador a partir da classe X509CertificateValidator.

None

Validação do certificado está desabilitada.

PeerOrChainTrust

Esta opção permite o WCF confiar em um certificado somente se ele foi emitido através de um orgão como a VeriSign ou se ele estiver dentro do repositório Trusted People do cliente.

PeerTrust

O certificado é validado se ele estiver dentro do repositório Trusted People do cliente.

Com isso, o arquivo de configuração do cliente, App.Config, fica da seguinte forma:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint
        name="DevMindsService"
        address="http://localhost:2969/DevMinds.WebHostingCS/DefaultService.svc"
        behaviorConfiguration="ClientBehavior"
        binding="wsHttpBinding"
        bindingConfiguration="ClientBinding"
        contract="DevMinds.ClientUICS.localhost.IServiceContract">
        <identity>
          <certificate
            encodedValue="AwAAAAEAAAAUAAAAUJNV9NaKKu1ufLDJSReysIhSHokgAAAAAQAAALkBAAAwgg
                          G1MIIBY6ADAgECAhD8doIP5AdsgEtkMqF7eimCMAkGBSsOAwIdBQAwFjEUMBIG
                          A1UEAxMLUm9vdCBBZ2VuY3kwHhcNMDcwNDAzMTMxNjM3WhcNMzkxMjMxMjM1OT
                          U5WjAYMRYwFAYDVQQDEw1JQUNlcnRpZmljYXRlMIGfMA0GCSqGSIb3DQEBAQUA
                          A4GNADCBiQKBgQC/+fgp/kuwIVmhpJW2XFDdFQKT0hDRhbdsIvV8ikrUANCuii
                          ufPvo10FqMrVPqeowy+c2XxYpQ6lGeMx4QWVeZC9LdGCzli1UNKdarT6wBiBsK
                          qAxmb6lJkKnkL4HycepYckbT2H65oIDDnfFNv1TYUP1/gPHoz6bsIFCa4eXCMw
                          IDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEUMBIG
                          A1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwCQYFKw4DAh0FAA
                          NBAGoc8S3JfsoQPRqwCIO0WYopE8vJ92IYLkxQ5k68BuN1mLUftbVoEv/TO/Y5
                          7ToFWzTsDMT5BfG37G1Y0P5Iq+Q=" />
        </identity>
      </endpoint>
    </client>
    <bindings>
      <wsHttpBinding>
        <binding name="ClientBinding">
          <security mode="Message">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="ClientBehavior">
          <clientCredentials>
            <serviceCertificate>
              <authentication certificateValidationMode="PeerTrust" />
            </serviceCertificate>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Invocando os métodos

Como as configurações já estão definidas tanto do lado do servidor quanto do cliente, chega o momento em que devemos invocar os métodos que o serviço disponibiliza.

Basicamente, a chamada para os métodos não mudando absolutamente em nada. A única diferença é que devemos, antes de invocar o(s) método(s), informar o nome de usuário e senha para o proxy, que enviará essas credenciais para o serviço quando o método for invocado. O proxy expõe uma propriedade chamada ClientCredentials do tipo ClientCredentials. Essa classe possui uma propriedade chamada UserName do tipo UserNamePasswordClientCredential.

Essa classe habilita o usuário a configurar o userName e o password que o WCF deve utilizar quando estiver efetuando a autenticação. Essa simples classe possui duas propriedades que recebem uma string. Essas propriedades, auto-explicativas, são chamadas de Password e UserName. O exemplo (Windows Application) abaixo ilustra como devemos configurar essas propriedades, informando o userName e password:

using (localhost.ServiceContractClient c = new localhost.ServiceContractClient())
{
    c.ClientCredentials.UserName.UserName = "Claudia";
    c.ClientCredentials.UserName.Password = "123456";
    MessageBox.Show(c.DataAtual().ToString());
    MessageBox.Show(c.BoasVindas());
}

Recapitulando o código de implementação do serviço, o método BoasVindas somente poderá ser executado por usuários que estão contidos dentro da role User. Já o método DataAtual somente pode ser executado por usuários que estão contidos dentro da role Manager.

Como podemos reparar, passamos como userName o nome Claudia, que executa os dois métodos com sucesso pois este usuário está contido dentro das duas roles. Se alterarmos o userName para Juliano, que está contido somente dentro da role User, ao tentarmos executar o método DataAtual, que é permitido somente a usuários que estão contidos dentro da role Manager, uma exceção será atirada informando que o acesso foi negado.

Conclusão: O WCF possui uma grande variedade de formas de autenticação e autorização para os serviços. Neste artigo abordamos uma das formas de segurança que o WCF expõe, que é justamente a integração da autenticação e autorização com toda a infraestrutura que o ASP.NET 2.0 já fornece para os desenvolvedores de aplicações web e, como é o intuíto do WCF, toda a configuração para integrar com o ASP.NET 2.0, bem como a maioria da segurança do WCF, baseado em plug-and-play.