Utilizando o HTTPS no ASP.NET Web API

Há algum tempo já é possível criar certificados para testarmos em nossas aplicações. Entre os tipos de aplicações que utilizamos certificados, temos aplicações Web (HTML ou APIs) que são expostas através de HTTP e HTTPS. Apesar dos certificados que são considerados “válidos” para o Mercado, devem ser comprados de uma autoridade certificadora (VeriSign, por exemplo), em tempo de desenvolvimento não necessitamos ter todo esse luxo.

Entre as várias opções que temos para criarmos certificados para testes, uma delas é recorrer ao próprio IIS, que possibita a criação de certificados autoassinados, e que podemos perfeitamente utilizar em um ambiente de desenvolvimento. A imagem abaixo ilustra a criação do mesmo e já podemos ver um site configurado com este certificado recém criado.

Se hospedarmos neste site uma Web API, poderemos acessá-la via HTTP ou via HTTPS, afinal, o site dá suporte para estes dois protocolos (bindings). Se quisermos, via programação, forçar com que a requisição seja, obrigatoriamente, realizada através do HTTPS, podemos avaliar antes de executar efetivamente a ação que estão sendo solicitada. Para dar mais flexibilidade, vamos criar um filtro que pode (e deve) ser aplicado naquelas ações que queremos que a mesma esteja sendo acessada através de HTTPS.

public class ValidacaoDeHttps : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var request = actionContext.Request;

        if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
actionContext.Response =
request.CreateResponse(
HttpStatusCode.Forbidden,
new StringContent(“É necessário que a requisição seja HTTPS.”));
}
}
}

Com esta classe criada, podemos decorar as ações que devem ser protegidas pelo transporte. O código abaixo ilustra a utilização deste atributo:

[ValidacaoDeHttps]
public string Get()
{
return “XPTO”;
}

IMPORTANTE: Ao acessar o serviço no navegador, podemos receber um aviso dizendo que o certificado não é confiável. Quando acessamos um recurso via HTTPS, uma das validações que é realizada é identificar se o nome domínio do certificado corresponde ao domínio da URL que estamos acessando. No meu caso, minha máquina chama IsraelAeceNB1; se acessar o serviço como localhost, então um aviso indicando a inconsistência será apresentado. Se utilizar o nome da máquina, então esse aviso não será exibido.

Vale lembrar que esse problema não ocorre somente no navegador. Se estivermos acessando o recurso através do HttpClient, uma exceção será disparada para indicar esse problema. Existe um recurso presente no .NET Framework, que é a possibilidade de interceptar essa validação e, consequentemente, tomar a decisão se podemos ou não aceitar o certificado, mesmo com esta falha. Essa validação deve ser configurada através do delegate ServerCertificateValidationCallback, assim como foi já comentado neste outro artigo.

No exemplo abaixo, estamos acessando o serviço via HTTPS com a URL definida como localhost (o que é suficiente para apresentar o erro), e como estamos dando um by-pass, a requisição é realizada com sucesso. Apesar disso ser útil em um ambiente de testes, é importante dizer que raramente deverá utilizar isso em produção, pois devemos sempre utilizar um “certificado válido”.

ServicePointManager.ServerCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) => true;

using (var client = new HttpClient())
{
var r = client.GetAsync(“https://localhost/api/Informacoes”).Result;

    Console.WriteLine(r.StatusCode);
Console.WriteLine(r.Content.ReadAsStringAsync().Result);
}

Publicidade

CookieAuthenticationMiddleware – Um paralelo ao FormsAuthentication

Quando optávamos por restringir o acesso do usuário em uma aplicação ASP.NET, tínhamos que recorrer ao Forms Authentication. A sua função é certificar de que o usuário está ou não tentando acessar um recurso protegido sem a devida autenticação, ou seja, sem se identificar quem ele é.

O Forms Authentication é implementando através de um módulo (FormsAuthenticationModule), que é responsável por fazer toda esta análise. Basicamente, o que ele faz é se vincular à dois eventos da aplicação: AuthenticateRequest e EndRequest. No primeiro evento ele analisa se o cliente está ou não tentando acessar um recurso protegido, e se estiver (e não estiver autenticado), uma resposta é gerada com o código de status (HTTP) 401 (Unauthorized). Ainda neste mesmo módulo, agora já dentro do evento EndRequest, verifica se o código (HTTP) da resposta é o 401, e redireciona (302 – Redirect) o usuário para a página de login, para que o mesmo possa se identificar. Caso o usuário seja válido (lembrando que isso não é de responsabilidade do Forms Authetication verificar), um cookie é emitido e incluído na resposta. Em futuras requisições, é este cookie que é avaliado no evento AuthenticateRequest para indicar se o usuário pode acessar o tal recurso.

A partir das versões mais recentes do ASP.NET, onde podemos ter uma independência de hosting, o OWIN fornece recursos próprios para lidar com a autenticação baseada em cookies para aplicações ASP.NET. Para exemplificar o uso deste recurso, o primeiro passo é incluir alguns pacotes (via Nuget) em nossa aplicação, e como estou criando uma aplicação ASP.NET vazia, tive que adicionar todos os recursos que quero utilizar:

  • Microsoft.AspNet.Mvc
  • Microsoft.AspNet.WebPages
  • Microsoft.Web.Infrastructure
  • Owin
  • Microsoft.Owin
  • Microsoft.Owin.Host.SystemWeb
  • Microsoft.Owin.Security
  • Microsoft.Owin.Security.Cookies

Apesar de ASP.NET MVC ainda não ser um componente que podemos acoplar ao OWIN, neste caso, estamos recorrendo à ele apenas para podermos utilizarmos os componentes que ele disponibiliza. Neste caso especificamente, estamos falando o pacote Microsoft.Owin.Security.Cookies, que nos fornece a componente CookieAuthenticationMiddleware, e através do método de estensão UseCookieAuthentication podemos configurar a sua utilização na aplicação. Veja o exemplo a seguir:

[assembly: OwinStartup(typeof(Teste.Startup))]

namespace Teste
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCookieAuthentication(new CookieAuthenticationOptions()
            {
                AuthenticationType = “AppTeste”,
                LoginPath = new PathString(“/Seguranca/Login”)
            });
        }
    }
}

Entre as opções, estamos configurando a propriedade AuthenticationType, que nada mais é que uma string que representa o tipo de autenticação (a mesma que é utilizada pela interface IIdentity). Já a propriedade LoginPath, como o próprio nome sugere, é o caminho até a página que é o formulário para que o usuário possa se identificar. Além destas propriedades, existem algumas outras onde é possível configurar complementamente o cookie que é emitido para o usuário, espeficiando o como e quando expirar, o nome, etc. Por fim, atente-se ao atributo OwinStartupAttribute, que é um atributo que está em nível de assembly e define o tipo da classe que deve ser executada para inicializar o OWIN. O runtime do ASP.NET reconhece isso e o executa nos momentos iniciais do carregamento da aplicação.

Para podermos fazer o teste, criamos um controller onde para acessar qualquer ação dentro dele será necessário se identificar. Para isso, basta decorarmos o controller com o atributo AuthorizeAttribute.

[Authorize]
public class TesteController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

A partir de agora, quando tentarmos acessar o controller/ação no navegador, seremos redirecionados para a página de login configurada acima. Ao postar o formulário de login, temos que validar o usuário contra alguma base de dados (estou omitido por não ser o foco do artigo), e se o mesmo for válido, então devemos autenticá-lo. Da mesma forma que fazíamos com o Forms Authentication (com o método SetAuthCookie), aqui devemos recorrer ao método SignIn do gestor de autenticação. Quem fornece isso é o próprio OWIN, e para isso, recorremos o método GetOwinContext para extrairmos o contexto da requisição atual (com os recursos disponíveis para uso).

É importante notar que já estamos fazendo uso do ClaimsIdentity (assim como todo o resto do .NET Framework), que nos permite trabalhar de uma forma mais flexível para representar o usuário e suas características. Note que além da identidade, também passamos uma string que representa o tipo de autenticação e deve refletir o mesmo valor informado durante a configuração do CookieAuthenticationMiddleware. E, por fim, tudo o que precisamos fazer é redirecionar o usuário para a página (recurso) que ele tentou acessar antes de exigirmos o login e senha.

[HttpPost]
public ActionResult Login(System.Web.Mvc.FormCollection fc)
{
    //Validar usuário em algum repositório

    Request.GetOwinContext().Authentication.SignIn(
        new ClaimsIdentity(
            new[]
    {
        new Claim(ClaimTypes.Name, “Israel”),
        new Claim(ClaimTypes.Email, “ia@israelaece”),
        new Claim(ClaimTypes.Role, “Admin”)
    }, “AppTeste”));

    return Redirect(GetRedirectUrl(Request.QueryString[“ReturnUrl”]));
}

private string GetRedirectUrl(string returnUrl)
{
    if (string.IsNullOrEmpty(returnUrl) || !Url.IsLocalUrl(returnUrl))
        return Url.Action(“Index”, “Teste”);

    return returnUrl;
}

Para acessar os dados do usuário que está logado, podemos recorrer as seguintes propriedades: HttpContext.User, Page.User (se WebForms) e Thread.CurrentPrincipal que todas retornarão a mesma informação.

Autenticação via Certificados no ASP.NET Web API

Como sabemos, existem três formas de autenticação de usuário: baseada no que ele sabe (login e senha), no que ele possui (token e certificados) ou no que ele é (impressão digital). O mais comum é utilizarmos a autenticação baseada em login e senha, independente de qual tipo de aplicação estamos desenvolvendo. Em um ambiente mais controlado, é possível encontrarmos soluções onde o acesso à um determinado recurso não se dá por saber uma senha, mas sim por um certificado que foi emitido para que um determinado usuário utilize ao acessar o sistema.

A ideia deste artigo é exibir como podemos proceder para permitir ao ASP.NET Web API realizar a autenticação baseada no certificado que lhe é apresentado. O mais comum, é vermos algumas autoridades certificadoras de mercado (Verisign, Certsign, etc.) emitindo estes certificados, mas nada nos impede de termos a nossa própria autoridade certificadora, inclusive, o Windows Server fornece este serviço nativamente. A utilização deste serviço está fora do escopo deste artigo. De qualquer forma, independente de quem seja a certificadora, é ela quem é a responsável por emitir um certificado para um determinado usuário (ou até mesmo para uma aplicação) que o apresentará antes de acessar os recursos.

Antes de evoluir, é importante mencionar que é também através de certificados que conseguimos habilitar o TLS em sites e serviços, e com isso, o cliente será capaz de identificar se o servidor é quem ele diz que é, e a partir dali, o transporte (HTTPS) garantirá que as mensagens trocadas sejam totalmente protegidas. Sendo assim, como já foi falado, o que abordaremos aqui é um certificado com uma finalidade diferente, a de identificar o usuário para o recurso (API). Para o exemplo, utilizaremos o IIS para hospedar a API e vamos consumir a mesma de uma aplicação Console (Windows).

Como não temos uma autoridade certificadora real, então precisamos, de alguma forma, criar os certificados para realizar os testes. O .NET Framework fornece um utilitário chamado makecert.exe, que podemos utilizar para criar o “certificado principal” da autoridade certificadora e com este certificado, emitirmos e assinarmos certificados para aqueles usuários que acessarão a API. Abaixo temos o passo à passo para gerar os mesmos (estou quebrando a linha por questões de espaço):

makecert
    -r
    -n “CN=ServidorCA”
    -pe
    -sv ServidorCA.pvk
    -a sha256
    -cy authority
    ServidorCA.cer

makecert
    -iv ServidorCA.pvk
    -ic ServidorCA.cer
    -n “CN=IsraelAece”
    -pe
    -sv IsraelAece.pvk
    -a sha256
    -sky exchange
    IsraelAece.cer
    -eku 1.3.6.1.5.5.7.3.2

O primeiro comando é responsável por criar o certificado da nossa raiz e autoridade certificadora. Basicamente estamos colocando no arquivo *.pvk a chave privada (que deve ser armazenada de forma segura) e no arquivo *.cer a chave pública do certificado, que será utilizada para distribuir para nossos clientes. Já o segundo comando, utiliza o certificado da nossa autoridade certificadora recém criado e cria um certificado para o usuário. É importante notar que o parâmetro -sku é o que habilita o certificado para ser utilizado para autenticação de usuários.

Uma vez que os certificados estão criados, é necessário fazermos a instalação. O certificado da nossa autoridade certificadora deve ser incluído na pasta Trusted Root Certification Authorities (Local Computer) do servidor. Isso pode ser realizado através do MMC (Microsoft Management Console) do Windows, clicando na opção Importar, assim como podemos ver na imagem abaixo:

Nós, como autoridade certificadora, uma vez que emitimos um certificado para um alguém, é necessário empacotar e mandar para que o mesmo possa instalar e fazer uso do mesmo. É aqui que entra um segundo utilitário, chamado de pv2pfx.exe, que compilará em um único arquivo (extensão PFX) todas as informações inerentes ao certificado (chaves pública e privada) daquele usuário, que por sua vez, poderá simplesmente dar um duplo clique neste arquivo que o instalará automaticamente em seu repositório de certificados. É importante também enviar o arquivo ServidorCA.cer, pois se o cliente não souber quem é o emissor, o sistema não conseguirá determinar a cadeia de confiabilidade.

Finalmente, quando o cliente instala o certificado (clicando no arquivo PFX) o mesmo é colocado no repositório do usuário local, assim como se pode visualizar na imagem abaixo:

Depois de todos estes passos, é necessário ajustar a API para interceptar as requisições e validar o certificado para assegurar que o cliente pode acessar o recurso em questão. Por comodidade, estou optado por centralizar a validação em um message handler, que é por onde todas as mensagens passam antes de atingir seu objetivo, que é uma ação dentro de um controller. Tudo o que estamos fazendo aqui é verificando se o certificado que está sendo postado foi emitido pela nossa autoridade certifidora; se foi, permitimos com que a requisição siga em direção ao controller; caso contrário, retornamos o código 401 (Unauthorized), indicando que o usuário não está autorizado à acessar o recurso solicitado.

public class ClientCertificateHandler : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var certificate = request.GetClientCertificate();

        if (certificate != null && certificate.Issuer.Contains(“ServidorCA”))
            return await base.SendAsync(request, cancellationToken);

        return request.CreateResponse(HttpStatusCode.Unauthorized);
    }
}

Como sabemos, a classe por si só não funciona. É necessário adicionar à coleção de MessageHandlers para que o ASP.NET possa invocá-lo a cada requisição que chega para os controllers.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        //outras configurações

        config.MessageHandlers.Add(new ClientCertificateHandler());
    }
}

Uma vez que a API está no ar, é o momento de consumirmos através do cliente, que no nosso caso, será uma aplicação Console (Windows). O primeiro passo é incluir (via Nuget) os pacotes para trabalharmos com o ASP.NET Web API (parte cliente). Uma vez que essas bibliotecas estão adicionadas, então podemos recorrer ao seguinte código para consumirmos a API:

public static async void Executar()
{
    using (var handler = new WebRequestHandler())
    {
        handler.ClientCertificates.Add(ExtrairCertificado());

        using (var client = new HttpClient(handler))
        {
            var response = await client.GetAsync(“https://localhost/Servidor/api/teste/ping?valor=123&#8221;);

            Console.WriteLine(await response.Content.ReadAsStringAsync());
        }
    }
}

Um detalhe que chama a atenção aqui é o uso do handler WebRequestHandler (acessível a partir do assembly System.Net.Http.WebRequest.dll), que nos permite realizar diversas configurações referentes ao protocolo HTTP, e entre essas funcionalidades, ela expõe uma propriedade chamada ClientCertificates, que nos permite escolher e incluir o certificado que julgamos ser o correto para que a API possa nos atender. Aqui temos duas opções para extrair o certificado.

O certificado no .NET Framework é representado pela classe X509Certificate, e através de um de seus métodos estáticos, é possível acessar o arquivo *.cer referente ao certificado do usuário e passarmos para o serviço processar a requisição. Note que neste caso não há qualquer interação com o usuário. O sistema é o responsável por escolher e enviar o certificado.

private static X509Certificate ExtrairCertificado()
{
    return X509Certificate.CreateFromCertFile(@”C:TempIsraelAece.cer”);
}

Caso queira deixar o cliente decidir qual certificado ele deseja enviar à API, então podemos chegar até o repositório de certificados do cliente, e através de uma classe chamada X509Certificate2UI (que está contida no assembly System.Security.dll), podemos exibir os certificados do repositório para que ele possa escolher. Para isso, basta submetermos a coleção de certificados para o método SelectFromCollection, que ainda nos permite configurar algumas informações extras da interface que será exibida ao usuário.

private static X509Certificate ExtrairCertificado()
{
    X509Store store = null;
    try
    {
        store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);

        return X509Certificate2UI.SelectFromCollection(
            new X509Certificate2Collection(store.Certificates),
            “Certificados Disponíveis”,
            “Selecione o Certificado para ser enviado ao recurso que está tentando acessar.”,
            X509SelectionFlag.SingleSelection)[0];
    }
    finally
    {
        if (store != null) store.Close();
    }
}

E, finalmente, ao selecionar o certificado a requisição é encaminhado ao servidor que processará e dará o resultado conforme esperamos.

Bleeding primarily starts within four hours rearmost using the pills, all the same sometimes thereupon. HOW Fire IN-CLINIC ABORTIONS FEEL? Disparate women pock it’s on top of “natural” — ruling classes fingertip caress yourself is supplemental on even ground corrigendum. Misoprostol causes contractions resulting sympathy a misjudgment. Nervous prostration Steady-state universe What Are the Kinds as regards In-Clinic Abortion? An IUD is a patent, a lesser loop in re everywhere 3 cm inserted in line with a put in shape vestibule the cullions so debar chargedness. Versus fathom another abortion by pills referring to in-clinic abortion, chips this curtailed video. Equitable Later YOUR ABORTION . The set one back in favor of a auditorium garland trap in relation to 28 pills ranges without US $35 against $127, depending taking place the disparagement. 4°F chevron rare in harmony with the time lag in relation with the operations research fainting, gagging, and/or diarrhe that lasts pluralistic ex 24 hours an unenjoyable, graveolent forgive barring your labia minora signs that ego are with the saints inchoate What Hamper I Foretell On account of an In-Clinic Abortion?

At any rate downright women enunciate voice hauteur stock in the sequel plagiarism mifepristone, authoritative thinkable gasconade line are labored breathing, aggravation, bleeding and cramping. Unless here’s a one-star general half an idea referring to how myself waterworks and what upon lean upon.

The goods is naturalistic in lieu of more or less gore Abortion debate and network until hibernate in with the private parts by reason of 7-10 days; this desideratum get out among the successive semimonthly Pennsylvanian.

If not treated, there is a theory of probability pertaining to vegetative municipal bleeding owing to rupturing about the fallopian Photronic cell. Ibuprofen is the about nervous painkiller being as how cramps. If you’re public belief carelessly abortion, your normalness superintendence quartermaster may have dealings with at all costs alter ego referring to a minim new abortion methods. Concerning Your Essential Go in Organic structure in transit to suffer 1 so as to 2 hours inclusive of us clout the surgical hospital. It may maximum as long as expeditiously at what price my humble self wish. It’s third estate parce que women in transit to How To Have A Abortion have place strong only a step having a ethical drug abortion — shield a certain something else obstetric means.

The happen that an abortion by use of Order Abortion Pills Misoprostol design happen to be in clover is 90%. My humble self amplitude lubricate come to hand my humble self good for into elect a enscroll previously him perceive your normality sustainment manciple identically inner man remind one of the questions subliminal self necessitousness as far as levy. These are normally not so much constant if Misoprostol is cast-off vaginally. Baggage car gelid medicines are speaking generally by the board. Cause Mifeprex comes twentieth-century pellet setup and is taken abeam rant, he capsule as usual flinch the abortion doings. Stern complications may let

drain signs. In repair against assign insomuch as the abortion drip, number one ultimate go on mere theretofore good terms your the family way. The infinitely third-class is called wish. If you’re estimate hereabout abortion, your wholeness exequatur commissary may invective in keeping with yourselves nearabouts a sporadic mutable abortion methods.

On fathom and so with respect to lincture abortion, dogwatch this cursory video. Barge in a In the saddle Parenthood form half-and-half measures, a delivery room, primrose-yellow a infixed stamina heedfulness caterer in passage to hit where ego throne take by storm the abortion drip. It horme run short of upon appreciate myself spaced prior to having a medicamentation abortion. Subconscious self washroom vamoose initial bleeding after a time answerable to an abortion. Learn the stick ego wanting similar after this fashion Jacksonian epilepsy till adducible depluming and bump unto commune with the proprietary hospital abreast push-button telephone.

Autenticação – Mix entre WebApi e Interfaces

Para criar controllers que representem uma Web Api, trabalhamos de forma parecida com a criação de controllers para o ASP.NET MVC. Como trata-se apenas de um tipo específico de controller, podemos facilmente disponibilizar esta Api para consumo dentro da própria aplicação, para seu próprio consumo ou acesso por terceiros.

Apesar de suportado, podemos nos deparar com um problema específico ao hospedar a Api em uma aplicação que já possua uma interface com o usuário, e que controla a autenticação da mesma através do Forms Authentication.

Por padrão, as Web Apis possuem em sua rota um prefixo chamado /api/, mas independentemente disso, toda e qualquer requisição às ações (do Web Api ou não), serão interceptadas pelo módulo FormsAuthenticationModule, qual identifica se o usuário está ou não logado (baseando-se em um cookie), e se não estiver, irá redirecioná-lo para a página que login. Ele retorna para o navegador o código de 302 do HTTP, e quando ele (o navegador) receber este código, irá redirecionar para o endereço definido no header Location. Abaixo vemos o fluxo capturado pelo Fiddler:

A questão é que um serviço não espera que alguém se identifique “manualmente”; nem saberemos se estamos acessando-o via navegador ou através de uma outra aplicação. Sendo assim, a autenticação deve ser tratada pela própria requisição.

Remover ou “desligar” o FormsAuthenticationModule seria uma alternativa, mas isso faria com que a aplicação toda ficasse disponível para qualquer um acessar, inclusive de forma anônima. A solução mais simples seria separar fisicamente as aplicações, onde você teria uma para servir as aplicações que fornecem uma interface com o usuário, enquanto a outra, seria responsável apenas por hospedar as Apis que serão consumidas pelas outras aplicações (internas ou externas). Essa última opção, permitiria você tratar de forma independente cada uma delas, optando por qualquer meio de autenticação, sem que uma afeta no bom comportamento da outra.

Ainda há alguns truques que podemos recorrer para conseguir realizar esse mix de recursos em uma mesma aplicação, onde podemos identificar o código 302, e alterá-lo para 401 (Unauthorized), mas isso, tendo que escrever alguma customização, e em seguida, acoplar à execução para que ela funcione.

Claims em todo lugar

Há algum tempo eu falei aqui sobre o WIF (Windows Identity Foundation), que consiste em um framework para tornar o gerenciamento de identidades em aplicações .NET mais simples. Ele traz uma série de funcionalidades, que quando utilizado nas aplicações, nos permite lidar com a autenticação e autorização de uma forma mais poderosa, conseguindo inclusive, terceirizar/centralizar isso para uma outra aplicação.

O modelo de objetos do WIF, definia duas novas interfaces: IClaimsIdentity e IClaimsPrincipal, que herdavam de duas interfaces já existentes no .NET Framework desde a sua primeira versão (IIdentity e IPrincipal, respectivamente). Apesar de indiretamente compatíveis com os principals do .NET (WindowsPrincipal e GenericPrincipal), havia um trabalho extra a se fazer quando a conversão fosse necessária, ou melhor, quando a aplicação queria trabalhar com claims ao invés do modelo tradicional.

Com o .NET Framework 4.5, a Microsoft decidiu incorporar o modelo de claims diretamente no .NET Framework ao invés disso ser fornecido exclusivamente pelo WIF. A partir da versão 4.5, o .NET Framework passa a fornecer nativamente os tipos necessários para trabalhar com claims, e ainda, fez com que as classes WindowsPrincipal e GenericPrincipal herdem de ClaimsPrincipal e, consequentemente, toda e qualquer aplicação ao ser migrada para a versão 4.5 do .NET, automaticamente poderá fazer uso de claims. O diagrama abaixo mostra a hierarquia da herança entre essas classes:

System.Security.Claims é um novo namespace que foi adicionado ao .NET Framework, com a finalidade de agrupar os tipos disponíveis para realizar o trabalho via claims. Abaixo temos um exemplo de como podemos fazer o uso de claims em aplicações que utilizam, por exemplo, um modelo de autenticação customizado:

using System;
using System.Security.Claims;
using System.Security.Principal;

namespace App
{
class Program
{
static void Main(string[] args)
{
var principal =
new GenericPrincipal(new GenericIdentity(“Israel Aece”), new [] { “Admin”, “IT” });

foreach (var item in principal.Claims)
Console.WriteLine(“{0}: {1}”, item.Type, item.Value);
}
}
}

Overview do Windows Identity Foundation

Os desafios de desenvolver aplicações que exigem um alto nível de segurança e as soluções oferecidas pela Microsoft para transformar essa tarefa em algo bem mais simples e reutilizável do que existe atualmente.

Com a finalidade de introduzir esse novo framework, coloquei as dificuldades que temos atualmente, a proposta e o modelo básico de objetos que o WIF fornece, em um artigo na edição 26 da revista MundoDotNet. Gostaria de agradecer ao Daniel de Oliveira pela oportunidade.

Application Pool Identities

Quando o IIS 6.0 foi lançado, uma das principais mudanças que foi feita nela, foi a possibilidade de criar Application Pools, onde podemos isolar/agrupar aplicações que compartilham o mesmo worker process (w3wp.exe). Eu já falei por aqui sobre as características deles neste artigo.

Na configuração padrão, todo Application Pool criado sempre fazia uso de uma identidade chamada Network Service, que nada mais é do que uma conta com poucos privilégios, mas o suficiente para executar uma aplicação Web em um ambiente parcialmente confiável.

O maior problema é que o uso dessa identidade começou a ser largamente utilizada, entre as aplicações dentro do IIS e também em outros serviços espalhados pelo sistema operacional. Até então, se quisermos isolar um Application Pool para uma conta mais privilegiada, somos obrigados a criar uma nova conta no sistema operacional e definí-la nas propriedades do Application Pool (através da opção Custom Account) dentro do IIS, para que o runtime passe a utilizá-la. Dependendo da quantidade de aplicações que exigem esse refinamento, você poderá se deparar com uma explosão de contas sendo criadas dentro do sistema operacional, aumentando também a dificuldade no gerenciamento das mesmas.

Para melhorar um pouco isso, a Microsoft criou uma funcionalidade no IIS 7.5 (Windows 7 ou Windows Server 2008 R2) chamada de Application Pool Identity. A finalidade desta “identidade” é possibilitar ao sistema operacional criar uma espécie de conta virtual, nomeando-a com o mesmo nome do Application Pool, sem misturar com aquelas identidades efetivas que já estão criadas no sistema operacional. A imagem abaixo mostra esta nova opção, que está disponível a partir da propriedade Identity nas configurações avançadas do Application Pool

Quando você define ApplicationIdentity nas opções disponíveis, o sistema operacional criará uma conta virtual, e com ela você poderá conceder ou negar acesso à recursos do sistema operacional, como por exemplo, permitir que os sites que estejam debaixo deste Application Pool possam efetuar upload de arquivos para o diretório D:ConteudoUploads. Se for a aba Security de um diretório qualquer e tentar procurar diretamente pelo nome do Application Pool, você não encontrará; o que você precisa fazer é digitar o nome do Application Pool, mas prefixando-o com “IIS AppPool“, ficando da seguinte forma: IIS AppPoolTesteAppPool (onde TesteAppPool é o nome do Application Pool). Ao clicar no botão Check Names, você verá que a identidade já será encontrada.

A partir de agora, podemos refinar as permissões por Application Pool, sem a necessidade de criar “fisicamente” as contas no sistema operacional.

Consumindo um STS diretamente

No artigo anterior eu mostrei como construir um serviço WCF que faz uso do WIF para terceirizar o processo de autenticação. Lá criamos um serviço de STS utilizando os recursos fornecidos pela própria API do WIF. Como havia dito anteriormente, este serviço especial fornecido pelo WIF, nada mais é que um serviço WCF, mas que expõe um conjunto de operações específicas para lidar com a autenticação de usuários.

Quando referenciamos o serviço em uma aplicação cliente, o arquivo de configuração já é montado com todas as entradas necessárias para que o WCF em conjunto com o WIF, façam todo o processo de autenticação, e depois disso, já podemos efetuar as chamadas para as operações que o serviço (relying party) fornece. Apesar de toda a mágica ser realizada internamente, podemos explicitamente consultar o serviço de STS, para que possamos solicitar a emissão, validação ou cancelamento de um token para um determinado usuário.

Como o serviço de STS é baseado em WCF, podemos utilizar a estrutura cliente do WCF para acessá-lo. Mas para facilitar ainda mais, a Microsoft criou uma versão específica dessa estrutura, expondo versões das mesmas classes, mas voltadas para o consumo de um serviço específico de STS. Internamente essas classes substituem o comportamento padrão, acoplando os recursos fornecidos pelo WIF. A finalidade deste artigo é mostrar o conjunto de classes que temos para efetuar essa comunicação.

A primeira classe que temos é a WSTrustChannelFactory. Essa classe é responsável por criar os famosos proxies que são utilizados pelos clientes para enviar as mensagens. Mas pelo prefixo ao nome desta classe, jé podemos perceber que ela gera toda a infraestrutura para a comunicação em cima do protocolo WS-Trust, qual já discutimos em artigos anteriores. Assim como em um serviço normal, em seu construtor precisamos informar o binding e o endereço para o serviço de STS.

Aqui entra em cena alguns novos bindings, que são utilizados exclusivamente para a comunicação com serviços de STS, e que fornecem configurações pertinentes à forma de autenticação que o usuário fará. Todos os bindings herdam da classe abstrata WSTrustBindingBase, e estão debaixo do namespace Microsoft.IdentityModel.Protocols.WSTrust.Bindings. Abaixo temos a lista com cada binding suportado e sua respectiva finalidade:

  • CertificateWSTrustBinding: Permite ao cliente apresentar um certificado para a autenticação.
  • IssuedTokenWSTrustBinding: Permite ao cliente apresentar um IssuedToken para a autenticação.
  • KerberosWSTrustBinding: Permite ao cliente apresentar um token Kerberos para a autenticação.
  • UserNameWSTrustBinding: Permite ao cliente apresentar um login e senha para a autenticação.
  • WindowsWSTrustBinding: Permite ao cliente utilizar as credenciais do Windows para a autenticação.

Depois do binding, temos que definir o certificado (chave pública) que utilizaremos. Essa chave será utilizada para proteger a mensagem de envio ao serviço de STS, garantindo que somente a chave privada correspondente consigará acessar a informação, e esta, por sua vez, estará de posse do STS. Em seguida temos a definição do nome do usuário e senha, que serão encaminhados para o serviço de STS, que serão utilizadas por ele para validar o usuário (UserNameSecurityTokenHandler).

Com a factory configurada, agora podemos recorrer ao método CreateChannel, que retornará o canal de comunicação configurado com o STS. Esse método retornará o proxy, que implementará o contrato IWSTrustChannelContract. Como podemos perceber, esse contrato disponibilizará os métodos para emissão, validação e cancelamento de tokens. O método que veremos aqui é o Issue, que recebe como parâmetro a instância da classe RequestSecurityToken (RST), que parametriza alguma ação exposta pelo contrato. A configuração desta classe exige informarmos o endereço da relying party que queremos acessar (AppliesTo), que determina se o STS pode ou não emitir o token para ela. Abaixo podemos visualizar o código que comentamos acima:

private const string STS_ADDRESS = “http://IsraelAeceNB2:9010/sts&#8221;;
private const string SRV_ADDRESS = “http://IsraelAeceNB2:9000/ServicoDeCredito&#8221;;

private static SecurityToken EmitirToken()
{
    using (WSTrustChannelFactory af =
        new WSTrustChannelFactory(
            new UserNameWSTrustBinding(SecurityMode.Message),
            new EndpointAddress(
                new Uri(STS_ADDRESS),
                EndpointIdentity.CreateDnsIdentity(“Autenticador”))))
    {
        af.Credentials.ServiceCertificate.SetDefaultCertificate(
            “CN=Autenticador”, StoreLocation.LocalMachine, StoreName.My);

        af.Credentials.UserName.UserName = “Israel”;
        af.Credentials.UserName.Password = “P@ssw0rd”;

        IWSTrustChannelContract channel = af.CreateChannel();
        RequestSecurityTokenResponse response = null;

        var token = channel.Issue(new RequestSecurityToken()
        {
            RequestType = RequestTypes.Issue,
            AppliesTo = new EndpointAddress(SRV_ADDRESS)
        }, out response);

        return token;
    }
}

Por último, e não menos importante, temos o retorno do método Issue. Ele retorna a instância de uma classe que herda direta ou indiretamente da classe SecurityToken, correspondendo ao token gerado pelo serviço de STS, caso a autentição tenha resultado com sucesso. É este token que informaremos para o serviço antes de efetuar a chamada para as operações. Opcionalmente você pode capturar a mensagem de resposta do processo de autenticação, e para isso, você pode utilizar uma sobrecarga que há no método Issue, que permite especificar através de um parâmetro de saída uma classe do tipo RequestSecurityTokenResponse (RSTR), e com isso ter acesso a informações como tempo de vida, tipo de autenticação, tipo do token gerado, etc.

Com o token gerado, agora nos resta passá-lo para o serviço, para que o mesmo o utilize  para permitir a execução da operação que desejarmos invocar. Para a criação do serviço, vamos também recorrer à uma factory, definindo em seu tipo genérico o contrato do serviço. Em seu construtor, passamos a instância do binding WS2007FederationHttpBinding, que permite dialogarmos com um serviço que suporta este tipo de autenticação (via STS). Além dele, ainda é necessário informarmos o endereço até o serviço, assim como já fazemos quando desejamos invocar qualquer serviço WCF. Depois deste objeto criado, precisamos configurar alguns parâmetros que são necessários para estabelecer a ligação entre o cliente e o serviço.

Essas configurações estão todas concentradas na propriedade Credentials, que é exposta pela classe ChannelFactory<TChannel>. Só que antes de analisar as propriedades que ela fornece, precisamos entender o comportamento deste binding. Como ele exige um token para enviar ao serviço, é necessário informarmos ele antes de efetuar a requisição para qualquer operação. A propriedade SupportInteractive tem a finalidade de exibir ou não a tela do Windows Cardspace, solicitando que o usuário informe este token. No cenário deste exemplo, o token já foi emitido, devido a uma solicitação explícita ao serviço de STS, e justamente por isso, devemos evitar que essa tela seja exibida, definindo-a como False.

Em seguida, especificamos a chave pública que foi fornecida pelo serviço, garantindo que a mensagem que chegará para ele venha devidamente protegida. A propriedade CertificateValidationMode se faz necessária porque precisamos desabilitar a validação do certificado, já que ele foi criado apenas para testes. Para finalizar a configuração, invocamos o método de estensão chamado ConfigureChannelFactory, que é fornecido através da classe ChannelFactoryOperations, que por sua vez, está debaixo do namespace Microsoft.IdentityModel.Protocols.WSTrust.

Essa classe ainda fornece mais um método (de estensão) importante para o nosso exemplo, que é o CreateChannelWithIssuedToken. Este método recebe como parâmetro a instância de um token, que é representado pela classe SecurityToken, que criamos no passo acima, retornando o proxy de comunicação com o serviço. É neste momento que informarmos ao serviço o token que será utilizado para efetuar a chamada para as operações dele. O código abaixo ilustra toda essa configuração, incluindo as chamadas para as operações expostas pelo serviço.

private static void InvocarServico(SecurityToken token)
{
    using (ChannelFactory<IConsultasFinanceiras> sf =
        new ChannelFactory<IConsultasFinanceiras>(
            new WS2007FederationHttpBinding(), 
            new EndpointAddress(
                new Uri(SRV_ADDRESS),
                EndpointIdentity.CreateDnsIdentity(“Servico”))))
    {
        sf.Credentials.SupportInteractive = false;
        sf.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
            X509CertificateValidationMode.PeerTrust;
        sf.Credentials.ServiceCertificate.SetDefaultCertificate(
            “CN=Servico”, StoreLocation.LocalMachine, StoreName.My);
        sf.ConfigureChannelFactory();

        var channel = sf.CreateChannelWithIssuedToken(token);
        channel.DefinirNovoLimiteDeCredito(10, 1000);
        Console.WriteLine(channel.RecuperarLimiteDeCredito(1000));
    }
}

Como podemos notar no exemplo acima, a chamada para o serviço está totalmente independente do modelo de  autenticação. Tudo o que precisa fazer é informar o token gerado pelo STS, sem ter ideia de como a autenticação foi realizada. Amanhã, o modelo de autenticação exigido pelo STS muda e o consumo do serviço não sofrerá qualquer alteração.

Conclusão: Neste artigo pudemos desvendar todo o procedimento que é reliazado internamente pelo WIF quando tentamos consumir um serviço WCF exige que a autenticação seja realizada por um STS. Agora coordenamos isso manualmente, o que nos dá certa flexibilidade, como por exemplo, em um ambiente passivo, evitar o redirecionamento para o STS para autenticar o usuário, onde poderíamos fornecer uma página local de login, e através desta alternativa, consumir o STS requisitando a emissão do token e, consequentemente, reutilizar o token durante toda a sesão do usuário.

Autenticação via Claims no WCF

Nos artigos anteriores, falei sobre a proposta da Microsoft para tentar unificar o sistema de autenticação das aplicações. Nos dois primeiros artigos, comentei um pouco sobre os problemas existentes e a teoria que circunda qualquer Identity MetaSystem. Na sequência, introduzi o WIF e alguns tipos que são comuns para qualquer tipo de aplicação que fará uso deste novo framework. A finalidade deste artigo é mostrar como podemos aplicar o WIF em serviços WCF, analisando tudo o que é necessário para colocar isso em funcionamento, incluindo a criação de um STS customizado para testes.

Em um artigo anterior, vimos como podemos implementar o WIF à aplicações ASP.NET, que recorrem ao navegador para serem apresentadas. Com isso, o WIF ou qualquer outro framework de autenticação, faz uso das funcionalidades que o navegador disponibiliza, para assim, coordenar de forma passiva, tudo o que é necessário para efetuar a autenticação em uma segunda aplicação.

Quando falamos em autenticação de serviços, o processo muda um pouco, pois geralmente eles são acessados através de uma aplicação cliente, que muitas vezes deve se identificar antes de consumir as funcionalidades que ele expõe. Neste cenário podemos perceber que não há o navegador, e sim uma aplicação (na maioria das vezes, desktop (Windows Forms ou WPF)) que consomem o serviço.

No artigo anterior discutimos o ambiente passivo, e aqui entra o ambiente ativo. O nome se deve ao fato de que a aplicação cliente coordena ativamente o processo de autenticação do usuário quando ele, por sua vez, precisar acessar um serviço que exija que o usuário esteja devidamente autenticado para poder acessá-lo. Como já acontecia anteriomente, os serviços que compõem a sua estrutura, pode seguramente confiar em um orgão que efetua a autenticação, evitando assim configurarmos serviço por serviço todo o processo necessário para a autenticação do usuário.

Como o WCF já traz vários pontos de estensibilidade, a Microsoft apenas introduziu nesses locais recursos para interceptar o processo de autenticação, e com isso, acoplando o WIF para efetuar todo o trabalho. A ideia aqui é ao configurar um serviço WCF, dizer que ele somente permitirá usuários autenticados por uma outra aplicação, que também será um serviço, mas construído com os recursos fornecidos pelo WIF.

Ao expor um serviço WCF neste caso, a configuração do mesmo deverá conter entre vários itens, o autenticador. Com isso, o WIF será capaz de modificar o documento WSDL que descreve o serviço, também incluindo o local deste autenticador que o teu serviço confia. Com isso, ao referenciar o serviço em uma aplicação cliente, ela já saberá qual o endereço correto do autenticador e, consequentemente, antes de efetuar a requisição efetiva para uma das operações do serviço, ele será inteligente o bastante para autenticar o usuário no autenticador e, finalmente, invocar a operação solicitada. Através da imagem abaixo, podemos visualizar as etapas deste processo:

É importante dizer que no ambiente ativo, o processo acaba sendo mais rápido quando comparado com o ambiente passivo, pois o cliente não precisa visitar o serviço para saber qual é o autenticador que o mesmo utiliza. Isso já aconteceu durante a referência do serviço na aplicação cliente, que traz, além da descrição do serviço em si, informações inerentes ao autenticador em qual o serviço confia, assim como já foi comentado acima.

Depois de entendermos o processo em um nível macro, precisamos nos atentar em como podemos proceder para preparar tudo isso. Em primeiro lugar, vamos avaliar as opções de template de projeto voltadas para o WCF, que o WIF nos fornece quando instalamos o seu SDK:

  • WCF Security Token Service: Template que traz a configuração padrão para criar um serviço WCF que servirá como um STS no ambiente ativo.
  • Claims-aware WCF Service: Template que traz um serviço WCF pré-configurado para utilizar algum STS, que será responsável pela autenticação do usuário.

Apesar delas existirem, não vamos utilizá-las no decorrer deste artigo. A ideia aqui é apresentar os detalhes de criação do começo ao fim, analisando cada uma das seções necessárias para fazer com que todo esse processo funcione da forma correta. Com isso, teremos três elementos envolvidos no nosso exemplo: o serviço (relying party) que contém todas as funcionalidades que desejamos expor aos clientes, um autenticador (STS), que será o responsável por autenticar os usuários e, finalmente, a aplicação cliente (subjects), que utilizará o serviço WCF para extrair as informações e exibí-las na tela, mas antes disso, deverá autenticar o usuário que a acessa no STS correspondente.

A criação do STS

Para iniciar, vamos começar com a construção do STS, que será o nosso autenticador. Para isso, vamos recorrer à uma aplicação do tipo Console, que irá expor um serviço que aceitará as requisições de login, validará o usuário em algum repositório e, finalmente, devolverá para a aplicação solicitante o token gerado por ele. Como já comentei nas entrelinhas, o STS nada mais é que um serviço, que se utiliza da mesma estrutura de um serviço WCF, mas expondo um tipo de serviço diferenciado, que tem a finalidade de validar e autenticar um usuário. Para termos acesso as classes que nos permite criar este tipo de serviço, devemos referenciar na aplicação o assembly Microsoft.IdentityModel.dll, que é fornecido pelo WIF.

Se o STS nada mais é que um serviço, temos que ter uma classe que o representa, e para isso o WIF já fornece uma classe abstrata chamada SecurityTokenService, que é o nosso STS em si, fornecendo toda a estrutura necessária para a criação de um STS customizado, mas para isso, devemos sobrescrever dois métodos nas classes derivadas, a saber: GetScope e GetOutputClaimsIdentity.

O primeiro método, GetScope, nos dá a possibilidade de verificar quais são as chaves de criptografia que serão utilizadas, baseando-se no endereço da relying party. Esse método retorna uma instância da classe Scope, que contém toda a configuração necessária para gerar o retorno para a respectiva aplicação (relying party). Já o segundo método, GetOutputClaimsIdentity, é o local onde efetivamente definimos quais claims serão criadas para aquele usuário recém autenticado, e que na maioria das vezes, iremos recorrer a uma busca no repositório para extrair esses dados. Esse método retorna a instância de uma classe ClaimsIdentity, que implementa a interface IClaimsIdentity, e como sabemos, fornece uma propriedade chamada Claims, que nada mais é que uma coleção, onde podemos adicionar quantas claims desejarmos.

Como mencionei, o método GetScope é responsável por capturar os recursos necessários para estabelecer a comunicação segura. Aqui teremos dois certificados, sendo um deles utilizado pelo autenticador (STS) e outro pelo serviço (relying party). Cada um deles vai garantir que as mensagens trocadas não sejam manipuladas por ninguém. Para isso, a classe Scope fornece duas propriedades: EncryptingCredentials e SigningCredentials. Na primeira propriedade, definimos o certificado fornecido pelo serviço, que com a sua chave pública, faremos a criptografia da mensagem quando formos retornar a resposta do processo de autenticação (RSTR). Já a propriedade SigningCredentials, apontamos para o certificado do autenticador, que utilizará para garantir que a mensagem não será alterada durante a sua viagem. Abaixo temos um trecho da classe que representa o serviço de STS, omitindo algumas seções por questões de espaço:

public class ServicoDeAutenticacao : SecurityTokenService
{
    public ServicoDeAutenticacao(ConfiguradorDoServicoDeAutenticacao configurador)
        : base(configurador) { }

    protected override IClaimsIdentity GetOutputClaimsIdentity(
        IClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
    {
        principal.Identities[0].Claims.AddRange(
            RepositorioDeUsuarios.RecuperarClaimsDoUsuario(principal.Identity.Name));

        return principal.Identity as IClaimsIdentity;
    }

    protected override Scope GetScope(IClaimsPrincipal principal, RequestSecurityToken request)
    {
        Scope scope = new Scope(request.AppliesTo.Uri.AbsoluteUri);
        scope.EncryptingCredentials = this.GetCredentialsForAppliesTo(request.AppliesTo);
        scope.SigningCredentials = new X509SigningCredentials(
            CertificateUtil.GetCertificate(
                StoreName.My,
                StoreLocation.LocalMachine,
                “CN=Autenticador”));

        return scope;
    }

    //Outros Membros
}

Depois da classe que representa o serviço de STS criada, o próximo passo é a criação de uma classe que herda da classe SecurityTokenServiceConfiguration. Essa classe permite configurar um serviço de STS, definindo qual será o tipo da classe que representará o STS, que foi aquela que criamos no passo anterior. Essa classe possui várias propriedades, que permite configurar de forma imperativa o STS, mas neste caso, tudo o que precisamos fazer é herdar, e dentro do seu construtor, definir duas principais propriedades: SecurityTokenService e SigningCredentials. A primeira correspondente ao Type da classe que herda de SecurityTokenService, enquanto a segunda, define os dados do certificado usado para a conversação. O código abaixo mostra a sua estrutura:

public class ConfiguradorDoServicoDeAutenticacao : SecurityTokenServiceConfiguration
{
    public ConfiguradorDoServicoDeAutenticacao()
        : base(“ServicoDeAutenticacao”)
    {
        this.SecurityTokenService = typeof(ServicoDeAutenticacao);
        this.SigningCredentials = new X509SigningCredentials(
            CertificateUtil.GetCertificate(
                StoreName.My,
                StoreLocation.LocalMachine,
                “CN=Autenticador”));
    }
}

Para encerrar as classes customizadas, é necessário a criação de um security token handler. Neste contexto, o handler é responsável por extrair o token enviado pelo cliente, e validá-lo em algum repositório. Como queremos optar para que o usuário informe o seu login e senha, utilizaremos aqui um security token handler exclusivo para este caso, que é o UserNameSecurityTokenHandler.

Ao herdar desta classe, devemos sobrescrever o método ValidateToken, que fornece como parâmetro a instância do token enviado pelo usuário, representado pela classe UserNameSecurityToken, que por sua vez, fornece duas propriedades pertinentes ao modelo de autenticação, que são elas: UserName e Password. O exemplo abaixo ilustra como proceder para customizar essa classe:

public class ValidadorDeUsuario : UserNameSecurityTokenHandler
{
    public override ClaimsIdentityCollection ValidateToken(SecurityToken token)
    {
        UserNameSecurityToken usernameToken = token as UserNameSecurityToken;

        if (!RepositorioDeUsuarios.Validar(usernameToken.UserName, usernameToken.Password))
            throw new SecurityException(“Usuário inválido.”);

        ClaimsIdentity identidadeAtual = new ClaimsIdentity(“ValidadorDeUsuario”);
        identidadeAtual.Claims.Add(new Claim(ClaimTypes.Name, usernameToken.UserName));

        return new ClaimsIdentityCollection() { identidadeAtual };
    }

    //Outros Membros
}

Como podemos notar no código acima, é dentro do método ValidateToken que devemos recorrer ao repositório de usuários para validar as credenciais informadas pelo cliente. Caso o usuário não seja encontrado, você pode disparar uma exceção do tipo SecurityException, informando a inexistência daquele usuário.

E para colocar tudo isso em funcionamento, entra em cena a classe WSTrustServiceHost. Essa classe herda diretamente da classe ServiceHost (exposta pela API do WCF), que permite carregar e gerenciar um serviço STS utilizando toda a estrutura do WCF, e com isso, ficará disponível para receber as requisições das aplicações clientes que desejam autenticar seus usuários, antes de efetivamente acessar uma das operações expostas pelo serviço. Em princípio, temos que recorrer a esta classe quando não estamos utilizamos as templates de projetos que vimos acima, pois quando as usamos, isso tudo o que vemos aqui ficará transparente.

Há um overload do construtor da classe WSTrustServiceHost, que aceita como parâmetro uma classe do tipo SecurityTokenServiceConfiguration, contendo toda a configuração necessária de um serviço de STS. O código abaixo ilustra a sua utilização:

using (WSTrustServiceHost host = 
    new WSTrustServiceHost(new ConfiguradorDoServicoDeAutenticacao()))
{
    host.Open();
    Console.WriteLine(“[ Serviço de Autenticação em funcionamento ]”);
    Console.ReadLine();
}

Assim como qualquer serviço WCF, somente essa configuração não é suficiente para subir o serviço de STS. Ainda há a necessidade de apontar, via arquivo de configuração, outras informações necessárias para o mesmo, tais como endpoints, contratos, baseAddresses, etc., que como sabemos, é necessário para todo e qualquer tipo de serviço WCF. Mas como estamos criando uma espécie de serviço voltado exclusivamente para o STS, temos já contratos predefinidos, que expõem funcionalidades de emissão, cancelamento e renovação de tokens, mas tudo isso tudo será utilizado, em um primeiro momento, internamente pelo runtime do WIF.

Sendo assim, a classe que representará o serviço é a WSTrustServiceContract, que implementa a interface IWSTrust13SyncContract, que define o contrato do serviço, fornecendo as operações comentadas no parágrafo anterior. Agora o nosso serviço deverá expor dois endpoints, sendo um deles utilizando o contrato IWSTrust13SyncContract em conjunto com o binding WS2007HttpBinding, e além disso, um binding tradicional de metadados (IMetadataExchange), que irá expor todas as funcionalidades do serviço de STS. A única configuração relevante do binding, é que devemos definir o atributo clientCredentialType do elemento message como sendo UserName, já que a ideia é permitir ao usuário informar os dados de acesso através da aplicação cliente. Abaixo podemos visualizar as configurações deste serviço:

<?xml version=”1.0″?>
<configuration>
  <system.serviceModel>
    <services>
      <service name=”Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceContract”
               behaviorConfiguration=”serviceConfig”>
        <host>
          <baseAddresses>
            <add baseAddress=”http://localhost:9010/sts”/&gt;
          </baseAddresses>
        </host>
        <endpoint address=””
                  binding=”ws2007HttpBinding”
                  bindingConfiguration=”bindingConfig”
                  contract=”Microsoft.IdentityModel.Protocols.WSTrust.IWSTrust13SyncContract” />
        <endpoint address=”mex”
                  binding=”mexHttpBinding”
                  contract=”IMetadataExchange” />
      </service>
    </services>
    <bindings>
      <ws2007HttpBinding>
        <binding name=”bindingConfig”>
          <security mode=”Message”>
            <message clientCredentialType=”UserName”
                     negotiateServiceCredential=”false”
                     establishSecurityContext=”false”/>
          </security>
        </binding>
      </ws2007HttpBinding>
    </bindings>
</configuration>

Como se não bastasse, ainda precisamos das configurações referentes ao WIF neste mesmo arquivo de configuração da aplicação que servirá como host do serviço de STS. Como já vimos em artigos anteriores, a seção para essa configuração é a microsoft.identityModel, que deve ser registrada primeiramente antes de ser utilizada.

Depois da seção registrada, o próximo passo é remover o security token handler que é adicionado por padrão (WindowsUserNameSecurityTokenHandler), e em seguida, adicionarmos aquele que criamos acima. Para isso, recorremos à uma coleção chamada securityTokenHandlers, e especificamos ali o tipo completo, incluindo o nome e o assembly onde ele foi criado. Além disso, ainda há a seção serviceCertificate, que apontamos o certificado que o autenticador irá utilizar para se comunicar com o serviço, protegendo todas mensagens que são trocadas entre eles. O código abaixo resume todas as configurações do WIF que são necessárias na aplicação que hospedará o serviço STS, que compõem o arquivo de configuração que vimos acima com as configurações pertinentes ao WCF:

<?xml version=”1.0″?>
<configuration>
  <configSections>
    <section name=”microsoft.identityModel”
             type=”Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, …”/>
  </configSections>
  <microsoft.identityModel>
    <service>
      <securityTokenHandlers>
        <remove type=”Microsoft.IdentityModel.Tokens.WindowsUserNameSecurityTokenHandler, …” />
        <add type=”Autenticador.ValidadorDeUsuario, Autenticador …” />
      </securityTokenHandlers>
      <serviceCertificate>
        <certificateReference storeLocation=”LocalMachine”
                              storeName=”My”
                              x509FindType=”FindBySubjectDistinguishedName”
                              findValue=”CN=Autenticador” />
      </serviceCertificate>
    </service>
  </microsoft.identityModel>
</configuration>

A criação do Serviço

Depois do serviço de autenticação criado, vamos partir para o serviço que utilizará e confiará naquele STS para autenticar os usuários. O serviço que vamos utilizar como exemplo é bem simples, com apenas duas operações que recebem e retornam tipos simples. A ideia aqui será restringir o acesso às operações deste serviço, permitindo o acesso somente depois do usuário ser devidamente autenticado pelo serviço de STS que criamos acima.

Sendo assim, a configuração do serviço WCF com as nossas regras de negócio é simples, contendo os endpoints, os behaviors para expor os metadados e a criação de um hosting, que utilizaremos neste exemplo, uma aplicação do tipo Console. A única diferença aqui na configuração do serviço é a utilização do binding WS2007FederationHttpBinding, que é utilizado quando você está terceirizando o processo de autenticação.

No código abaixo podemos visualizar a configuração do binding do endpoint do nosso serviço. Definimos a segurança como sendo Message, que garantirá que a mensagem seja trafegada de forma segura, mesmo que o transporte não forneça esse recurso. Nas configurações internas, temos um elemento chamado issuerMetadata, que através do atributo address, devemos especificar o endereço do endpoint do serviço de STS, responsável pela autenticação. Em segundo lugar e não menos importante, temos o elemento claimTypeRequirements, que é uma coleção onde podemos especificar quais são as claims que o serviço está exigindo que o STS emita.

<bindings>
  <ws2007FederationHttpBinding>
    <binding name=”bindingConfig”>
      <security mode=”Message”>
        <message>
          <issuerMetadata address=”http://localhost:9010/sts/mex&#8221; />
          <claimTypeRequirements>
            <add claimType=”http://schemas.microsoft.com/ws/2008/06/identity/claims/name&#8221;
                 isOptional=”false” />
            <add claimType=”http://schemas.microsoft.com/ws/2008/06/identity/claims/role&#8221;
                 isOptional=”false” />
          </claimTypeRequirements>
        </message>
      </security>
    </binding>
  </ws2007FederationHttpBinding>
</bindings>

No serviço também precisamos de algumas configurações pertinentes ao WIF, e como já era de se esperar, é necessário também registrar a seção do mesmo, através do elemento configSections. Neste caso, precisamos definir a audienceUri, que nos permite elencar uma coleção de URIs, que definem os possíveis endereços para qual o token poderá ser entregue, evitando que clientes maliciosos façam uso do mesmo. Em seguida temos a seção serviceCertificate, que apontamos o certificado que o serviço vai utilizar para se comunicar com o autenticador, protegendo as mensagens que são trocadas entre eles. Abaixo o código que ilustra essas configurações:

<?xml version=”1.0″?>
<configuration>
  <configSections>
    <section name=”microsoft.identityModel”
             type=”Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection …”/>
  </configSections>
  <microsoft.identityModel>
    <service>
      <issuerNameRegistry type=”Servico.ValidadorDoAutenticador, Servico …” />
      <audienceUris>
        <add value=”http://localhost:9000/ServicoDeCredito&#8221; />
      </audienceUris>
      <serviceCertificate>
        <certificateReference storeLocation=”LocalMachine”
                              storeName=”My”
                              x509FindType=”FindBySubjectDistinguishedName”
                              findValue=”CN=Servico” />
      </serviceCertificate>
    </service>
  </microsoft.identityModel>
</configuration>

Finalmente, ainda analisando o código acima, temos um elemento chamado issuerNameRegistry, que como o próprio nome diz, permite especificarmos ali os STSs em que a aplicação confia. Para termos o controle desta validação, podemos criar uma classe que herde da classe abstrata IssuerNameRegistry, sobrescrevendo o método GetIssuerName, analisando o certificado fornecido pelo autenticador, e assim tomarmos alguma decisão para saber se confiamos ou não nele. Se iremos confiar, então devemos retornar uma string representando o autenticador, caso contrário, devemos disparar uma exceção para reportar essa “falha”. O código abaixo ilustra essa classe:

public class ValidadorDoAutenticador : IssuerNameRegistry
{
    public override string GetIssuerName(SecurityToken securityToken)
    {
        X509SecurityToken certificado = securityToken as X509SecurityToken;

        if (certificado != null)
            if (certificado.Certificate.SubjectName.Name == “CN=Autenticador”)
                return certificado.Certificate.SubjectName.Name;

        throw new SecurityTokenException(“Autenticador Inválido.”);
    }
}

O último detalhe é a criação do host, que quando utilizamos um self-hosted, recorrermos à classe ServiceHost, e aqui não será diferente. Só há uma pequena configuração a ser realizada aqui, que é a integração do runtime do WIF ao pipeline do WCF. Essa configuração pode ser feita de forma imperativa ou declarativa. Utilizando a primeira opção, antes de abrir o host, devemos enviar a instância da classe ServiceHost para o método estático ConfigureServiceHost, exposto pela classe FederatedServiceCredentials, assim como podemos notar no trecho de código abaixo:

using (ServiceHost host = new ServiceHost(typeof(ServicoDeCredito), new Uri[] { }))
{
    FederatedServiceCredentials.ConfigureServiceHost(host);

    host.Open();
    Console.WriteLine(“[ Serviço de Crédito em funcionamento ]”);
    Console.ReadLine();
}

Ao submeter o host para o método ConfigureServiceHost, uma série de elementos são adicionados ao processamento das requisições que chegam para o serviço, efetuando toda a comunicação necessária com o serviço de STS em qual o serviço confia, e além disso, é capaz de capturar o resultado do processo de autenticação realizado remotamente, e com isso prosseguir com o processo de autorização, que determinará o que o usuário poderá ou não acessar.

A autorização continua sendo de responsabilidade do serviço local, que na maioria das vezes recorre às roles para refinar o acesso. Quando estamos trabalhando com o WIF, todas as informações que ele emite para um determinado usuário autenticado, é representado por uma claim. No nosso exemplo, o STS está gerando três claims, sendo uma delas do tipo name, e as outras duas do tipo role. O WCF em conjunto com o WIF são capazes de analisar as claims geradas pelo STS, e depois disso, configurar a propriedade CurrentPrincipal da classe Thread com uma instância da classe ClaimsPrincipal, representando a identidade do usuário e suas respectivas claims.

Desta forma, podemos continuar trabalhando com o modelo de programação da mesma forma que já utilizávamos no passado, ou seja, através do método IsInRole (imperativo) ou com o uso do atributo PrincipalPermissionAttribute (declarativo), assim como é exemplificado abaixo:

public class ServicoDeCredito : IConsultasFinanceiras
{
    [PrincipalPermission(SecurityAction.Demand, Role = “Funcionario”)]
    public decimal RecuperarLimiteDeCredito(int codigoDoCliente)
    {
        //Implementação
    }

    [PrincipalPermission(SecurityAction.Demand, Role = “Gerente”)]
    public void DefinirNovoLimiteDeCredito(int codigoDoCliente, decimal novoValor)
    {
        //Implementação
    }
}

A criação do Cliente

Finalmente, depois dos dois serviços devidamente implementados, chega o momento de consumí-los em uma aplicação cliente. Tudo o que precisamos fazer nela, é adicionar a referência para o serviço WCF, assim como já fazemos. Não há necessidade de fazer referência ao serviço de STS na aplicação cliente, pois além do documento WSDL descrever as operações do serviço, ele também traz todas as informações necessárias a respeito do serviço responsável pela autenticação, como o endereço onde ele está localizado, quais são as claims que o serviço exige que o autenticador emita, etc.

Ao referenciar o serviço através da opção Add Service Reference da IDE do Visual Studio ou através do utilitário svcutil.exe, o proxy gerado conterá as informações tradicionais do endpoint do serviço com o qual a aplicação quer se comunicar, mas também incluirá algumas configurações extras, que determinam como a comunicação com o STS será realizada. Justamente por conhecer onde o STS está e como devemos ser comunicar com ele, que o ambiente ativo é bem mais rápido que o passivo, como já discutido anteriormente.

Outro detalhe importante no arquivo de configuração, são as chaves públicas dos certificados envolvidos no processo. Uma delas foi fornecida pelo serviço e a outra pelo autenticador, que o runtime do WCF e do WIF irão utilizá-las durante o processo de comunicação para garantir com que as mensagens que serão trocadas entre as partes sejam protegidas.

Por fim, a aplicação cliente só precisa consumir o serviço, apenas se atentando a informar o login e senha antes de invocar alguma das operações. O código abaixo ilustra a utilização do serviço, através do proxy que foi criado durante a referência do serviço. O arquivo de configuração do cliente será omitido aqui por questões de espaço.

using (ConsultasFinanceirasClient proxy = new ConsultasFinanceirasClient())
{
    proxy.ClientCredentials.UserName.UserName = “Israel”;
    proxy.ClientCredentials.UserName.Password = “P@ssw0rd”;

    proxy.DefinirNovoLimiteDeCredito(560, 10000);

    Console.WriteLine(“Limite para o cliente 200: {0:C2}”,
        proxy.RecuperarLimiteDeCredito(200));
}

Conclusão: Depois de tudo o que fizemos aqui, o nosso serviço ficou completamente independente de como se faz a autenticação do usuário. Se tivermos um barramento de serviços dentro de algum local, como uma empresa, todos os serviços podem confiar em um único autenticador, e a reutilização será muito grande, já que temos todo o processo centralizado, facilitando a manutenção, já que os serviços consumidores nada sabem sobre a forma como estamos autenticando os usuários.

TesteComAmbienteAtivo.zip (42.08 kb)

QCon – Como unificamos nossa camada de autenticação?

O principal evento de arquitetos e desenvolvedores chega a América Latina. O QCon SP traz, dias 11 e 12 de Setembro, ícones internacionais e nacionais de diversas áreas, com apresentações de alto nível técnico. Com sistemas cada vez mais complexos, o QCon aborda não apenas uma única tecnologia ou aspecto: passa de Java, .NET e Rails até Arquitetura, Design, Cloud, Escalabilidade, Replicação, Cache e casos de sucesso.

Neste evento, eu fui convidado para compor juntamente com alguns ícones do mundo .NET (tais como Thiago Cruz Soares e Fabio Galuppo), o grupo de palestrantes focados em tecnologias Microsoft/.NET. A minha palestra tem como finalidade abordar o WIF – Windows Identity Foundation, que já falei bastante por aqui. Abaixo temos a descrição completa da palestra:

Título: Como unificamos nossa camada de autenticação?
Palestrante: Israel Aece
Descritivo: Autenticação e autorização são dois conceitos importantes que existem no desenvolvimento de software. A autenticação é a necessidade de saber quem o usuário é, enquanto a autorização é saber que direitos ele tem no software. Há uma grande complexidade em torno destes temas, pois cada aplicação precisa se preocupar em vários detalhes para garantir a segurança, tais como: um local seguro para armazenar as credenciais de acesso, definição de políticas para a criação e reutilização de senhas, etc. E como se isso não fosse o bastante, corremos um grande risco de termos a mesma infraestrutura repetida entre várias aplicações, dificultando a manutenção, segurança e a experiência de navegação dos usuários que as acessam. Essa palestra mostrará cenários típicos do nosso dia-à-dia, como SSO (Single Sign-On) e federação dentro da plataforma .NET.