O NeoMatrix Tech está de casa nova!

Você deverá ser redirecionado em 6 segundos. Se não, visite:
http://www.leonelfraga.com/neomatrixtech
e atualize seus favoritos.

Aviso IMPORTANTÍSSIMO!

Aviso aos navegantes:

O NeoMatrix Tech mudou de casa!!!

A partir de agora, acessem pelo novo endereço:

http://www.leonelfraga.com/neomatrixtech

Ué... mas é só o domínio mudou de lugar?

R: Na verdade, não é bem assim hehe. Este domínio que você acessa agora aponta para um blog hospedado no Blogger, enquanto no novo, aponta para um blog na plataforma Wordpress, hospedagem própria, muito mais rápida e com um layout mais agradável de ler ;)

Não vou fechar este domínio igual ao que eu fiz com o NM Light (que já está 100% na nova plataforma). Talvez beeeeeeem depois eu faça isso.

Todos os posts daqui se encontram lá, e novos posts serão colocados somente no novo endereço.
A única coisa que não consegui importar foram os comentários. Mas em breve vai ter um post contando sobre a epopéia que foi migrar o NeoMatrix Tech!

Somente vou fechar a área de comentários daqui. Caso queiram comentar, favor ver o post correspondente no "Novo NeoMatrix Tech" e comentem por lá. É bem melhor! (pena que os permalinks "amigáveis para SEO" não funcionam lá, dá erro 404 e não consigo fazer a configuração funcionar. E olha que eu já vi vários artigos falando desse assunto :( ).

Quem assina o feed, já está lendo o conteúdo do novo NeoMatrix Tech!

sábado, 30 de agosto de 2008

Controle de Usuários para Sistemas ASP.NET - Parte 3 - Classe de Usuários

Olá pessoal! Enfim vamos a terceira parte do projeto de controle de usuários para aplicações ASP.NET. Nas partes anteriores, modelamos a base de dados e escrevemos a classe que manipula os perfis de usuário. Nesta parte iremos abordar a classe responsável pelas tarefas relativas ao usuário: inserir, atualizar, excluir e autenticar. Vamos ao código, lembrando sempre que o exemplo completo pode ser pego na Página de Suporte no link de download no final desta postagem.

 

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using System.Data;
   5: using System.Collections;
   6: using System.Security.Cryptography;
   7:  
   8: namespace UserManager
   9: {
  10:     public class TUsuario : Conexao
  11:     {
  12:         #region Parte VO
  13:         private int _USUARIO_ID = 0;
  14:         private string _NOME = "";
  15:         private string _LOGIN = "";
  16:         private int _PERFIL_ID = 0;
  17:         private string _SENHA = ""; //a ser criptografada quando for gravada no BD
  18:         private string _STATUS = "";
  19:         private string _SqlSearch = "";
  20:         private TPerfilUsuario _PERFIL = new TPerfilUsuario();
  21:  
  22:         public int UsuarioId { get { return _USUARIO_ID; } set { _USUARIO_ID = value; } }
  23:         public string Nome { get { return _NOME; } set { _NOME = value; } }
  24:         public string Login { get { return _LOGIN; } set { _LOGIN = value; } }
  25:         public int PerfilId { get { return _PERFIL_ID; } set { _PERFIL_ID = value; } }
  26:         public string Senha { get { return _SENHA; } set { _SENHA = value; } }
  27:         public string Status { get { return _STATUS; } set { _STATUS = value; } }
  28:         public TPerfilUsuario Perfil { get { _PERFIL.PerfilId = _PERFIL_ID; _PERFIL.SetByID(); return _PERFIL; } }
  29:         #endregion
  30:  
  31:         #region Parte DAO
  32:         public void SetByID()
  33:         {
  34:             try
  35:             {
  36:                 this._SelectSQL = "select * from USUARIOS where USUARIO_ID = @USUARIO_ID ";
  37:                 ClearSQLParams();
  38:                 AddSQLParam("@USUARIO_ID", this._USUARIO_ID, ParameterDirection.Input);
  39:  
  40:                 this.Select(true);
  41:                 if (this.ListaCamposTabela.Count > 0)
  42:                 {
  43:                     this._USUARIO_ID = Consts.Funcoes.NullOrInt(this["USUARIO_ID"]);
  44:                     this._NOME = Consts.Funcoes.NullOrString(this["NOME"]);
  45:                     this._LOGIN = Consts.Funcoes.NullOrString(this["LOGIN"]);
  46:                     this._PERFIL_ID = Consts.Funcoes.NullOrInt(this["PERFIL_ID"]);
  47:                     this._STATUS = Consts.Funcoes.NullOrString(this["STATUS"]);
  48:                     this._PERFIL.PerfilId = _PERFIL_ID;
  49:                     this._PERFIL.SetByID();
  50:                 }
  51:                 else
  52:                 {
  53:                     this.fMsgInfo = "Registro não encontrado";
  54:                 }
  55:             }
  56:             catch (Exception ex)
  57:             {
  58:                 this.fMsgInfo = "Erro ao obter dados -> " + ex.Message;
  59:             }
  60:         }
  61:  
  62:         public bool Inserir()
  63:         {
  64:             bool st = false;
  65:             ClearSQLParams();
  66:             AddSQLParam("@USUARIO_ID", Consts.Funcoes.ZeroOrDBNull(this._USUARIO_ID), ParameterDirection.Output);
  67:             AddSQLParam("@NOME", Consts.Funcoes.ValueOrDBNull(this._NOME.ToUpper().Trim()), ParameterDirection.Input);
  68:             AddSQLParam("@LOGIN", Consts.Funcoes.ValueOrDBNull(this._LOGIN.ToUpper().Trim()), ParameterDirection.Input);
  69:             AddSQLParam("@PERFIL_ID", Consts.Funcoes.ZeroOrDBNull(this._PERFIL_ID), ParameterDirection.Input);
  70:             AddSQLParam("@SENHA", DBNull.Value, ParameterDirection.Input);
  71:             AddSQLParam("@STATUS", Consts.Funcoes.ValueOrDBNull(this._STATUS.ToUpper().Trim()), ParameterDirection.Input);
  72:             List<TCampoCadastro> res = executeStoredProcedure("SP_INSERE_USUARIO", true);
  73:  
  74:             if (st = (res.Count > 0))
  75:             {
  76:                 _USUARIO_ID = Consts.Funcoes.NullOrInt(res[0].Valor);
  77:                 TrocarSenha(_SENHA);
  78:                 fMsgInfo = "Inserido com sucesso!";
  79:             }
  80:             return st;
  81:         }
  82:  
  83:         public bool Atualizar()
  84:         {
  85:             bool st = false;
  86:             _UpdateSQL = "UPDATE USUARIOS set USUARIO_ID = @USUARIO_ID, NOME = @NOME, LOGIN = @LOGIN, PERFIL_ID = @PERFIL_ID, STATUS = @STATUS where USUARIO_ID = @USUARIO_ID ";
  87:             ClearSQLParams();
  88:             AddSQLParam("@USUARIO_ID", Consts.Funcoes.ZeroOrDBNull(this._USUARIO_ID), ParameterDirection.Input);
  89:             AddSQLParam("@NOME", Consts.Funcoes.ValueOrDBNull(this._NOME.ToUpper().Trim()), ParameterDirection.Input);
  90:             AddSQLParam("@LOGIN", Consts.Funcoes.ValueOrDBNull(this._LOGIN.ToUpper().Trim()), ParameterDirection.Input);
  91:             AddSQLParam("@PERFIL_ID", Consts.Funcoes.ZeroOrDBNull(this._PERFIL_ID), ParameterDirection.Input);
  92:             AddSQLParam("@STATUS", Consts.Funcoes.ValueOrDBNull(this._STATUS.ToUpper().Trim()), ParameterDirection.Input);
  93:  
  94:             if (st = Salvar(OperacaoBD.opUpdate, true))
  95:             {
  96:                 fMsgInfo = "Atualizado com sucesso!";
  97:             }
  98:             return st;
  99:         }
 100:  
 101:         public bool _Excluir()
 102:         {
 103:             _DeleteSQL = "delete from USUARIOS where USUARIO_ID = @USUARIO_ID ";
 104:             ClearSQLParams();
 105:             AddSQLParam("@USUARIO_ID", Consts.Funcoes.ZeroOrDBNull(this._USUARIO_ID), ParameterDirection.Input);
 106:  
 107:             bool st = false;
 108:             if (st = Salvar(OperacaoBD.opDelete, true))
 109:             {
 110:                 fMsgInfo = "Excluído com sucesso!";
 111:             }
 112:             return st;
 113:         }
 114:  
 115:         private List<TUsuario> ListaGenerica()
 116:         {
 117:             List<TUsuario> l = new List<TUsuario>();
 118:             DataTable dt = this.getTable(_SqlSearch, (SQLParams.Count > 0));
 119:             foreach (DataRow r in dt.Rows)
 120:             {
 121:                 l.Add(new TUsuario());
 122:                 l[l.Count - 1].UsuarioId = Consts.Funcoes.NullOrInt(r["USUARIO_ID"]);
 123:                 l[l.Count - 1].Nome = Consts.Funcoes.NullOrString(r["NOME"]);
 124:                 l[l.Count - 1].Login = Consts.Funcoes.NullOrString(r["LOGIN"]);
 125:                 l[l.Count - 1].PerfilId = Consts.Funcoes.NullOrInt(r["PERFIL_ID"]);
 126:                 l[l.Count - 1].Status = Consts.Funcoes.NullOrString(r["STATUS"]);
 127:             }
 128:             return l;
 129:         }
 130:  
 131:         public List<TUsuario> ListarTodos()
 132:         {
 133:             _SqlSearch = "select * from USUARIOS";
 134:             
 135:             return ListaGenerica();
 136:         }
 137:  
 138:         #endregion
 139:  
 140:         #region Parte BO
 141:  
 142:         public static List<TUsuario> Listar(string pNome, string pLogin, string pStatus, int pPerfil)
 143:         {
 144:             List<TUsuario> results = new List<TUsuario>();
 145:             TUsuario u = new TUsuario();
 146:             try
 147:             {
 148:                 u._SqlSearch = "select * from USUARIOS where (1=1) ";
 149:                 if (!pNome.Equals(""))
 150:                 {
 151:                     u._SqlSearch += " and NOME like @NOME ";
 152:                     u.AddSQLParam("@NOME", pNome.Trim().ToUpper() + "%", ParameterDirection.Input);
 153:                 }
 154:                 if (!pLogin.Equals(""))
 155:                 {
 156:                     u._SqlSearch += " and LOGIN like @LOGIN ";
 157:                     u.AddSQLParam("@LOGIN", pLogin.Trim().ToUpper() + "%", ParameterDirection.Input);
 158:                 }
 159:                 if (!pStatus.Equals(""))
 160:                 {
 161:                     u._SqlSearch += " and STATUS = @ST ";
 162:                     u.AddSQLParam("@ST", pStatus.Trim().ToUpper(), ParameterDirection.Input);
 163:                 }
 164:                 if (!pPerfil.Equals(0))
 165:                 {
 166:                     u._SqlSearch += " and PERFIL_ID = @PE ";
 167:                     u.AddSQLParam("@PE", pPerfil, ParameterDirection.Input);
 168:                 }
 169:                 results = u.ListaGenerica();
 170:             }
 171:             finally
 172:             {
 173:                 u.Dispose();
 174:             }
 175:             return results;
 176:         }
 177:  
 178:         public bool TrocarSenha(string pNovaSenha)
 179:         {
 180:             bool ok = false;
 181:             //Criptografa a senha atual utilizando Hash MD5 (não poderá recuperá-la depois :-( )
 182:             MD5CryptoServiceProvider hash = new MD5CryptoServiceProvider();
 183:             UTF8Encoding encode = new UTF8Encoding();
 184:             byte[] senhacripto = hash.ComputeHash(encode.GetBytes(pNovaSenha));
 185:  
 186:             //Agora salvamos a senha criptografada no BD.
 187:             string isql = "update USUARIOS set SENHA = @SENHA where USUARIO_ID = @USUID";
 188:             ClearSQLParams();
 189:             AddSQLParam("@SENHA", senhacripto, ParameterDirection.Input);
 190:             AddSQLParam("@USUID", this._USUARIO_ID, ParameterDirection.Input);
 191:             if (ok = executaSQL(isql, true))
 192:             {
 193:                 fMsgInfo = "Senha atualizada com sucesso!";
 194:             }
 195:             return ok;
 196:         }
 197:  
 198:         private bool _Autenticar(string pLogin, string pSenha, out int pUsuID)
 199:         {
 200:             pUsuID = 0;
 201:             string isql = "select USUARIO_ID,SENHA from USUARIOS where LOGIN = @LOGIN";
 202:             ClearSQLParams();
 203:             AddSQLParam("@LOGIN", pLogin.Trim().ToUpper(), ParameterDirection.Input);
 204:             DataTable dt = getTable(isql, true);
 205:             if (dt.Rows.Count > 0)
 206:             { 
 207:                 //Criptofrafa a senha atual
 208:                 MD5CryptoServiceProvider hash = new MD5CryptoServiceProvider();
 209:                 UTF8Encoding encode = new UTF8Encoding();
 210:                 byte[] senhacripto = hash.ComputeHash(encode.GetBytes(pSenha));
 211:  
 212:                 //Obtém a senha atual do BD
 213:                 byte[] pwd = (byte[])dt.Rows[0][1];
 214:                 if (Consts.Funcoes.CompararIgualdadeByteArray(pwd, senhacripto))
 215:                 {
 216:                     pUsuID = (int)dt.Rows[0][0];
 217:                     return true;
 218:                 }
 219:                 else
 220:                 {
 221:                     fMsgInfo = "Usuário ou senha inválidos";
 222:                     return false;
 223:                 }
 224:             }
 225:             else
 226:             {
 227:                 fMsgInfo = "Usuário inexistente.";
 228:                 return false;
 229:             }
 230:         }
 231:  
 232:         public static TUsuario Autenticar(string pLogin, string pSenha, out string Mensagem)
 233:         {
 234:             TUsuario u = new TUsuario();
 235:             Mensagem = "";
 236:             int pID = 0;
 237:             if (u._Autenticar(pLogin, pSenha, out pID))
 238:             {
 239:                 u._USUARIO_ID = pID;
 240:                 u.SetByID();
 241:                 return u;
 242:             }
 243:             else
 244:             {
 245:                 Mensagem = u.MsgInfo;
 246:                 return null;
 247:             }
 248:         }
 249:  
 250:         public static bool Excluir(int pID, out string msg)
 251:         {
 252:             TUsuario p = new TUsuario();
 253:             bool st = false;
 254:             try
 255:             {
 256:                 p._USUARIO_ID = pID;
 257:                 st = p._Excluir();
 258:                 msg = p.MsgInfo;
 259:             }
 260:             finally
 261:             {
 262:                 p.Dispose();
 263:             }
 264:             return st;
 265:         }
 266:  
 267:  
 268:         #endregion
 269:     }
 270: }

Coloquei o código completo da classe TUsuario (eita mania de Delphero colocar "T" nos nomes das classes hehe), assim é melhor para acompanhar a explicação, né?

Temos aqui campos (e as suas respectivas propriedades com get/set) para guardar o ID, Nome, Login, ID do Perfil e Status do Usuário. Também temos um campo que guarda o perfil completo (incluindo os módulos que este usuário pode acessar) do tipo TPerfilUsuario.
Também fazemos as pesquisas seguindo o modelo da classe TPerfilUsuario, ou seja, temos uma variável que guarda uma instrução SQL e um método genérico que retorna os campos ditos por esta instrução SQL, e os demais métodos de pesquisa apenas manipulam esta instrução.

Os métodos de inserção, atualização e exclusão fazem chamadas aos métodos da classe Conexao, com o método de inserção executando uma stored procedure que retorna o ID gerado e o Atualizar e o Excluir utilizando instruções SQL simples, fazendo chamada ao método Salvar, de Conexao.
O método Inserir() tem uma coisa interessante: Após salvar o registro através da stored procedure, caso tenha sucesso é chamado o método TrocarSenha.

Lembra que o campo SENHA no nosso banco de dados está definido como BLOB do tipo binário e na classe a propriedade foi definida como String? Então, neste blob guardaremos na verdade o Hash MD5 da senha informada na propriedade Senha.
Vamos destacar o método TrocarSenha:

   1: public bool TrocarSenha(string pNovaSenha)
   2: {
   3:     bool ok = false;
   4:     //Criptografa a senha atual utilizando Hash MD5 (não poderá recuperá-la depois :-( )
   5:     MD5CryptoServiceProvider hash = new MD5CryptoServiceProvider();
   6:     UTF8Encoding encode = new UTF8Encoding();
   7:     byte[] senhacripto = hash.ComputeHash(encode.GetBytes(pNovaSenha));
   8:  
   9:     //Agora salvamos a senha criptografada no BD.
  10:     string isql = "update USUARIOS set SENHA = @SENHA where USUARIO_ID = @USUID";
  11:     ClearSQLParams();
  12:     AddSQLParam("@SENHA", senhacripto, ParameterDirection.Input);
  13:     AddSQLParam("@USUID", this._USUARIO_ID, ParameterDirection.Input);
  14:     if (ok = executaSQL(isql, true))
  15:     {
  16:         fMsgInfo = "Senha atualizada com sucesso!";
  17:     }
  18:     return ok;
  19: }

Primeiramente, inicializamos a classe MD5CryptoServiceProvider, que será a responsável por calcular o MD5 da string que passaremos como senha. O método ComputeHash desta função retorna um array de bytes com o hash calculado e recebe como parâmetro um outro array de bytes com a informação a qual iremos calcular este hash. Instanciamos um objeto da classe UTF8Encoding justamente para transformar esta string em um array de bytes compatível com o método ComputeHash.
Em seguida, damos um UPDATE via instrução SQL no registro correspondente ao usuário que irá ter a senha trocada. Se tudo der certo, retornamos true.

Agora, vamos destacar os métodos que realizam a autenticação do usuário no sistema:

   1: private bool _Autenticar(string pLogin, string pSenha, out int pUsuID)
   2: {
   3:     pUsuID = 0;
   4:     string isql = "select USUARIO_ID,SENHA from USUARIOS where LOGIN = @LOGIN";
   5:     ClearSQLParams();
   6:     AddSQLParam("@LOGIN", pLogin.Trim().ToUpper(), ParameterDirection.Input);
   7:     DataTable dt = getTable(isql, true);
   8:     if (dt.Rows.Count > 0)
   9:     { 
  10:         //Criptofrafa a senha atual
  11:         MD5CryptoServiceProvider hash = new MD5CryptoServiceProvider();
  12:         UTF8Encoding encode = new UTF8Encoding();
  13:         byte[] senhacripto = hash.ComputeHash(encode.GetBytes(pSenha));
  14:  
  15:         //Obtém a senha atual do BD
  16:         byte[] pwd = (byte[])dt.Rows[0][1];
  17:         if (Consts.Funcoes.CompararIgualdadeByteArray(pwd, senhacripto))
  18:         {
  19:             pUsuID = (int)dt.Rows[0][0];
  20:             return true;
  21:         }
  22:         else
  23:         {
  24:             fMsgInfo = "Usuário ou senha inválidos";
  25:             return false;
  26:         }
  27:     }
  28:     else
  29:     {
  30:         fMsgInfo = "Usuário inexistente.";
  31:         return false;
  32:     }
  33: }
  34:  
  35: public static TUsuario Autenticar(string pLogin, string pSenha, out string Mensagem)
  36: {
  37:     TUsuario u = new TUsuario();
  38:     Mensagem = "";
  39:     int pID = 0;
  40:     if (u._Autenticar(pLogin, pSenha, out pID))
  41:     {
  42:         u._USUARIO_ID = pID;
  43:         u.SetByID();
  44:         return u;
  45:     }
  46:     else
  47:     {
  48:         Mensagem = u.MsgInfo;
  49:         return null;
  50:     }
  51: }

Temos o método privado _Autenticar que fará a validação do usuário/senha informados no momento do login. A verificação do login é feita através de uma instrução SELECT, a qual retornaremos seu resultado num DataTable.
Se o login informado existir na base de dados, aí sim é feita a verificação da senha. Como a função MD5 é de mão única, ou seja, não podemos obter a senha em formato de string a partir de seu hash, para poder comparar a senha informada com a armazenada no BD precisamos calcular o hash MD5 da senha informada no login e por fim comparar os dois hashs.
Só que agora tem um probleminha: Se fizermos a comparação direta de dois arrays utilizando o operador "==" (para variáveis do tipo array não existe o método Equals), esta comparação irá retornar sempre falsa, ou seja, ela nem é feita por sinal. Uma forma de contornar isso é comparar o array elemento a elemento. Para fazer tal comparação, criei um método chamado CompararIgualdadeByteArray, que faz a comparação entre dois arrays de bytes. Caso todos os elementos sejam iguais, retornará true.
Segue o método:

   1: public static bool CompararIgualdadeByteArray(byte[] a1, byte[] a2)
   2: {
   3:     bool r = true;
   4:     for (int i = 0; i < a1.Length; i++)
   5:     {
   6:         if (a2[i] != a1[i])
   7:         {
   8:             r = false;
   9:             break;
  10:         }
  11:     }
  12:     return r;
  13: }

Esta é uma função simples, que varre cada elemento do primeiro array e verifica se na mesma posição do segundo array há um elemento igual. Se houver alguma diferença, a função é abortada e retorna falso. No nosso exemplo, ela está dentro do arquivo Consts.Funcoes.
Como eu tenho certeza que pelo menos neste projeto estarei trabalhando com arrays de mesmo tamanho (16 bytes) e mesmo tipo de dado em cada elemento, não fiz a verificação do tamanho dos arrays :-)

Voltando à autenticação... Após a senha ser verificada, caso esteja OK a função irá retornar true e o ID do usuário correspondente no argumento de saída pUsuID.

Note que o método _Autenticar é declarado como privado. Iremos expor este método através do método público e estático Autenticar, em que iremos informar o usuário e a senha e se a autenticação for bem sucedida irá retornar uma instância de TUsuario, com todas as informações do usuário (inclusive perfil e módulos que pode acessar) carregadas.

É isso! Agora já temos as classes necessárias, no próximo artigo iremos implementar a interface em ASP.NET que irá manipular este cadastro.

Download:
Exemplo Sistema de Login em ASP.NET (com BD Firebird)  (289 kB)

Até lá e abraço a todos!

[Update 26/02/2008: Para facilitar o download, ao invés da página de suporte hospedada no Geocities, estarei movendo os arquivos para hospedagem própria, diretamente no domínio leonelfraga.com e colocando os links diretos para o arquivo.]

Leia o restante deste post...

  © Blogger templates ProBlogger Template by Ourblogtemplates.com 2008 - Editado e configurado por Leonel F.

Voltar ao TOPO