Sistema de Autenticação Web utilizando JPA

Veja nesse artigo como criar um sistema de autenticação web com login e senha utilizando a tecnologia JPA e conectividade com banco de dados PostgreSQL.

Fique por dentro
O objetivo desse artigo é criar um sistema web via JPA (Persistência) que tenha autenticação básica, onde será implementada uma página padrão de login e senha, sendo como inicial, e a outra página denominada principal com a opção de menu para sair do sistema.

JPA (Java Persistence API) é um framework para persistência de dados que auxilia desenvolvedores de sistemas Java a implementarem páginas para internet, ou em outros tipos, geradas de forma dinâmica e automaticamente. Nesse artigo, o ambiente de desenvolvimento a ser utilizado é o Netbeans, que além de nele existir o servidor para a execução da aplicação conhecido como Apache Tomcat, existem drivers específicos para a conectividade de dados como, por exemplo, a utilização do PostgreSQL que agiliza muito o envio e a troca de informações entre o banco de dados criado e a aplicação web.

Primeiros Passos

Figura 1. Configuração do Nome do Projeto Java Web AutenticaJPA

Depois de configurado, clique em Próximo e selecione o servidor Apache Tomcat, e para concluir clique em Finalizar. O projeto será criado e estará pronto para ser implementado.

Criando a Persistência

Em cima do nome do projeto AutenticaJPA, clique com o botão direito e vá em Novo. Ao clicar na opção Outros abrirá uma janela: selecione a Categoria Persistência e o tipo de arquivo Classe de Entidade. Ao clicar em Próximo, o nome da classe será definido como Funcionário e o pacote (que obrigatoriamente deve ser definido) será como autentica.entidades. Clique em Próximo novamente e aparecerá uma janela para selecionar a Biblioteca de persistência Eclipse (JPA 2.1) e selecionar a Conexão de Banco de dados jdbc:postgresql://127.0.0.1:5732/postgres [postgres em public] (mas pode mudar dependendo da configuração atual do Netbeans). Selecione a Estratégia de criação Criar e complete clicando em Finalizar.

Implementando o Pacote Entidades

Primeiramente será programada uma classe Java que controlará uma nova entidade com base na criação da tabela Funcionarios, seus campos, propriedades, tamanhos e consultas. Será criado um novo pacote (clicando com o botão direito em cima de Pacotes de Códigos-Fonte, vá em Novo → Pacote Java) com o nome autentica.entidades; clique em Finalizar.

Será criada a classe Funcionario.java a ser programada com código referente a Listagem 1. Essa classe será responsável por criar a tabela Funcionario com todos os campos, propriedades, chave primária e tamanhos.

package autentica.entidades; import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; import javax.persistence.UniqueConstraint; @Entity @Table(catalog = "postgres", schema = "public", uniqueConstraints = { @UniqueConstraint(columnNames = {"fun_login"})}) @NamedQueries({ @NamedQuery(name = "Funcionario.findAll", query = "SELECT f FROM Funcionario f"), @NamedQuery(name = "Funcionario.findByCodigo", query = "SELECT f FROM Funcionario f WHERE f.codigo = :codigo"), @NamedQuery(name = "Funcionario.findByLogin", query = "SELECT f FROM Funcionario f WHERE f.login = :login")}) public class Funcionario implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Basic(optional = false) @Column(name = "fun_codigo", nullable = false) private Integer codigo; @Basic(optional = false) @Column(name = "fun_nome", nullable = false, length = 100) private String nome; @Basic(optional = false) @Column(name = "fun_login", nullable = false, length = 15) private String login; @Basic(optional = false) @Column(name = "fun_senha", nullable = false, length = 32) private String senha; @Basic(optional = false) @Column(name = "fun_endereco", nullable = false, length = 100) private String endereco; @Column(name = "fun_endereco_numero") private Integer enderecoNumero; @Column(name = "fun_endereco_complemento", length = 15) private String enderecoComplemento; @Column(name = "fun_bairro", length = 70) private String bairro; @Basic(optional = false) @Column(name = "fun_ativo", nullable = false) private boolean ativo; public Funcionario() { } public Funcionario(Integer funCodigo) { this.codigo = funCodigo; } public Funcionario(Integer funCodigo, String funNome, String funLogin, String funSenha, String funEndereco, boolean funAtivo) { this.codigo = funCodigo; this.nome = funNome; this.login = funLogin; this.senha = funSenha; this.endereco = funEndereco; this.ativo = funAtivo; } public Integer getCodigo() { return codigo; } public void setCodigo(Integer codigo) { this.codigo = codigo; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public String getSenha() { return senha; } public void setSenha(String senha) { this.senha = senha; } public String getEndereco() { return endereco; } public void setEndereco(String endereco) { this.endereco = endereco; } public Integer getEnderecoNumero() { return enderecoNumero; } public void setEnderecoNumero(Integer enderecoNumero) { this.enderecoNumero = enderecoNumero; } public String getEnderecoComplemento() { return enderecoComplemento; } public void setEnderecoComplemento(String enderecoComplemento) { this.enderecoComplemento = enderecoComplemento; } public String getBairro() { return bairro; } public void setBairro(String bairro) { this.bairro = bairro; } public boolean isAtivo() { return ativo; } public void setAtivo(boolean ativo) { this.ativo = ativo; } @Override public int hashCode() { int hash = 0; hash += (codigo != null ? codigo.hashCode() : 0); return hash; } @Override public boolean equals(Object object) { if (!(object instanceof Funcionario)) { return false; } Funcionario other = (Funcionario) object; return !((this.codigo == null && other.codigo != null) || (this.codigo != null && !this.codigo.equals(other.codigo))); } @Override public String toString() { return "autentica.entidades.Funcionario[ fun_codigo=" + codigo + " ]"; } }
Listagem 1. Classe Funcionario.java

Logo em cima da declaração da chave primária são criadas as NamedQuery(s), onde cada uma é responsável por executar um comando de consulta pré-definida em cada linha. Em seguida, é criado um construtor que recebe todos os campos da tabela criada de acordo com cada propriedade respectiva definida por cada get e set imposto para que cada campo receba e envie sua informação.

Depois é criado o método hashCode, que é responsável por criar uma chave própria para a senha cujo seu objetivo é manter sua integridade.

O método do tipo boolean (equals) é responsável por comparar se o que o usuário digitou é compatível com o que está armazenado no banco de dados.

E finalmente o método toString é utilizado para que a entidade busque o código do funcionário e compare com a atual posição do funcionário pesquisado, caso houve êxito na autenticação.

Implementando o Pacote JSF

Agora será implementado o pacote JSF para manter um controle entre a persistência dos dados e a aplicação. Será criado um novo pacote chamado autentica.jsf.

Será criada também a classe abstrata AbstractForm.java a ser programada com código referente a Listagem 2. Ela será responsável por efetuar uma busca no contexto JSF, gerar uma mensagem de erro com nível de severidade Warn e gerar uma mensagem de erro vinculada a um dado componente.

package autentica.jsf; import autentica.persistence.JPAManager; import java.io.Serializable; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.persistence.EntityManagerFactory; public abstract class AbstractForm implements Serializable { private final JPAManager controlador; public AbstractForm() { super(); controlador = new JPAManager(); } protected final EntityManagerFactory getEntityManagerFactory() { return controlador.getEntityManagerFactory(); } protected final void closeEntityManagerFactory() { controlador.closeEntityManagerFactory(); } protected static FacesContext getFacesContext() { return FacesContext.getCurrentInstance(); } protected final void setMessage(String summary) { setMessage(null, summary); } protected final void setMessage(UIComponent component, String summary) { FacesContext ctx = getFacesContext(); ctx.addMessage((component != null ? component.getClientId(ctx) : null), new FacesMessage(FacesMessage.SEVERITY_WARN, summary, null)); } }
Listagem 2. Classe AbstractForm.java

Agora será necessário criar a interface Bean Gerenciado com o nome de FormTransiente.java, a ser programada com código referente a Listagem 3. Essa interface será responsável por marcar um Bean Gerenciado como transiente, devendo ser removido da sessão quando outro form for colocado na sessão, ou seja, será responsável por manter a sessão durante a navegação das páginas.

package autentica.jsf; public interface FormTransiente { }
Listagem 3. FormTransiente.java

E finalizando essa parte, será criada a classe Bean Gerenciado com o nome de LoginForm.java a ser programada com código referente a Listagem 4, e será responsável por controlar as ações do Login e Logoff da autenticação durante a aplicação.

package autentica.jsf; import autentica.entidades.Funcionario; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.Query; @ManagedBean(name = "loginForm") @SessionScoped public class LoginForm extends AbstractForm { private Funcionario funcionarioLogado; private String login; private String senha; public LoginForm() { super(); limpaAtributosForm(); } private void limpaAtributosForm() { funcionarioLogado = null; login = null; senha = null; } public Funcionario getFuncionarioLogado() { return funcionarioLogado; } public void setSenha(String senha) { this.senha = senha; } public String getSenha() { return senha; } public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public boolean isUsuarioLogado() { return funcionarioLogado != null; } public String limpaTelaCadastro_action() { limpaAtributosForm(); return "LIMPA"; } public String logout_action() { limpaAtributosForm(); return "LOGOUT"; } public String efetuaLogin_action() { EntityManager em = null; try { em = getEntityManagerFactory().createEntityManager(); Query q = em.createNamedQuery("Funcionario.findByLogin"); q.setParameter("login", login); this.funcionarioLogado = (Funcionario) q.getSingleResult(); if (funcionarioLogado.getSenha().equalsIgnoreCase(senha)) { return "PRINCIPAL"; } else { setMessage("Senha inválida."); } } catch (NoResultException ex) { setMessage("Usuário não encontrado."); } catch (Exception ex) { } finally { if (em != null) { em.close(); } closeEntityManagerFactory(); } funcionarioLogado = null; return null; } }
Listagem 4. Classe LoginForm.java

Os campos login e senha tem uma propriedade denominada funcionarioLogado que retorna se o login e a senha estão corretas ou erradas.

O método limpaAtributosForm() é responsável por limpar os campos e em seguida são criadas as propriedades get(s) e set(s)dos campos login e senha.

O método isUsuarioLogado() é responsável por retornar se o usuário está logado.

Já o método logout_action() é responsável por executar comandos para logout do sistema, e o método efetuaLogin_action() é responsável por executar toda a verificação dos campos digitados pelo usuário, verificando os valores com os contidos no banco de dados. Se houve êxito, é encaminhado para a página principal, se não houver êxito ou se o usuário não existir são informadas mensagens de erros na tela.

Implementando o Pacote Persistence

Agora será implementado o pacote Persistence cujo objetivo é criar um controle consistente para a persistência dos dados. Serão criados um novo pacote autentica.persistence e uma classe (customizadora de sessão) com o nome de JPAEclipseLinkSessionCustomizer.java. Essa será programada com código referente a Listagem 5 e será responsável por obter uma conexão ao banco de dados e enviar para a sessão a string de conexão. Se a string já existe é alterada para a próxima subsequente da lista, tornando a persistência íntegra, e se tudo ocorrer sem erros, em seguida essa string é gravada automaticamente para a execução do projeto com segurança e autenticidade.

package autentica.persistence; import javax.naming.Context; import javax.naming.InitialContext; import org.eclipse.persistence.config.SessionCustomizer; import org.eclipse.persistence.sessions.DatabaseLogin; import org.eclipse.persistence.sessions.JNDIConnector; import org.eclipse.persistence.sessions.Session; import org.eclipse.persistence.sessions.server.ServerSession; public class JPAEclipseLinkSessionCustomizer implements SessionCustomizer { @Override public void customize(Session session) throws Exception { JNDIConnector connector = null; Context context = null; try { context = new InitialContext(); if (null != context) { connector = (JNDIConnector) session.getLogin() .getConnector(); connector.setLookupType(JNDIConnector.STRING_LOOKUP); JNDIConnector writeConnector = (JNDIConnector) session.getLogin().getConnector(); writeConnector.setLookupType(JNDIConnector .STRING_LOOKUP); JNDIConnector readConnector = (JNDIConnector) ((DatabaseLogin) ((ServerSession) session) .getReadConnectionPool() .getLogin()).getConnector(); readConnector.setLookupType(JNDIConnector .STRING_LOOKUP); } else { throw new Exception("_JPAEclipseLinkSessionCustomizer: Context is null"); } } catch (Exception e) { } } }
Listagem 5. Classe JPAEclipseLinkSessionCustomizer.java

Para finalizar o conceito de persistência que está sendo aplicado ao projeto, será necessário criar uma nova classe com o nome de JPAManager.java e o código será referente a Listagem 6. A classe será responsável por gerenciar a persistência, nomear a unidade de persistência da aplicação usada para criação do gerenciador da entidade e fechar o seu gerenciamento liberando as propriedades da memória.

package autentica.persistence; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import org.eclipse.persistence.config.PersistenceUnitProperties; public final class JPAManager extends Object implements Serializable { private static final String NOME_UNIDADE_PERSISTENCIA = "ExemploJPA"; private transient EntityManagerFactory entityManagerFactory = null; public final EntityManagerFactory getEntityManagerFactory() { if (entityManagerFactory == null) { Map propertiesMap = new HashMap(); propertiesMap.put(PersistenceUnitProperties .CACHE_SHARED_DEFAULT, "false"); entityManagerFactory = Persistence.createEntityManagerFactory (NOME_UNIDADE_PERSISTENCIA, propertiesMap); } return entityManagerFactory; } public final void closeEntityManagerFactory() { if (entityManagerFactory != null) { entityManagerFactory.close(); entityManagerFactory = null; } } }
Listagem 6. JPAManager.java

Definindo as Páginas para o Usuário

Para finalizar o projeto, daremos início ao layout das páginas (camada de apresentação), que consistirá em uma página inicial para a autenticação do usuário e a outra página para o logout.

Dentro da página index.xhtml, será implementada o código referente a Listagem 7.

<?xml version='1.0' encoding='UTF-8' ?> <!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"> <h:head> <title>Autenticação de Usuário</title> </h:head> <h:body> <h1>Autenticação de Usuário</h1> <h:form> <h:messages /> <table> <tr> <th>Login:</th> <td> <h:inputText id="txtLogin" size="10" maxlength="6" value="#{loginForm.login}" required="true" requiredMessage="O Login não foi informado!" validatorMessage="Tamanho máximo do campo Login excedido!" title="Login"> <f:validateLength maximum="6"/> </h:inputText> </td> </tr> <tr> <th>Senha:</th> <td> <h:inputSecret id="txtSenha" size="10" maxlength="6" value="#{loginForm.senha}" required="true" requiredMessage="A senha não foi informada!" validatorMessage="Tamanho máximo do campo Senha excedido!" title="Senha"> <f:validateLength maximum="6"/> </h:inputSecret> </td> </tr> <tr> <td colspan="2"> <h:commandButton id="btnEnviar" value="Entrar" action="#{loginForm.efetuaLogin_action}" /> <h:commandButton id="btnLimpar" value="Limpar" immediate="true" action="#{loginForm.limpaTelaCadastro_action}" /> </td> </tr> </table> </h:form> </h:body> </html>
Listagem 7. Página index.xhtml

Esse código é responsável por definir o layout da página inicial que contém atributos text(s) para a inserção do login e senha do usuário contendo valores, campos requeridos, validadores de mensagens e há o botão contendo o direcionamento (ação) para o método efetuaLogin_action contido na classe loginForm.

Também existe o botão para o método limpaTelaCadastro_action, que é responsável por limpar os dados do formulário.

Além disso, será criada uma página denominada principal.xhtml que só será acessada caso o usuário tenha feito login com sucesso, descrita na Listagem 8.

<?xml version='1.0' encoding='UTF-8' ?> <!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"> <h:head> <title>Menu</title> </h:head> <h:body> Menu do Sistema <ul> <li> <h:form> <h:commandLink action="#{loginForm.logout_action()}" value="Sair"/> </h:form> </li> </ul> </h:body> </html>
Listagem 8. Principal.xhtml

O código é responsável por exibir ao usuário a página principal contendo um link para que possa ser feito o logout de acordo com o método logout_action() localizado na classe loginForm.

Após ter finalizado, basta salvar o projeto e executar o mesmo apertando F6.

O resultado será um formulário simples de autenticação mostrado em algum navegador, como mostra a Figura 2. No nosso caso abrimos usando o Mozilla Firefox.

Figura 2. Tela inicial do sistema sendo executado.

Inserindo Dados para o Banco de Dados

Para que tenham dados armazenados no banco de dados, é preciso cadastrar os mesmos. Assim, basta clicar na aba Serviços no Grupo de Menus (na maioria das vezes localizada na esquerda), de acordo com a Figura 3.

Figura 3. Banco de dados conectado em public, demonstrando que a tabela funcionário foi criada após a execução do projeto.

Agora, é só clicar com o botão direito em cima da tabela funcionário, clicar em Exibir Dados, e pode colocar comandos de SQL para insert. Feito isso, é só apertar Ctrl+Shift+E que executará o comando, gravando os dados na tabela. Um exemplo de insert para a tabela funcionario se encontra no código da Listagem 9.

insert into funcionario (fun_ativo,fun_bairro,fun_endereco,fun_endereco_complemento, fun_endereco_numero,fun_login,fun_nome,fun_senha) values ('1','Teste','Teste','Teste','123','admin','Admin','123456')
Listagem 9. Exemplo de instrução SQL para inserir dados para a tabela funcionario

Finalmente, quando executar o projeto web novamente, o parâmetro para o login será admin e para a senha 123456.

Para autenticar, basta colocar os dados que foram cadastrados na tabela anteriormente e depois clicar em Entrar.

Se a autenticação não for correta, então em seguida será apresentada uma mensagem de acordo com o sistema, por exemplo, se o login ou a senha não forem informados, como mostra a Figura 4.

Figura 4. Página com mensagem de erro que houve falha na autenticação do sistema.

Com isso encerramos o nosso artigo. Espero que tenham gostado do assunto.


Saiu na DevMedia!


Saiba mais sobre Java ;)

  • Guias de Estudo sobre Java: Encontre aqui os Guias de estudo que vão ajudar você a aprofundar seu conhecimento na linguagem Java. Desde o básico ao mais avançado. Escolha o seu!
  • Programador Java: Aprender Java não é uma tarefa simples, mas seguindo a ordem proposta nesse Guia, você evitará muitas confusões e perdas de tempo no seu aprendizado. Vem aprender java de verdade, vem!

Artigos relacionados