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.]
0 comentários:
Postar um comentário