9
1 JSF Filter: Criando um sistema de login com criptografia MD5 Veja neste artigo como criar um sistema de login utilizando a criptografia MD5 e JSF 2.0 e Filter. Neste artigo veremos como construir um sistema de login com JSF 2.0 utilizando Filters. Nosso sistema de login contará ainda com um nível a mais de segurança, implementando a criptografia MD5 nas senhas, evitando assim que as mesmas possam ser visualizadas por qualquer um. Em JSF, o Filter é um recurso que possibilita o gerenciamento de todas as requisições HTTP do seu servidor, filtrando o endereço que está sendo acessado. Sendo assim, quando o usuário João acessar aquela URL que é proibida, você pode imediatamente redirecioná-lo para outro endereço, antes que a resposta seja dada ao cliente. O MD5 ou Message-Digest Algorithm 5 é um algoritmo hash de 128bits unidirecional e pelo simples fato de ser unidirecional não há como decriptar o mesmo, ou seja, se você criptografar determinada senha em MD5, não terá como fazer o processo inverso, que seria descobrir a senha contida no MD5. Então se não há como decriptar um hash MD5, como saberemos se a senha que o usuário digitou está correta? Pense um pouco, nós podemos criptografar a senha digitada pelo usuário para MD5 e simplesmente comparar os dois hash MD5, sendo assim, se os dois forem iguais, saberemos que a senha está correta. Mas caso o usuário esqueça a senha, não há maneira de recuperá-la, apenas gerar uma nova senha. É por esse motivo que em muitos sistemas a recuperação da senha é na verdade a geração de uma nova senha. Criptografar a senha em MD5 lhe dá muitos pontos em segurança, confiabilidade e qualidade. Começando pelo fato de que qualquer pessoa que tiver acesso ao banco de dados não poderá visualizar as senhas de nenhum usuário, pois imagine se o usuário “joao2014” utiliza sua senha para outras coisas como: bank online, e-mail, facebook e etc. Por isso, a senha do usuário deve ser uma informação sigilosa que nem o desenvolvedor deve ter conhecimento, por uma questão simples de ética profissional. Existem ainda outros algoritmos HASH para criptografar informações, mas não é nosso foco estudá-los. Construindo sistema de login Além do JSF, trabalharemos com outros frameworks para nos auxiliar no desenvolvimento desta aplicação, mas não é obrigatoriedade usá-los, pois você pode adaptar o mesmo para a sua realidade. Usaremos então o JPA/Hibernate, Spring Framework e o nosso banco de dados será o PostgreSQL, mas fique a vontade para escolher outro de sua preferência. Lembrando que não mostraremos configurações básicas de JPA ou mesmo Spring, já que estamos partindo do principio que o foco deste artigo é mostrar a construção de um Login com Filter. Observe na Listagem 1 a criação da tabela usuario. Listagem 1. Criação da Tabela CREATE TABLE usuario ( id serial NOT NULL, data_cadastro date, email character varying(255),

JSF Filter - Criando Um SistemaJSF Filter - Criando um sistema de login com criptografia MD5 de Login Com Criptografia MD5

Embed Size (px)

DESCRIPTION

JSF Filter - Criando um sistema de login com criptografia MD5

Citation preview

Page 1: JSF Filter - Criando Um SistemaJSF Filter - Criando um sistema de login com criptografia MD5 de Login Com Criptografia MD5

1

JSF Filter: Criando um sistema de login com

criptografia MD5

Veja neste artigo como criar um sistema de login utilizando a criptografia MD5 e JSF 2.0 e

Filter.

Neste artigo veremos como construir um sistema de login com JSF 2.0 utilizando Filters. Nosso sistema de

login contará ainda com um nível a mais de segurança, implementando a criptografia MD5 nas senhas,

evitando assim que as mesmas possam ser visualizadas por qualquer um.

Em JSF, o Filter é um recurso que possibilita o gerenciamento de todas as requisições HTTP do seu

servidor, filtrando o endereço que está sendo acessado. Sendo assim, quando o usuário João acessar aquela

URL que é proibida, você pode imediatamente redirecioná-lo para outro endereço, antes que a resposta seja

dada ao cliente.

O MD5 ou Message-Digest Algorithm 5 é um algoritmo hash de 128bits unidirecional e pelo simples fato de

ser unidirecional não há como decriptar o mesmo, ou seja, se você criptografar determinada senha em MD5,

não terá como fazer o processo inverso, que seria descobrir a senha contida no MD5. Então se não há como

decriptar um hash MD5, como saberemos se a senha que o usuário digitou está correta? Pense um pouco,

nós podemos criptografar a senha digitada pelo usuário para MD5 e simplesmente comparar os dois hash

MD5, sendo assim, se os dois forem iguais, saberemos que a senha está correta. Mas caso o usuário esqueça

a senha, não há maneira de recuperá-la, apenas gerar uma nova senha. É por esse motivo que em muitos

sistemas a recuperação da senha é na verdade a geração de uma nova senha.

Criptografar a senha em MD5 lhe dá muitos pontos em segurança, confiabilidade e qualidade. Começando

pelo fato de que qualquer pessoa que tiver acesso ao banco de dados não poderá visualizar as senhas de

nenhum usuário, pois imagine se o usuário “joao2014” utiliza sua senha para outras coisas como: bank

online, e-mail, facebook e etc.

Por isso, a senha do usuário deve ser uma informação sigilosa que nem o desenvolvedor deve ter

conhecimento, por uma questão simples de ética profissional. Existem ainda outros algoritmos HASH para

criptografar informações, mas não é nosso foco estudá-los.

Construindo sistema de login

Além do JSF, trabalharemos com outros frameworks para nos auxiliar no desenvolvimento desta aplicação,

mas não é obrigatoriedade usá-los, pois você pode adaptar o mesmo para a sua realidade. Usaremos então o

JPA/Hibernate, Spring Framework e o nosso banco de dados será o PostgreSQL, mas fique a vontade para

escolher outro de sua preferência. Lembrando que não mostraremos configurações básicas de JPA ou

mesmo Spring, já que estamos partindo do principio que o foco deste artigo é mostrar a construção de um

Login com Filter.

Observe na Listagem 1 a criação da tabela usuario.

Listagem 1. Criação da Tabela

CREATE TABLE usuario

(

id serial NOT NULL,

data_cadastro date,

email character varying(255),

Page 2: JSF Filter - Criando Um SistemaJSF Filter - Criando um sistema de login com criptografia MD5 de Login Com Criptografia MD5

2

nome character varying(255),

senha character varying(255),

CONSTRAINT usuario_pkey PRIMARY KEY (id ),

CONSTRAINT usuario_email_key UNIQUE (email )

)

Criada a tabela acima em nosso banco de dados, precisamos criar nossa classe Usuario, que ficará como

definido na Listagem 2.

Listagem 2. Criando Classe Usuario

import java.util.Date;

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.NamedQueries;

import javax.persistence.NamedQuery;

import javax.persistence.Table;

import javax.persistence.Temporal;

import javax.persistence.TemporalType;

import javax.persistence.Transient;

@Entity

@NamedQueries(value = { @NamedQuery(name = "Usuario.findByEmailSenha",

query = "SELECT c FROM Usuario c "

+ "WHERE c.email = :email AND c.senha = :senha")})

@Table(name = "usuario")

public class Usuario {

/**

*

*/

private static final long serialVersionUID = 1L;

@Transient

public static final String FIND_BY_EMAIL_SENHA =

"Usuario.findByEmailSenha";

@Id

@GeneratedValue(strategy =

javax.persistence.GenerationType.IDENTITY)

private Integer id;

@Column

private String nome;

@Column(unique = true)

private String email;

@Column

private String senha;

@Column(name = "data_cadastro")

@Temporal(TemporalType.DATE)

private Date dataCadastro;

public Integer getId() {

return id;

}

public void setId(Integer id) {

this.id = id;

}

Page 3: JSF Filter - Criando Um SistemaJSF Filter - Criando um sistema de login com criptografia MD5 de Login Com Criptografia MD5

3

public String getNome() {

return nome;

}

public void setNome(String nome) {

this.nome = nome.trim();

}

public String getEmail() {

return email;

}

public void setEmail(String email) {

this.email = email.trim().toLowerCase();

}

public String getSenha() {

return senha;

}

public void setSenha(String senha) {

this.senha = senha.trim();

}

public Date getDataCadastro() {

return dataCadastro;

}

public void setDataCadastro(Date dataCadastro) {

this.dataCadastro = dataCadastro;

}

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + ((id == null) ? 0 :

id.hashCode());

return result;

}

@Override

public boolean equals(Object obj) {

if (this == obj)

return true;

if (obj == null)

return false;

if (getClass() != obj.getClass())

return false;

return (obj instanceof AbstractBean) ?

(this.getId() == null ? this == obj :

this.getId().equals((

(AbstractBean)obj).getId())):false;

}

}

Nossa classe é completa e possui todas as notações JPA necessárias, juntamente com os métodos equals() e

hashCode() e as namedQueries que nos serão úteis para pesquisar os usuários no banco e dados.

Como dissemos anteriormente, se fossemos mostrar detalhes da construção de cada parte do sistema com

por exemplo: DAO, (Data Access Object), BO (Bussiness Object) e Configurações, nosso artigo perderia o

Page 4: JSF Filter - Criando Um SistemaJSF Filter - Criando um sistema de login com criptografia MD5 de Login Com Criptografia MD5

4

foco, então mostraremos agora os métodos de verificação de login presentes em nosso BO, chamado

UsuarioBOImpl (Listagem 3).

Listagem 3. Método de validação do usuário no UsuarioBOImpl

// Verifica se usuário existe ou se pode logar

public Usuario isUsuarioReadyToLogin(String email, String senha) {

try {

email = email.toLowerCase().trim();

logger.info("Verificando login do usuário " + email);

List retorno = dao.findByNamedQuery(

Usuario.FIND_BY_EMAIL_SENHA,

new NamedParams("email", email

.trim(), "senha", convertStringToMd5(senha)));

if (retorno.size() == 1) {

Usuario userFound = (Usuario) retorno.get(0);

return userFound;

}

return null;

} catch (DAOException e) {

e.printStackTrace();

throw new BOException(e.getMessage());

}

}

Bom, nosso método recebe como parâmetro um Email e Senha, que são passados para o DAO utilizando

aquela NamedQuery chamada “findByEmailSenha” que definimos em nosso Bean Usuario. O importante

aqui é perceber duas coisas:

1. A senha que é passada por parâmetro não está criptografada, sendo assim, não conseguiríamos comparar com a senha no banco. Então antes de passar o parâmetro ao DAO, convertemos a senha para MD5 com o método “convertStringToMD5(senha)”.

2. Caso esse retorno do DAO seja uma Lista com um elemento, significa que o usuário foi encontrado no banco e retornamos o mesmo, caso contrário o retorno será “null”.

Veja na Listagem 4 como é nosso método para converter de String para MD5.

Listagem 4. Método conversor de String para MD5

private String convertStringToMd5(String valor) {

MessageDigest mDigest;

try {

//Instanciamos o nosso HASH MD5, poderíamos usar outro como

//SHA, por exemplo, mas optamos por MD5.

mDigest = MessageDigest.getInstance("MD5");

//Convert a String valor para um array de bytes em MD5

byte[] valorMD5 = mDigest.digest(valor.getBytes("UTF-8"));

//Convertemos os bytes para hexadecimal, assim podemos salvar

//no banco para posterior comparação se senhas

StringBuffer sb = new StringBuffer();

for (byte b : valorMD5){

sb.append(Integer.toHexString((b & 0xFF) |

0x100).substring(1,3));

}

return sb.toString();

Page 5: JSF Filter - Criando Um SistemaJSF Filter - Criando um sistema de login com criptografia MD5 de Login Com Criptografia MD5

5

} catch (NoSuchAlgorithmException e) {

// TODO Auto-generated catch block

e.printStackTrace();

return null;

} catch (UnsupportedEncodingException e) {

// TODO Auto-generated catch block

e.printStackTrace();

return null;

}

}

Então agora temos dois métodos importantes para nossa aplicação, no BO. Um para verificar se o usuário é

válido e outro para converter a senha para MD5. O próximo passo é criar um ManagedBean que comunicará

a página XHTML de Login com o nosso BO, que se UsuarioMBImpl, e também mostraremos apenas os

métodos importantes. Observe a Listagem 5.

Listagem 5. ManagedBean para Login do Usuário

//True se usuário está logado e false caso contrário

private boolean loggedIn;

//Armazena o usuário logado

private Usuario usuarioLogado;

//Email e senha digitado pelo usuário na página XHTML

private String email, senha;

public String getEmail() {

return email;

}

public void setEmail(String email) {

this.email = email;

}

public String getSenha() {

return senha;

}

public void setSenha(String senha) {

this.senha = senha;

}

//Realiza o login caso de tudo certo

public String doLogin(){

//Verifica se o e-mail e senha existem e se o usuario pode logar

Usuario usuarioFound = (Usuario)

usuarioBO.isUsuarioReadyToLogin(email, senha);

//Caso não tenha retornado nenhum usuario, então mostramos um erro

//e redirecionamos ele para a página login.xhtml

//para ele realiza-lo novamente

if (usuarioFound == null){

addErrorMessage("Email ou Senha errado, tente novamente !");

FacesContext.getCurrentInstance().validationFailed();

return "/login/login.xhtml?faces-redirect=true";

}else{

//caso tenha retornado um usuario, setamos a variável loggedIn

//como true e guardamos o usuario encontrado na variável

//usuarioLogado. Depois de tudo, mandamos o usuário

//para a página index.xhtml

Page 6: JSF Filter - Criando Um SistemaJSF Filter - Criando um sistema de login com criptografia MD5 de Login Com Criptografia MD5

6

loggedIn = true;

usuarioLogado = usuarioFound;

return "/restricted/index.xhtml?faces-redirect=true";

}

}

//Realiza o logout do usuário logado

public String doLogout(){

//Setamos a variável usuarioLogado como nulo, ou seja, limpamos

//os dados do usuário que estava logado e depois setamos a variável

//loggedIn como false para sinalizar que o usuário não está mais

//logado

usuarioLogado = null;

loggedIn = false;

//Mostramos um mensagem ao usuário e redirecionamos ele para a

//página de login

addInfoMessage("Logout realizado com sucesso !");

return "/login/login.xhtml?faces-redirect=true";

}

No código acima temos uma chamada ao nosso método “isUsuarioReadyToLogin()” que está no nosso BO

criado anteriormente. Caso a instância da variável “usuarioFound” seja nula, significa que não foi

encontrado nenhum usuário na base, então simplesmente retornamos um erro ao usuário e redirecionamos o

mesmo para a página de login novamente. Caso seja encontrado algum usuário setamos a variável

“loggedIn” como true, guardamos os dados do usuário logado na variável usuarioLogado e redirecionamos

ele para o index.xhtml, ou seja, a página de bem vindo.

O método de logout é simples, apenas fazemos o inverso que fizemos no método de login, setando o

loggedIn como false e o usuarioLogado como nulo.

Vamos ver agora nossa página XHTML de Login, conforme a Listagem 6.

Listagem 6. login.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"

xmlns:h="http://java.sun.com/jsf/html"

xmlns:f="http://java.sun.com/jsf/core"

xmlns:ui="http://java.sun.com/jsf/facelets"

xmlns:p="http://primefaces.org/ui"

xmlns:pe="http://primefaces.org/ui/extensions">

<h:head>

<h:outputStylesheet library="css" name="login.css" />

</h:head>

<h:body>

<h:form id="formLogin" enctype="multipart/form-data">

<p:growl autoUpdate="true" id="messages" />

<p:panelGrid styleClass="semBorda" columns="2">

<h:outputText value="Email: " />

<p:inputText value="#{usuarioMB.email}"

styleClass="lowercase"

size="35" required="true"

requiredMessage="O Email é obrigatório" />

<h:outputText value="Senha: " />

<p:password value="#{usuarioMB.senha}"

Page 7: JSF Filter - Criando Um SistemaJSF Filter - Criando um sistema de login com criptografia MD5 de Login Com Criptografia MD5

7

size="35" required="true"

requiredMessage="A Senha é obrigatória" />

</p:panelGrid>

<p:panelGrid columns="2" styleClass="semBorda">

<p:commandButton icon="ui-icon-unlocked"

value="Entrar"

action="#{usuarioMB.doLogin}" />

<p:commandButton icon="ui-icon-mail-closed"

value="Recuperar Senha"

action="#{usuarioMB.doLogin}" />

</p:panelGrid>

</h:form>

</h:body>

</html>

Temos então quase todo mecanismo pronto:

1. A página de login 2. A comunicação do XHTML com o BO através do ManagedBean 3. As tabelas do banco e o mapeamento via JPA no Java da nossa classe Usuario 4. As validações e conversões no BO.

Falta agora o principal, que é criar o Filter para direcionar o usuário para o local certo, então começaremos

definindo o filter no arquivo web.xml, de acordo com a Listagem 7.

Listagem 7. Definindo filter no web.xml

<!-- login filter -->

<filter>

<filter-name>LoginFilter</filter-name>

<filter-class>br.com.meuprojeto.LoginFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>LoginFilter</filter-name>

<url-pattern>/restricted/*</url-pattern>

</filter-mapping>

Acima estamos definindo duas coisas:

1. Dizemos através da tag <filter> que a nossa classe responsável por realizar o controle do filtro fica em br.com.meuprojeto.LoginFilter e chama-se LoginFilter.

2. Através da tag <filter-mapping> dizemos que o LoginFilter (definido através do <filter-name>) deve interceptar todas as requisições que passam por “/restricted/*”, ou seja, tudo que estiver dentro do diretório restricted será redirecionado para o LoginFilter que tomará alguma decisão ou simplesmente mandará prosseguir com a requisição. Este é o conceito chave, então entenda que se você acessar “/restricted/paginabbbb.xhtml” automaticamente você será enviado para o LoginFilter, claro que de forma imperceptível.

Então finalmente nosso LoginFilter será como o da Listagem 8.

Listagem 8. LoginFilter

import java.io.IOException;

import javax.servlet.Filter;

Page 8: JSF Filter - Criando Um SistemaJSF Filter - Criando um sistema de login com criptografia MD5 de Login Com Criptografia MD5

8

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import br.com.meuprojeto.mb.UsuarioMBImpl;

public class LoginFilter implements Filter {

public void destroy() {

// TODO Auto-generated method stub

}

public void doFilter(ServletRequest request,

ServletResponse response,

FilterChain chain) throws IOException, ServletException {

//Captura o ManagedBean chamado “usuarioMB”

UsuarioMBImpl usuarioMB = (UsuarioMBImpl)

((HttpServletRequest) request)

.getSession().getAttribute("usuarioMB");

//Verifica se nosso ManagedBean ainda não

//foi instanciado ou caso a

//variável loggedIn seja false, assim saberemos que

// o usuário não está logado

if (usuarioMB == null || !usuarioMB.isLoggedIn()) {

String contextPath = ((HttpServletRequest) request)

.getContextPath();

//Redirecionamos o usuário imediatamente

//para a página de login.xhtml

((HttpServletResponse) response).sendRedirect

(contextPath + "/login/login.xhtml");

} else {

//Caso ele esteja logado, apenas deixamos

//que o fluxo continue

chain.doFilter(request, response);

}

}

public void init(FilterConfig arg0) throws ServletException {

// TODO Auto-generated method stub

}

}

Fizemos questão de mostrar toda a classe LoginFilter para que você possa perceber a sua totalidade. Veja

que a única função desta classe (neste exemplo simples) é mandar o usuário para a página de login.xhtml ou

mandar ele prosseguir com a requisição através do “chain.doFilter”.

Recuperação de senha

Como bônus a este artigo, decidimos acrescentar mais um método muito útil para que você possa

implementar a geração de novas senhas automaticamente. Como você está trabalhando com senhas

criptografadas em MD5, não há a possibilidade de recuperar uma senha perdida, ou seja, aquela senha que o

usuário por algum motivo esqueceu.

Page 9: JSF Filter - Criando Um SistemaJSF Filter - Criando um sistema de login com criptografia MD5 de Login Com Criptografia MD5

9

A única forma de acessar o sistema novamente é gerando uma nova senha para este usuário. Então

sugerimos o método da Listagem 9, mas fique a vontade para adicionar a complexidade que achar

necessária ao mesmo.

Listagem 9. Método gerador de senhas

public String gerarNovaSenha() {

String[] carct = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",

"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l",

"m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x",

"y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",

"K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",

"W", "X", "Y", "Z" };

String senha = "";

for (int x = 0; x < 10; x++) {

int j = (int) (Math.random() * carct.length);

senha += carct[j];

}

return senha;

}

Você pode utilizar o método acima gerando uma nova senha para o usuário e enviado ao seu e-mail ou

mesmo mostrando diretamente na tela, o que não é muito seguro.

Com essa aplicação é possível criar uma sistema de login poderoso e robusto, obviamente que realizando

algumas modificações como, por exemplo, a adição de “Perfis de Usuário”.

Veja como torna-se simples controlar o que o usuário está fazendo com nosso LoginFilter, pois temos a

URL para onde ele deseja ir, cabe a nós decidir se ele deve ou não continuar. Poderíamos até criar um log de

todos os acessos em cada URL, na hora e minuto exato que ele acessou e muitos outros recursos.

Para finalizar, é importante salientar que existem outros métodos para implementação de um sistema de

login, frameworks com o Spring Security ou o JAAS e etc. Mas um bom filter pode realizar tarefas tão

robustas quanto, só depende do nível de complexidade adotado.