quarta-feira, 24 de fevereiro de 2016

Autenticação e Autorização no JSF com Realm

    O "Realm" é um "banco de dados" de nomes de usuários e senhas que identificam usuários válidos de uma aplicação web (ou conjunto de aplicações web), além de uma enumeração da lista de funções associadas a cada usuário válido. 
    É necessário algumas configurações para fazer a autenticação e controlar o acesso dos usuários. A autenticação é definida por papeis, e não usuário por usuário, uma vez que as configurações são feitas em arquivos XML.
  O JBoss e o TomCat permitem essa configuração. Eles definem uma interface Java (org.apache.catalina.Realm) que podem ser implementadas por componentes "plug-in"  para estabelecer esta conexão. Cinco plug-ins padrão são fornecidos, suportando ligações a várias fontes de informação de autenticação:
  • JDBCRealm - Acessa informações de autenticação armazenadas em um banco de dados relacional, acessado através de um driver JDBC.
  • DataSourceRealm - Acessa informações de autenticação armazenadas em um banco de dados relacional, acessado através de uma chamada DataSource JNDI JDBC.
  • JNDIRealm - Acessa informações de autenticação armazenadas em um servidor de diretório LDAP com base, acessado através de um provedor de JNDI.
  • MemoryRealm - Acessa informações de autenticação armazenadas em um objeto de coleção in-memory, que é inicializada a partir de um documento XML (conf / tomcat-users.xml).
  • JAASRealm - Acessa informações de autenticação através do framework Java Autenticação e Autorização de Serviço (JAAS).
    Vou mostrar a implementação de um JDBCRealm.

    Primeiro deve-se ter duas tabelas, uma com login e senha, e outra com login e o papel do usuário (vendedor, gerente financeiro, secretario, diretor, etc). Essas tabelas podem ter mais campos caso seja uma necessidade do seu modelo de dados.

create table users (
  login         varchar(15) not null primary key,
  senha         varchar(15) not null
);

create table user_roles (
  login         varchar(15) not null,
  funcao        varchar(15) not null,
  primary key (login, funcao)
);

    Dentro do diretório META-INF que fica dentro de WebContent, cria-se um arquivo XML chamado context.xml.

    Dentro desse arquivo é preciso preencher dados referentes a classe que implementa o plugin realm que vamos usar, dados referentes a conexão com o banco de dados e também os dados referentes as tabelas e colunas que irão conter os dados de usuários e acessos. Veja o exemplo que segue:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
      <Realm className="org.apache.catalina.realm.JDBCRealm" driverName="org.postgresql.Driver" connectionURL="jdbc:postgresql://localhost:5433/seguranca" connectionName="usuario do banco" connectionPassword="senha do banco" userTable="nome da tabela de usuario" userNameCol="nome da coluna de usuario" userCredCol="nome da coluna de senha" userRoleTable="nome da tabela de permissões" roleNameCol="nome da coluna que contem a permissao"/>
<!-- é importante colocar o driver do banco de dados na biblioteca do servidor (mesmo quando já existe na aplicação) -->

</Context>

    Para usar senhas criptografadas com MD5, basta acrescentar um parâmetro chamado "digest" e passar o MD5. (digest="MD5") .

    O arquivo Web.xml deve ser configurado com as regras de acesso. Segue o exemplo abaixo:

  <!-- configurações de acesso e autenticação -->
  
  <!-- Essa parte é obrigatória, onde se informa o tipo de autenticação e as páginas de login e erro -->

  <login-config>
  <auth-method>FORM</auth-method>
  <form-login-config>
  <form-login-page>/Login.xhtml</form-login-page>
  <form-error-page>/Login.xhtml</form-error-page>
  </form-login-config>
  </login-config>
  
  <!-- Aqui eu informo cada papel que vai ter no meu sistema -->

  <security-role>
  <role-name>vendedor</role-name>
  </security-role>
  <security-role>
  <role-name>gerente</role-name>
  </security-role>
  <security-role>
  <role-name>caixa</role-name>
  </security-role>
  
  <!-- Nessa parte informamos as páginas que cada papel pode ter acesso. 
  A TAG <web-resource-name> serve apenas para dar um titulo ao grupo de acessos-->
  
<security-constraint>
  <web-resource-collection>
  <web-resource-name>Perfil Vendedor</web-resource-name>
 
  <url-pattern>/CadastrarCliente.xhtml</url-pattern>
  <url-pattern>/ConsultarCliente.xhtml</url-pattern>
  <url-pattern>/EditarCliente.xhtml</url-pattern>
  <url-pattern>/CadastrarVenda.xhtml</url-pattern>
  <url-pattern>/ConsultarVenda.xhtml</url-pattern>
  </web-resource-collection>
  <auth-constraint>
  <role-name>vendedor</role-name>
  </auth-constraint>
  </security-constraint>
  
  <security-constraint>
  <web-resource-collection>
  <web-resource-name>Perfil Gerente</web-resource-name>
 
  <url-pattern>/CadastrarCliente.xhtml</url-pattern>
  <url-pattern>/ConsultarCliente.xhtml</url-pattern>
  <url-pattern>/EditarCliente.xhtml</url-pattern>
  <url-pattern>/CadastrarFuncionario.xhtml</url-pattern>
  <url-pattern>/ConsultarFuncionario.xhtml</url-pattern>
  <url-pattern>/EditarFuncionario.xhtml</url-pattern>
  <url-pattern>/CadastrarVenda.xhtml</url-pattern>
  <url-pattern>/ConsultarVenda.xhtml</url-pattern>
  <url-pattern>/EditarVenda.xhtml</url-pattern>
  </web-resource-collection>
  <auth-constraint>
  <role-name>gerente</role-name>
  </auth-constraint>
  </security-constraint>
  
  <security-constraint>
  <web-resource-collection>
  <web-resource-name>Perfil Caixa</web-resource-name>
 
  <url-pattern>/CadastrarCliente.xhtml</url-pattern>
  <url-pattern>/ConsultarCliente.xhtml</url-pattern>
  <url-pattern>/EditarCliente.xhtml</url-pattern>
  <url-pattern>/CadastrarVenda.xhtml</url-pattern>
  <url-pattern>/ConsultarVenda.xhtml</url-pattern>
  <url-pattern>/EditarVenda.xhtml</url-pattern>
  </web-resource-collection>
  <auth-constraint>
  <role-name>caixa</role-name>
  </auth-constraint>
  </security-constraint>
  <!-- fim das configurações de acesso e autenticação -->    

    Pronto, as configurações estão feitas e prontas para uso. O passo final é criar a página de login (Login.xhtml) e o ManagedBean que vai receber o usuário e senha.
No managedBean, o método que vai ser executado na action do commandButton da página de login deve capturar a request e setar o usuário e senha como mostrado abaixo:

FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); request.login(login, senha);

E para fazer o logoff basta chamar:

FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); request.logoff();

E uma dica legal, é que como nos arquivos ".xhtml" podemos acessar a request, temos como renderizar botões e linkes apenas para usuários que terão acessos as páginas que eles redirecionam. Exemplo:

<h:button value="Editar Funcionario" outcome="EditarFuncionario" rendered="#{request.isUserInRole("gerente")}" />

O site do Jboss tem outras informações sobre como implementar um Realm: https://docs.jboss.org/jbossweb/3.0.x/realm-howto.html


terça-feira, 9 de fevereiro de 2016

Trabalhando com a codificação "UTF-8" em aplicações JavaWeb

    As vezes mesmo configurando o eclipse para criar os arquivos na codificação "UTF-8" ainda sofremos com caracteres estranhos entre as requisições web, fazendo com o que as informações sejam exibidas ou armazenadas com erros de codificação.
    Uma forma de corrigir esse problema e garantir que as requisições irão chegar na aplicação e também a resposta retornará com a codificação "UTF-8", é criando um filtro que fique responsável por setar este padrão na "request" e na "response".
    É importante que o primeiro filtro faça essa padronização, antes de qualquer outra interação com a "request" e a "response". Não é necessário um filtro exclusivo para isso, porém, é necessário garantir que seja a primeira interação, para que não corra o risco de divergência nas informações trabalhadas.
    Caso a aplicação trabalhe com mais de um idioma, é necessário a análise de uma maneira de checar quando esse encodding deve ser setado.
    O código é simples:


request.setCharacterEncoding("UTF-8");

response.setCharacterEncoding("UTF-8");






Trabalhando com datas no Java

É comum precisarmos converter diferentes tipos de datas no Java, segue uma função que já trabalha alguns tipos diferentes...

import java.text.ParseException;
import java.text.SimpleDateFormat;

public class FuncoesUteis {

//Função que converte de java.util.Date para java.sql.Date
public static java.sql.Date DataUtilDateParaSQLDate(java.util.Date data) throws NullPointerException{
if(data != null){
return new java.sql.Date(data.getTime());
}
return null;
}


//Função que converte de java.sql.Date para java.util.Date
public static java.util.Date DataSQLDateParaUtilDate(java.sql.Date data) throws NullPointerException{
if(data != null){
return new java.util.Date(data.getTime());
}
return  null;
}

//função que pega uma data no formato texto e retorna java.util.Date, usando a classe java.text.SimpleDateFormat. OBS: pode dar erro se a string passada não for uma data.
public static java.util.Date DataTextoParaUtilDate(String data) throws ParseException, NullPointerException{
if(data != "" && data != null){
return new SimpleDateFormat("dd/MM/yyyy").parse(data);
}
return null;
}

//Recebe a data e retorna a data formatada.
public static String DataUtilFormatada(java.util.Date data) throws NullPointerException, ParseException{
if(data != null){
return new SimpleDateFormat("dd/MM/yyyy").format(data);
}
return "00/00/0000";
}

}