.NET

1 ago, 2011

ASP.NET MVC 3: Criando um sistema de autenticação com cookies criptografadas

Publicidade

Se você não é um extraterreste, com certeza já ouviu falar de ASP.NET MVC (caso seja, recomendo a leitura da série introdutória à tecnologia).

Quando web developers
acostumados a trabalhar com ASP.NET iniciam o processo natural de
migração para ASP.NET MVC, muitas dúvidas surgem. Apenas para relatar
algumas: “Como faço para utilizar meu gridview?”, “No ASP.NET tinha os componentes validator’s. Como faço no ASP.NET MVC?”, “Como faço para autenticar usuários?”, etc.

Assim, este artigo pretende apresentar uma das formas disponíveis para realizar a autenticação de usuários em seu site/blog/sistema utilizando ASP.NET MVC 3 com Razor. Para nos auxiliar neste processo, utilizaremos cookies criptografadas (isso porque evitamos sempre que possível a utilização de sessions, pelos motivos já conhecidos: performance, sobrecarga do servidor, etc.).

A estrutura do banco de dados

Para
construção do exemplo, utilizaremos uma estrutura de tabela
relativamente simples de banco de dados. Esta tabela está presente no
famoso banco “AdventureWorks“, disponibilizado gratuitamente
pela Microsoft para estudos/testes e está definida como
“Person.Contact”. Sua estrutura pode ser visualizada na Figura 1. Você
pode baixar o Adventure Works e outros exemplos gratuitamente no CodePlex, clicando aqui. O processo de instalação do Adventure Works em seu sistema pode ser encontrado aqui.

O
que fiz a seguir foi adicionar um registro à tabela com minhas
informações pessoais para realizar o teste de autenticação. Pré
requisitos atendidos, vamos a construção de nossa aplicação de exemplo.

Criando o projeto ASP.NET MVC 3

Para
construção deste exemplo, estou utilizando o Visual Studio 2010
Ultimate, entretanto, você pode obter os mesmos resultados utilizando Visual Web Developer Express. Assim, com sua IDE em execução, crie um novo projeto do tipo ASP.NET MVC 3. A Figura 2 ilustra este procedimento.

Clicando
em “Ok”, o Visual Studio inicia o processo de criação do projeto e,
antes que esse seja concluído, você deverá ser perguntado quanto a view engine que será utilizada. Como você já deve ter visto/ouvido, o padrão para o ASP.NET MVC 3 é Razor,
entretanto, você não é obrigado a utilizá-la (opcionalmente, você pode
utilizar ASP.NET tradicional). Além disso, nesta mesma tela, você pode
optar por criar um projeto baseado em um modelo do VS 2010 ou um projeto
vazio. Criaremos um projeto vazio e utilizaremos o Razor como engine padrão. A Figura 3 ilustra estas escolhas.

Construindo a página master

Iniciaremos pela construção da view master. Portanto, na solution explorer, vá até a pasta “Views/Shared” e dê um duplo clique no arquivo “_Layout.cshtml“. Esta será nossa página master. Onde isso está definido? No arquivo “_ViewStart.cshtml” um nível de diretórios abaixo, como pode ser visualizado na Listagem 1.

[csharp]
Layout = "~/Views/Shared/_Layout.cshtml";
[/csharp]

Naturalmente, é possível definir outro arquivo como página master, entretanto, para este exemplo, manteremos a estrutura atual. Assim, no interior da página master (_Layout.cshtml), adicione o código fonte apresentado pela Listagem 2.

[html]
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
</head>

<body>
<div id="BoxGeral">
@RenderBody()
</div>
</body>
</html>
[/html]

Para
gerar o padrão visual, precisamos definir o estilo (CSS) que formata os
elementos. Assim, utilizamos o arquivo CSS disponível na pasta Content (Site.css) para definir os estilos da master. Assim, adicione o trecho de código apresentado pela Listagem 3 no interior do arquivo mencionado.

[css]
body
{
font-family: Verdana, Tahoma, Arial, "Helvetica Neue", Helvetica, Sans-Serif;
background-color:#EFEFEF;
}
#BoxGeral
{
width:800px;
padding:15px;
border-radius:7px 7px 7px 7px;
margin-left:auto;
margin-right:auto;
margin-top:20px;
text-align:left;
background-color:#FFF;
}
[/css]

O resultado das operações executadas acima, pode ser visualizado na Figura 4.

Construindo o formulário de autenticação

Agora que já possuímos nossa “master page“, podemos passar a fase seguinte e construir nossa view onde o formulário de autenticação estará hospedado. Assim, na solution explorer, vá até a pasta “Controllers” e clique com o botão direito sobre a mesma. Selecione a opção “Add” e, em seguida, “Controller”. Nomeie seu controller conforme a conveniência (neste exemplo, seu nome será Home). A Figura 5 ilustra este procedimento.

Se tudo correu bem, sua solution explorer deve ter sofrido alguma alteração, isto é, um novo arquivo (HomeController.cs) deve ter sido adicionado a pasta Controllers. Dê um duplo clique sobre este arquivo. No editor de código, clique com o botão direito sobre o nome da action “Index.cshtml” e selecione a opção “Add view…“. Na janela que se abre, mantenha o nome padrão da view e selecione a opção “Use a layout or master page” e navegue pela árvore de diretórios do projeto até chegar no arquivo trabalhado por nós anteriormente como master page, isto é, Layout.cshtml, conforme ilustrado pela Figura 6.

Como estamos trabalhando com o conceito de page layout’s, na view
nos preocuparemos apenas com com as informações inerentes a ela, isto
é, sem preocupação com aspectos gerais visuais da aplicação. Assim, na solution explorer, dê um duplo clique sobre o arquivo Index.cshtml, localizado na pasta Views/Home. Em seu interior, adicione o código apresentado pela Listagem 4.

[html]
@{
ViewBag.Title = "Sistema Administrativo";
Layout = "~/Views/Shared/_Layout.cshtml";
}

<div id="BoxTitulo">
<h1>
Você é você?
</h1>
</div>
<div id="BoxFormsGeral">
<div id="BoxFormLogin">
<fieldset>
<legend>Informe seus dados</legend>
<form method="post" action="/Home/Index" id="frmLogin">
<table style="margin-top:10px;">
<tr>
<td>
Usuário:
</td>
<td>
<input type="text" name="txtUsuario" id="txtUsuario" maxlength="20" class="" />
</td>
</tr>
<tr>
<td>
Senha:
</td>
<td>
<input type="password" name="txtSenha" id="txtSenha" maxlength="10" class="" />
</td>
</tr>
<tr>
<td>
<input type="submit" name="btnEntrar" id="btnEntrar" value="Entrar" />
</td>
</tr>
</table>
</form>
</fieldset>
</div>
<div id="BoxCadastro">
<fieldset>
<legend>Cadastre-se</legend>
<span style="margin-top:15px;">
Ainda não se cadastrou em nosso portal? Cadastre-se agora mesmo!<br /><br />
<input type="button" value="Cadastrar agora!" id="btnCadastrar" />
</span>
</fieldset>
</div>
</div>
<div id="BoxOutrasInformacoes">
<a href="@Href("~/Usuarios/Relembrar")" target="_self" class="LinkGeral">
Esqueceu seu usuário e/ou senha?
</a>
</div>
[/html]

O código é simples e dispensa comentários. Para que a formatação dos elementos adicionados a view possam ocorrer satisfatóriamente, adicione ao arquivo Site.css o techo de código CSS apresentado pela Listagem 5.

[css]
#BoxTitulo
{
width:100%;
float:left;
margin-bottom:20px;
}
#BoxFormsGeral
{
width:100%;
float:left;
margin-bottom:10px;
display:inline-block;
}
#BoxOutrasInformacoes
{
width:100%;
float:left;
display:inline-block;
}
#BoxFormLogin
{
width:370px;
display:inline-block;
float:left;
text-align:left;
padding:15px;
}
#BoxCadastro
{
width:370px;
display:inline-block;
float:right;
text-align:left;
padding:15px;
}

/* Textos e Links
-----------------------------------------------------------*/
h1
{
font-family:Arial;
font-size:45px;
letter-spacing:-5px;
font-weight:bold;
margin:0px;
}

.LinkGeral:link { font-family:Arial; font-size:14px; color:#666; text-decoration:none;}
.LinkGeral:hover { font-family:Arial; font-size:14px; color:#666; text-decoration:underline;}
.LinkGeral:action { font-family:Arial; font-size:14px; color:#666; text-decoration:undeline;}
.LinkGeral:visited { font-family:Arial; font-size:14px; color:#CCC; text-decoration:none;}
[/css]

O resultado obtido com a correta aplicação dos códigos das Listagens 4 e 5 pode ser visualizado na Figura 7.

Implementando a autenticação

Se algum web designer estiver acompanhando este post,
até o tópico anterior ficou satisfeito com o conteúdo, já que falamos
apenas de aspectos visuais de nossa aplicação. Agora, falaremos do back-end, ou seja, do processo de autenticação de fato.

Existem alguns meios possíveis para se realizar a autenticação de usuários com ASP.NET MVC. No exemplo deste post, utilizaremos um modelo personalizado de autenticação utilizando cookies
criptografadas. Para boa parte dos casos testados, esta técnica
mostrou-se eficiente e, além disso, com poucas personalizações neste
modelo, é possível acrescer características importantes, tais como:
validações de cartão de crédito, etc.

Como
iremos acessar o banco de dados para verificar a existência do usuário
fornecido no formulário, precisamos de uma metodologia de acesso ao
mesmo. Neste exemplo, utilizaremos o Entity Framework 4.0, mas você poderia utilizar outro ORM ou métodos do próprio ADO.NET para realizar esta tarefa.

Criaremos também duas classes que atuarão como um repositórios de ações, aqui chamados de “UsersRepository.cs” e “CryptographyRepository.cs“. A utilização de repositórios é uma boa prática, pois, facilita a manutenção do código e, além disso, melhora o design da aplicação.

Como
temos um bom caminho ainda para percorrer, vamos a implementação de
fato. Iniciaremos pela criação de nosso modelo de dados com EF (Entity Framework). Como já possuímos a base de dados AdventureWorks, o que temos a fazer é ordenar ao EF que faça o mapeamento desta base e nos forneça o acesso a todos os recursos. Assim, na solution explorer, clique com o botão direito sobre a pasta “Models” e, em seguida, selecione a opção “Add” seguida da opção “New item…. Na janela que se apresenta, clique na guia “Data” à esquerda e, em seguida, selecione a opção “ADO.NET Entity Data Model“. Nomeie o arquivo conforme a necessidade e clique em “Ok”. A Figura 8 ilustra este procedimento.

Na tela seguinte, escolha a opção “Generate from database” e clique em “Next“, conforme apresenta a Figura 9.

A
próxima tela perguntará a respeito da conexão do banco de dados. Se
deseja utilizar uma conexão já existente ou se deseja criar uma nova.
Nesta tela, você deverá informar também o nome da conexão utilizada. As
Figuras 9 e 10 apresentam as opções (sugestivas) para prosseguir com o
processo. Em nosso caso, criaremos uma nova conexão, pois ainda não
temos uma estabelecida.

Na
tela seguinte, as opções apresentadas referem-se aos objetos do banco
de dados que queremos que o EF realize o mapeamento. Para este exemplo,
estamos interessados apenas na tabela “Person.Contact“, portanto, a selecionaremos e finalizaremos o processo clicando em “Finish“. A Figura 11 apresenta o processo descrito acima.

Conforme mencionado anteriormente, trabalharemos com cookies criptografadas no processo de autenticação, assim, o que faremos a seguir é criar o repositório de criptografia (CryptographyRepository.cs) e nele, teremos encapsuladas as operações relacionadas. Assim, vá até a solution explorer, na pasta “Models” e clique com o botão direito sobre a mesma. Selecione a opção “Add” e, em seguida, “class“.
Nomeie conforme a necessidade e clique em “Ok”. Substitua o conteúdo da
classe gerada, por aquele apresentado pela Listagem 6.

[csharp]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Cryptography;
using System.IO;
using System.Text;

namespace AutenticacaoUsuarioMVC3.Models
{
public class CryptographyRepository
{
private static byte[] chave = { };
private static byte[] iv = { 12, 34, 56, 78, 90, 102, 114, 126 };

private static string chaveCriptografia = "fabricio1234567";

//Criptografa o Cookie
public static string Criptografar(string valor)
{
DESCryptoServiceProvider des;
MemoryStream ms;
CryptoStream cs; byte[] input;

try
{
des = new DESCryptoServiceProvider();
ms = new MemoryStream();

input = Encoding.UTF8.GetBytes(valor); chave = Encoding.UTF8.GetBytes(chaveCriptografia.Substring(0, 8));

cs = new CryptoStream(ms, des.CreateEncryptor(chave, iv), CryptoStreamMode.Write);
cs.Write(input, 0, input.Length);
cs.FlushFinalBlock();

return Convert.ToBase64String(ms.ToArray());
}
catch (Exception ex)
{
throw ex;
}
}

//Descriptografa o cookie
public static string Descriptografar(string valor)
{
DESCryptoServiceProvider des;
MemoryStream ms;
CryptoStream cs; byte[] input;

try
{
des = new DESCryptoServiceProvider();
ms = new MemoryStream();

input = new byte[valor.Length];
input = Convert.FromBase64String(valor.Replace(" ", "+"));

chave = Encoding.UTF8.GetBytes(chaveCriptografia.Substring(0, 8));

cs = new CryptoStream(ms, des.CreateDecryptor(chave, iv), CryptoStreamMode.Write);
cs.Write(input, 0, input.Length);
cs.FlushFinalBlock();

return Encoding.UTF8.GetString(ms.ToArray());
}
catch (Exception ex)
{
throw ex;
}
}
}
}
[/csharp]

O
código apresentado pela Listagem 6 é simples, e dispensa comentários
pormenorizados. Assim, ressaltarei apenas os apectos importantes sobre
os mesmo. Como o próprio nome sugere, o método “Criptografar” recebe uma
string como parâmetro e, utiliza recursos da classe “System.Security.Cryptography
para criptografar a mesma. Os atributos “chave” e “iv” são parâmetros
para criar o encriptador, como pode ser visualizado na linha 31. O
método “Descriptografar”, como o nome também sugere, desfaz o processo
realizado pelo método anterior, isto é, recebe uma string como parâmetro e a descriptografa de acordo com a chave definida.

Agora
que possuímos o processo de criptografia pronto, podemos implementar a
autenticação dos usuários. Para isso, criaremos o repositório de
usuários, seguindo os mesmos procedimentos descritos para criação do
repositório de criptografia. Isto feito, o que deve ser realizado é a
adição do código apresentado pela Listagem 7 à classe “UsersRepository.cs“.

[csharp]
//Propriedade que verifica se o usuário encontra-se logado.
public static Contact UsuarioLogado
{
get
{
var Usuario = HttpContext.Current.Request.Cookies["UserCookieAuthentication"];
if (Usuario == null)
{
return null;
}
else
{
string NovoToken = AutenticacaoUsuarioMVC3.Models.CryptographyRepository.Descriptografar(Usuario.Value.ToString());

int IDUsuario;

if (int.TryParse(NovoToken, out IDUsuario))
{
return GetUsuarioByID(IDUsuario);
}
else
{
return null;
}
}
}
}

//Recuperando o usuário pelo ID
public static Contact GetUsuarioByID(int CodigoUsuario)
{
AdventureWorksEntities ContextoUsuario = new AdventureWorksEntities();

var Consulta = (from usuario in ContextoUsuario.Contact
where usuario.ContactID == CodigoUsuario
select usuario).SingleOrDefault();

return Consulta;
}

/// <summary>
/// Com base no Username e no Password, este método autentica o usuário e o direciona para o local correto.
/// </summary>
/// <param name="_Username"></param>
/// <param name="_Password"></param>
/// <returns></returns>
public static bool AutenticarUsuario(string _Username, string _Password)
{
//Criando o contexto de dados para autenticação
AdventureWorksEntities ContextoUsuario = new AdventureWorksEntities();

try
{
var RetornoQueryUser = (from u in ContextoUsuario.Contact
where u.EmailAddress == _Username && u.PasswordHash == _Password
select u).SingleOrDefault();

if (RetornoQueryUser == null)
{
return false;
}
else
{
//Criando um objeto cookie
HttpCookie UserCookie = new HttpCookie("UserCookieAuthentication");

//Setando o ID do usuário no cookie
UserCookie.Value = AutenticacaoUsuarioMVC3.Models.CryptographyRepository.Criptografar(RetornoQueryUser.ContactID.ToString());

//Definindo o prazo de vida do cookie
UserCookie.Expires = DateTime.Now.AddDays(1);

//Adicionando o cookie no contexto da aplicação
HttpContext.Current.Response.Cookies.Add(UserCookie);

return true;
}
}
catch (Exception)
{
return false;
}
}
}
[/csharp]

Novamente, um código vale mais que mil palavras. Vamos aos aspectos gerais do código apresentado pela Listagem 7. A classe UsersRepository possui três componentes: uma propriedade do tipo Contact chamada “UsuarioLogado”, um método chamado “GetUsuarioByID” que retorna uma instância de Contact e finalmente, um método de retorno booleano “AutenticarUsuario”. Basicamente, as funções de cada um são descritas a seguir:

  • UsuarioLogado: propriedade que verifica se o usuário que está solicitando determinada view já se encontra ou não autenticado.
  • GetUsuarioByID: método que recebe um inteiro (que representa o código do usuário) e, caso exista, retorna uma instância de Contact para o objeto chamador.
  • AutenticarUsuario:
    método que recebe o e-mail e a senha do usuário, verifica a existência
    do mesmo com base nestas informações e, caso exista, retorna true. Caso contrário retorna false.

Pronto.
Agora já possuímos nossos repositórios com seus respectivos métodos e a
base para a autenticação está pronta. Nos resta agora, implementar a
recuperação dos dados e a troca de mensagens entre os objetos, assim, na
solution explorer, na pasta Controllers dê um duplo clique sobre o arquivo “HomeController.cs“. Adicione uma nova actionIndex” e decore-a com o atributo [HttpPost], conforme apresentado pela Listagem 8.

[csharp]
[HttpPost]
public ActionResult Index(FormCollection frmLogin)
{
string _Username, _Password;

//Recuperando valores do formulário
_Username = frmLogin["txtUsuario"];
_Password = frmLogin["txtSenha"];

//Se alguma das informações
if (UsersRepository.AutenticarUsuario(_Username, _Password))
{
return RedirectToAction("Sucesso", "Home");
}
else
{
return RedirectToAction("Falha", "Home");
}
}
[/csharp]

Pronto,
nosso processo de autenticação está concluído. Colocando nossa
aplicação e testando-a, obtemos os resultados apresentados pelas Figuras
12 e 13.

Espero que este artigo
possa ajudá-lo de alguma forma em seus projetos futuros com ASP.NET MVC
3. Dúvidas, sugestões e possíveis correções, por favor, envie através
do formulário de contato deste site ou através dos comentários deste artigo.

Grande abraço a todos :-).