Monday, April 8, 2019

Keycloak Server: delegando a segurança de sua aplicação (parte 2)

Na primeira parte deste post, no console de administração, configuramos o Keycloak Server segurar uma aplicação web que vamos criar. A aplicação não vai conter nenhuma linha de código relacionada à login/logout, ela simplesmente vai terceirizar para o Keycloak toda essa tarefa.

Nesta parte 2, criamos a aplicação e testamos seu login e o logout via Keycloak. Nosso container de aplicação será o Wildfly 15.

Criando e publicando MyWebApp

MyWebApp é uma aplicação maven com JSF e uma página inicial index.xhtml:
<?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:f="http://java.sun.com/jsf/core"
 xmlns:h="http://java.sun.com/jsf/html">

<h:head>
 <title>My App</title>
</h:head>
<h:body>
 <h:form>
  <h:panelGrid>
  <h1>Welcome to My APP!</h1>
  <h:commandButton value="LOGOUT" action="#{indexMB.logout()}" />
  </h:panelGrid>
 </h:form>
</h:body>
</html>

E o managed bean IndexMB:
package acme.view;

import javax.enterprise.inject.Model;
import javax.faces.context.FacesContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.keycloak.KeycloakSecurityContext;

@Model
public class IndexMB {

 public String logout() throws ServletException {
  
  System.out.println("Logout!.....");
  HttpServletRequest req = (HttpServletRequest)
      FacesContext.getCurrentInstance().getExternalContext().getRequest();
  KeycloakSecurityContext context = (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
  System.out.println("-------- realm name: "+context.getRealm());
  req.logout();
  return null;
 }
}

Abaixo o arquivo de configuração pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
 <groupId>com.acme</groupId>
 <artifactId>MyWebApp</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>war</packaging>
 <properties>
  <java.version>1.8</java.version>
 </properties>
 <dependencies>
  <!-- https://mvnrepository.com/artifact/org.keycloak/keycloak-core -->
  <dependency>
 <groupId>org.keycloak</groupId>
 <artifactId>keycloak-core</artifactId>
 <version>5.0.0</version>
  </dependency>
  <!-- https://mvnrepository.com/artifact/javax/javaee-api -->
  <dependency>
   <groupId>javax</groupId>
   <artifactId>javaee-api</artifactId>
   <version>7.0</version>
   <scope>compile</scope>
  </dependency>  
 </dependencies>
 <build>
 <finalName>myapp</finalName>
 </build>
</project>

E o arquivo web.xml em WEB-INF. Nele é necessário especificar o método de autenticação como KEYCLOAK:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
 <display-name>myapp</display-name>
 <welcome-file-list>
  <welcome-file>index.xhtml</welcome-file>
 </welcome-file-list>
 <servlet>
  <servlet-name>Faces Servlet</servlet-name>
  <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>Faces Servlet</servlet-name>
  <url-pattern>*.xhtml</url-pattern>
 </servlet-mapping>
  <!-- Keycloak confs -->
 <security-constraint>
  <web-resource-collection>
   <url-pattern>/*</url-pattern>
  </web-resource-collection>
  <auth-constraint>
   <role-name>Developer</role-name>
  </auth-constraint>
 </security-constraint>

 <login-config>
  <auth-method>KEYCLOAK</auth-method>
  <realm-name>TeamSecurity</realm-name>
 </login-config>

 <security-role>
  <role-name>Developer</role-name>
 </security-role>
</web-app>
Na pasta WEB-INF colocamos o arquivo keycloak.json que foi gerado pelo console admin do Keycloak na parte 1 deste post. O código completo do projeto se encontra no github.

Frameworks de Autorização e Autenticação

O Keycloak trabalha com 2 formas de autenticação: OpenID Connect e o SAML 2.0.

OpenID Connect (OIDC) é uma camada de autenticação e identificação de usuário no topo do framework OAuth 2.0. O OAuth 2.0 trabalha com autorizações (o quê pode ser acessado), enquanto o OIDC define um fluxo de autenticação de usuários (quem está acessando).

SAML 2.0 é um padrão de troca de informações entre partes sobre autorização e autenticação por meio de um arquivo XML digitalmente assinado.

Quando for segurar uma aplicação pelo Keycloak a primeira coisa a decidir é qual mecanismo de autenticação e autorização você ira usar. Na maioria dos casos recomenda-se o OIDC, o qual será usado neste post.

Client Adapters

São bibliotecas que tornam mais fácil segurar aplicações e serviços com o Keycloak. Há vários adpaters diferentes dependendo de qual plataforma sua aplicação esteja rodando. Se for uma aplicação Java, existem adpaters para o Wildfly, Tomcat, Spring Boot, Jetty, etc. Há ainda outros adapaters para Java Script, Node.js, Python, iOS, entre outras. Veja a relação completa aqui.

Como nosso container é o WildFly, vamos utilizar seu respectivo adapter para o OIDC. Baixe o arquivo keycloak-wildfly-adapter-dist-5.0.0.zip. Descompacte seu conteúdo (pastas bin, docs e modules) na pasta raiz do Wildfly.

Após descompactar o arquivo, execute o seguinte comando dentro do diretório raiz do Wildfly:
wildfly-15.0.1.Final$ ./bin/jboss-cli.sh --file=bin/adapter-elytron-install-offline.cli
Esse script edita os arquivos de configuração do Wildfly para que ele fique ciente do método de autenticação do Keycloak referenciados no web.xml.

Os próximos passos são:

1 Subir o Keycloak
keycloak-5.0.0$ ./bin/standalone.sh -Djboss.socket.binding.port-offset=100
Após executar o comando, acesse o Keycloak em http://localhost:8180/auth/.

2 Subir o WildFly
wildfly-15.0.1.Final$ ./bin/standalone.sh
Após executar o comando, acesse o Wildfly em http://localhost:9990.

3 Empacotar a aplicação
~/workspace/MyWebApp$ mvn install
Nesse caso utilizamos o Maven. Como alternativa, você pode construir a aplicação utilizando as ferramentas de alguma IDE. O comando acima gera o arquivo myapp.war dentro da pasta target. Copie a URL do arquivo.

4 Publicar myapp.war no Wildfly

Para fazer o deploy da aplicação, vamos utilizar o cliente do Wildfly jboss-cli.sh, que se encontra dentro da pasta bin do Wildfly (ou jboss-cli.bat se for Windows). Execute o seguintes comandos, substituindo o caminho de myapp.war de acordo com o seu ambiente:
~/Servers/wildfly-15.0.1.Final$ ./bin/jboss-cli.sh
You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.
[disconnected /] connect
[standalone@localhost:9990 /] deploy /home/rafael/workspace/MyWebApp/target/myapp.war
Não esqueça de substituir o caminho até myapp.war de acordo com o seu ambiente.

Pronto! Aplicação foi publicada e possui um mecanismo de autenticação totalmente desacoplado. Para testar acesse a aplicação em http://localhost:8080/myapp. Você vai bater de frente com a seguinte página:


Logue com o usuário que você criou na parte 1 deste post, para o qual foi atribuida a role Developer.

Após logar, você será direcionado para a página da aplicação:


Clique em logout. E o Keycloak vai encerrar sua sessão e você é novamente redirecionado para a página de login.

Servidor de Sessão

O Keycloak também funciona como um servidor de sessão. Acesse myapp de um browser diferente e em seguida logue com o admin na console de administração do Keycloak.

No realm  TemSecurity, no menu client, escolha o client MyWebApp, e depois clique na aba Sessions:



Você verá que o keycloak lista o usuário que você criou com 2 sessões ativas. Clique no usuário, e você poderá derrubá-lo a partir do console do Keycloak.

O Keycloak é uma solução muito interessante que fornece autenticação como microserviço, ou seja, não há dependência entre os componentes internos da aplicação e os processos de autenticação, autorização e manipulação dos dados do usuário. Nenhuma linha de código para login/logout foi escrita e a aplicação já está totalmente segurada.

referências:
https://www.keycloak.org/documentation.html  


"Determinarás tu algum negócio, e ser-te-á firme, e a luz brilhará em teus caminhos."

Jó 22:28

       

Thursday, April 4, 2019

Keycloak Server: delegando a segurança de sua aplicação (parte 1)

Adicionar autenticação para qualquer aplicação é uma tarefa trabalhosa. Se sua aplicação não usa alguma forma de autenticação e controle de autorização e acessos de usuário, esteja certo de que uma hora isso será necessário, você terá que implementar autentiação, autorização e todo um esquema de gestão de usuários. E você fatalmente acabará fazendo isso do jeito errado.
Já que autenticação e diversas questões relacionadas à segurança de sua aplicação demandam tempo considerável de análise e implementação, e mesmo depois de prontas, após um longo trabalho, sua implementação apresentará alguma falha, por que não delegar tudo isso para quem sabe fazer? 

Na parte 2 deste post criamos uma aplicação web simples sem escrever uma única linha relacionada a login/logout de usuário, mas que vai rodar tendo por trás um sistema robusto e confiável de autenticação. Nesta parte 1 apresentamos e configuramos o Keycloak Server.

Keycloak Server

Keycloak Server é uma solução open source, baseado no Wildfly server, para identificação e autenticação de usuários para as aplicações e serviços modernos, ou, em outras palavras, é uma aplicação JAX-RS que expõe diversos serviços customizáveis relacionados à autenticação e autorização de usuários que qualquer aplicação pode fazer uso. Embora seja uma distribuição do Wildfly, o Keycloak não pode ser usado como container de aplicações.

Conceitos

Antes de começar a testar o Keycloak, é importante entender alguns termos importantes presentes no seu contexto:

  • Realm: é um território que você cria. No Keycloak as aplicações são agrupadas por realms, os usuários também, entre outras entidades.
  • Client: são as aplicações e serviços segurados pelo Keycloak (não confundir com o usuário).
  • Roles: tipo ou categoria de usuário, geralmente vinculada a um tipo de acesso ou permissão.
  • Users: Entidades que podem logar no sistema.
  • User role mapping: a relação entre users e roles. Um user pode ter várias roles.
  • Groups: Conjunto de usuários. Groups também podem ter atributos e roles.
  • Acess token: Um token JSON que concede acesso a um serviço especificado

Configurando o Keycloak para o uso

Faça download do arquivo keycloak-5.0.0.[zip|tar.gz], que contem a distribuição do server. Descompacte o arquivo zipado. Se você já utilizou o Widlfly server, vai reparar que a estrutura de diretórios do Keycloak é praticamente idêntica a dele:



Para subir o Keycloak, vá para a pasta bin e execute o arquivo standalone.bat (se o seu ambiente for windows) ou standalone.sh (para Linux). O argumento -Djboss.socket.binding.port-offset=100 é opcional e foi passado para impedir que o Keycloak ocupe a porta 8080 e assim não cause conflito com o Wildfly server (que será executado logo mais).

$ ./standalone.sh -Djboss.socket.binding.port-offset=100

Após executar o comando acima, você pode acessar o console de administração do Keycloak no endereço http://localhost:8180/auth/:


Na tela acima, cadastre um novo usuário administrador (com login e senha). Esse processo também cria o realm master automaticamente. Após clicar em create,  clique no link Administration Console:

Logue com o usuário e senha de administrador que você acabou de criar. A tela inicial é a que segue:



No canto superior esquerdo, temos o realm default Master. Vamos criar um novo realm chamado TeamSecurity. Posicione o cursor em Master e clique no botão add realm:



Em name, coloque o nome do novo realm. Após, clique em create. Repare no canto superior esquerdo que o nome do realm mudou de Master para TeamSecurity (podemos alternar entre os diversos realms criados).

Agora vamos adicionar um cliente chamado MyWebApp dentro do realm TeamSecurity. No menu lateral esquerdo, clique em client e depois (à direita) create. Na combo-boox client protocol deixe a opção default openid-connect. Em Root URL coloque a URL da aplicação, que neste caso pode ser http://localhost:8080/myapp.


Após salvar, a seguinte tela aparece:



Preencha os campos Root URL, Valid Redirect URL e Web Origins conforme a imagem abaixo. myapp será aplicação que será protegida pelo Keycloak. Na combobox Access type, escolha confidential. Após selecionar o access type, as opções Service Accounts Enabled e Authorization Enabled vão aparecer, ative-as colocando em ON, conforme a imagem abaixo. E clique em save.



Keycloak Adapters

Keycloak suporta autenticação por meio de dois protocolos: OpenID Connect (OIDC) e SAML 2.0. Você deve escolher qual dos dois será usado na sua aplicação. Neste caso escolhemos o OIDC.

Dependendo de qual framework/container sua aplicação Java esteja utilizando (Wildfly, Tomcat, Spring Boot, Jetty, etc), o Keycloak provê alguns adaters para integrar de forma mais eficiente com a plataforma, há ainda adapters para outras linguagens.

No caso dos Java Adapters, todos eles compartilham uma série de configurações em comum, as quais podem ser definidas em um arquivo JSON colocado dentro da pasta WEB-INF ou, no caso do Wildfly, definí-las em um arquivo de configurações do servidor, como o standalone.xml por exemplo (dessa forma você não precisa alterar o arquivo .war).

Neste post, vamos gerar o arquivo de configuração JSON.

Ainda na área do client MyWebApp, na aba Instalation, na combobox format option, selecione Keycloak OIDC JSON. Esse procedimento dispões para download as configurações básicas no formato JSON, incluindo a secret-key do realm para a criptografia das informações trocadas entre o cliente e o servidor. A public-key para está acessível na url http://localhost:<keycloak-port>/auth/realms/{realm} (neste exemplo a url seria http://localhost:8180/auth/realms/TeamSecurity)

Salve esse arquivo como keycloak.json. Ele será usado na parte 2 deste post.



Agora vamos criar uma role de usuário. No menu lateral esquerdo clique em Roles. E depois em add role. Dê a esta role o nome de Developer, depois salve-a.

Agora vamos criar um usuário. No menu lateral, clique em users. E depois Add User. Dê um nome ao user. Você também pode preencher os campos First Name, Last Name e email. Marque a opção  Email Verified (ON), para habilitá-lo imediatamente. Salve.

Esse usuário que você criou poderá logar no console, mas não como admin, na url http://localhost:8180/auth/realms/{realmName}/account. Uma vez logado em sua própria conta, o usuário poderá atualizar seu perfil, entre outras atividades.



Depois de salvar esse user, na aba role mappings, adicione a role Developer para ele:


Na aba credentials, crie e guarde uma senha para esse usuário. Em seguida, clique em Reset Password. Teste se seu usuário foi criado com sucesso logando na área de usuários em http://localhost:8180/auth/realms/TeamSecurity/account/. Use o login e senha que você acabou de criar. No primeiro acesso, o Keycloak solicita ao usuário que mude sua senha.

Essa configurações representam o básico para proteger a aplicação MyWebApp no Keycloak. Na parte dois deste post criamos a aplicação e delegamos o seu processo de autenticação e autorização de usuários para o Keycloak.

"E não sede conformados com este mundo, mas sede transformados pela renovação do vosso entendimento, para que experimenteis qual seja a boa, agradável, e perfeita vontade de Deus"

Romanos 12:2


       

Tuesday, February 26, 2019

SpringBoot + MySQL + Docker-Compose (parte 2)

Na parte 1 deste artigo criamos o front-end. Nesta, criamos o back-end e o serviço de banco de dados.

Back-end com Spring Boot

Acesse a página start.spring.io e preencha os campos de dependências conforme a imagem abaixo e clique em Generate project. Unzipe o arquivo baixado e importe o projeto para o eclipse.



O projeto back-end tem como base o pacote com.app.backend, a partir do qual criamos o pacote entidade, que contém a entidade Pessoa.java idêntica ao do projeto front-end. Temos também o pacote repository, que declara uma interface para persistência de dados utilizando o Spring data. E o pacote controller, que expõe um web service REST que recebe as requisições do front-end. A estrutura do projeto back-end é como da imagem abaixo:


Interface PessoaRepository.java cuja implementação fica a cargo do container do Spring:

package com.app.backend.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.app.backend.entidade.Pessoa;

@Repository
public interface PessoaRepository extends JpaRepository{

}

Web Service REST PessoaController.java:
package com.app.backend.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.app.backend.entidade.Pessoa;
import com.app.backend.repository.PessoaRepository;

@RestController
@RequestMapping("/pessoa")
public class PessoaController {
 
 @Autowired
 PessoaRepository pessoaDao;
 
 @PostMapping(value = "/salvar")
 public ResponseEntity salvarPessoa(@RequestBody Pessoa pessoa) {
    
  try {
   pessoa = pessoaDao.save(pessoa);
   ResponseEntity response = new ResponseEntity<>(pessoa, HttpStatus.OK);   
   return response;
  }
  catch(Exception e) {
   e.printStackTrace();
   ResponseEntity response = new ResponseEntity<>(e.getMessage(), 
               HttpStatus.INTERNAL_SERVER_ERROR);   
   return response;
  }  
 }
 
 @GetMapping(value = "/todas")
 public ResponseEntity getTodas() {  
 
  List result = pessoaDao.findAll();  
  ResponseEntity> response = new ResponseEntity<>(result, HttpStatus.OK);
  return response;
 }   
}

A entidade Pessoa.java é idêntica àquela descrita na parte 1 deste artigo. O arquivo Dockerfile é auto explicativo e descreve passo a passo como construir a aplicação e executá-la dentro do Docker:
    FROM openjdk:8
    MAINTAINER "http://finalexception.blogspot.com"
    ADD target/back-end.jar .
    ENTRYPOINT ["java", "-jar", "back-end.jar"]


Na pasta main/resources temos o arquivo application.properties do Spring vazio. Deixe-o conforme abaixo:
# JPA PROPS
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy

# APPLICATION CONTEXT PATH
server.servlet.context-path=/app-backend

# DATABASE
spring.datasource.url=jdbc:mysql://mysql-standalone:3306/bd_pessoa?useSSL=true&serverTimezone=UTC
spring.datasource.username=rafael
spring.datasource.password=12345

spring.database.driverClassName =com.mysql.cj.jdbc.Driver

Isso é tudo! Com o botão direito do mouse no projeto, selecione Run As e Run as Maven Install. Esse comando gera o arquivo back-end.jar na pasta target, conforme descrito no Dockerfile.

Subindo a aplicação com Docker-compose

Nossa aplicação tem 3 serviços, poderia ter dezenas. Subir e gerenciar uma grande quantidade de serviços pode ser trabalhos, é aí que entra o docker-compose.

O Compose é um arquivo binário escrito em Python que você deve instalar no host que roda o docker.

A pasta dos projetos back-end e front-end devem estar dentro do mesmo diretório, que também devre conter o arquivo docker-compose.yml:



Com o Docker-compose, definimos os serviços da aplicação em arquivo YML e o Compose se encarrega de publicar a aplicação na engine do Docker.

docker-compose.yml
:
version: '3.5'
services:
  mysql-standalone:
    image: mysql:5.6
    environment:
      - MYSQL_ROOT_PASSWORD=rootpass
      - MYSQL_DATABASE=bd_pessoa
      - MYSQL_USER=rafael
      - MYSQL_PASSWORD=12345
    expose:
      - 3306
  backend:
    build: back-end
    depends_on:
      - mysql-standalone
    ports:
      - 8989:8080
    environment:
      - DATABASE_HOST=mysql-standalone
      - DATABASE_USER=rafael
      - DATABASE_NAME=bd_pessoa
      - DATABASE_PASSWORD=12345
      - DATABASE_PORT=3306
  frontend:
    build: front-end
    depends_on:
      - backend
    ports:
      - 8180:8080


O arquivo docker-compose.yml acima também serve como uma valiosa documentação do projeto. Nele vemos que nossa aplicação possui 3 serviços, cada qual em um host virtual separado: mysql-standalone, backend e frontend. Execute o comando $ docker-compose up para subir aplicação:



Agora você pode acessar o front-end na porta 8180 e testar a aplicação:


O código fonte do projeto completo está no git-hub.

"Fiquem unidos a mim, e eu ficarei unidos a vocês. O ramo que não fica unido à videira não pode dar frutos. Vocês também não poderão dar frutos, se não ficarem unidos a mim. Eu sou a videira, e vocês são os ramos. Quem fica unido a mim, e eu a ele, dará muito fruto, porque sem mim vocês não podem fazer nada"


João 15:4-5

       

Monday, February 4, 2019

SpringBoot + MySQL + Docker-Compose (parte 1)


A maioria das aplicações modernas são constituidas de múltiplos pequenos serviços independentes que interagem e assim dão forma à própria aplicação. Chamamos isso de microserviços. Um simples exemplo pode ser uma aplicação com 3 serviços:
  1. web front-end
  2. web back-end
  3. banco de dados
Coloque os três serviços juntos e temos uma aplicação com alguma utilidade.

Subir e gerenciar vários serviços pode se tornar uma tarefa difícil. É aí que entra o Docker-Compose.

Ao invés de juntar tudo com scripts e comandos extreamente longos, o Docker-Compose permite você descrever toda aplicação em único arquivo de configuração declarativo. Depois então você sobe todos os serviços com um único comando:

$ docker-compose up

Vamos ilustrar o uso do docker-compose criando uma aplicação formada por 3 serviços: uma interface REST no backend (com Spring Boot), um front-end HTML (Java Server Faces) e um banco de dados MySQL. Cada serviço totalmente separado um do outro

Front-End com Java Server Faces

Nosso front-end é um formulário HTML com JSF para cadastrar uma entidade Pessoa, a qual possui dois campos: nome e profissão. O front também lista todas as pessoas registradas na base de dados em um elemento table. O container é o WildFly. Há uma camada de serviço para fazer a requisições ao backend, representada pela classe Service.java e retorná-las ao managed bean. A classe que recebe os eventos da página HTML é ManagedBean.java:

classe ManagedBean.java

package com.app;

import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Model;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import com.app.entidade.Pessoa;

@Model
public class ManagedBean {

 @Inject
 private Service service;
 private Pessoa pessoa = new Pessoa();
 private List pessoas;
 
 
 public String salvar() {
  
    try {
  pessoa = service.salvarPessoa(pessoa);
  pessoa = new Pessoa();
  FacesContext.getCurrentInstance()
    .addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, 
           "salvo com sucesso", "salvo com sucesso"));
   }  
   catch(Exception e) {
   FacesContext.getCurrentInstance()
    .addMessage(null, new FacesMessage(FacesMessage.SEVERITY_FATAL, 
            e.getMessage(), e.getMessage()));
  }
  return null;
 }
 
 public Pessoa getPessoa() {
  return pessoa;
 }
 public void setPessoa(Pessoa pessoa) {
  this.pessoa = pessoa;
 }
 public List getPessoas() {
  try {   
   pessoas = service.getPessoas();  
   return pessoas;
  }
  catch(RuntimeException e) {
   FacesContext.getCurrentInstance()
   .addMessage(null, new FacesMessage(FacesMessage.SEVERITY_FATAL, 
           e.getMessage(), e.getMessage()));   
  }
  return null;
 }
 public void setPessoas(List pessoas) {  
  this.pessoas = pessoas;
 } 
}

classe Service.java

package com.app;

import java.net.URI;
import java.util.List;
import javax.enterprise.inject.Model;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;

import com.app.entidade.Pessoa;

@Model
public class Service {

 private final String URL_BACKEND = "http://backend:8080/app-backend/";
 
 public Pessoa salvarPessoa(Pessoa pessoa) {
  
  final String operacao = "salvar";  
  URI uri = UriBuilder.fromUri(URL_BACKEND).path(operacao).build();
  
  //usando JAX-RS Client API para enviar o objeto pessoa ao server back-end
  Entity data = Entity.entity(pessoa, MediaType.APPLICATION_XML_TYPE);
  Response response = ClientBuilder.newClient()
      .target(uri)
      .request(MediaType.APPLICATION_XML)
      .post(data, Response.class);
  
  if(response.getStatusInfo() == Response.Status.OK)
   return pessoa;
  else
   throw new RuntimeException("Ocorreu um erro ao salvar a Pessoa");    
 }
 
 public List getPessoas() {
  
  final String operacao = "all";
  URI uri = UriBuilder.fromUri(URL_BACKEND).path(operacao).build();
  
  Response response = ClientBuilder.newClient()
       .target(uri)
       .request(MediaType.APPLICATION_JSON_TYPE)
       .get(Response.class);
  
  if(response.getStatusInfo() == Response.Status.OK)
   return response.readEntity(List.class);
  else
   throw new RuntimeException("Não foi possível obter a Lista de Pessoas");  
 }
}

Entidade Pessoa.java
package com.app.entidade;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Pessoa implements Serializable{

 private Long id;
 private String nome;
 private String profissao;

    //getters & setters omitidos
}

Página index.xhtml

<?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:f="http://java.sun.com/jsf/core"
 xmlns:h="http://java.sun.com/jsf/html">

<h:head>
 <title>Cadastro Pessoa</title>
</h:head>
<h:body>
 <h:form>
  <h:messages />
  <h:panelGrid columns="2">
   <h:outputText value="Nome" />
   <h:inputText value="#{managedBean.pessoa.nome}" />
   <h:outputText value="Idade" />
   <h:inputText value="#{managedBean.pessoa.profissao}" />
  </h:panelGrid>
  <h:commandButton action="#{managedBean.salvar}" value="Salvar" />
  <p />
  Lista
  <h:dataTable value="#{managedBean.pessoas}" var="pessoa" border="1">
   <h:column>
    <h:outputText value="#{pessoa.nome}" />
   </h:column>
   <h:column>
    <h:outputText value="#{pessoa.profissao}" />
   </h:column>
  </h:dataTable>
 </h:form>
</h:body>
</html>

O arquivo Dockerfile é auto explicativo e descreve passo a passo como construir a aplicação e executá-la dentro do Docker: A aplicação roda dentro do Wildfly (FROM) e o arquivo front-end.war é adicionado na pasta /opt/jboss/wildfly/standalone/deployments/ do servidor:

   FROM jboss/wildfly
   MAINTAINER "http://finalexception.blogspot.com"
   ADD target/front-end.war /opt/jboss/wildfly/standalone/deployments/

Com o botão direito do mouse na pasta raiz do projeto, selecione Run As e Run as Maven Install. Esse comando gera o arquivo front-end.war na pasta target, conforme descrito no Dockerfile.

Neste ponto a aplicação pode rodar, porém ela ainda não estará funcional, já que depende de dois outros serviços: o backend propriamente dito, este por sua vez depende do banco de dados.

Na parte 2 deste artigo criamos e subimos esses dois serviços que faltam.



"Ouçam agora, vocês que dizem: 'Hoje ou amanhã iremos para esta ou aquela cidade, passaremos um ano ali, faremos negócios e ganharemos dinheiro'. Vocês nem sabem o que acontecerá amanhã! Que é a sua vida? Vocês são como a neblina que aparece por um pouco de tempo e depois se dissipa. Em vez disso, deveriam dizer: 'Se o Senhor quiser, viveremos e faremos isto ou aquilo'."

Tiago 4:13-15


       

Tuesday, December 18, 2018

Docker: Imagens (muito!) menores com multi-stage-build

Quando se fala em containers no Docker, menos é mais, ou grande significa lento e difícil. Por essa razão, imagens no Docker precisam ser pequenas. A melhor imagem é sempre a menor possível.

É importante perceber que, dependendo da forma como as instruções são colocadas no Dockerfile, a imagem final poderá ser - muito! - maior ou menor.


Multi-stage builds é um novo recurso do Docker disponível a partir da versão 17.05. É uma ferramenta extremamente útil para deixar a imagem que irá rodar aplicação a menor possível. Basicamente, com Multi-Stage Builds nós temos um único Dockerfile com mais de uma instrução FROM. Cada instrução FROM representa um estágio de construção da imagem e pode facilmente copiar artefatos dos estágios anteriores para o atual. Vamos a um exemplo aproveitando um post anterior deste blog.

Diminuindo o tamanho da imagem em 90%

Neste artigo intitulado Hello World Java-Docker, criei uma aplicação java simples e que rodava em container do Docker, sem explorar outros recursos, apenas como uma introdução sobre como rodar o Java no Docker. O arquivo Dockerfile.simple é o que segue:
# constrói uma imagem baseada no openjdk 8
FROM openjdk:8

ADD HelloJavaDocker.class .

ENTRYPOINT ["java", "HelloJavaDocker"]
    
Após gerar o .class pelo comando javac (fora do Docker), construímos a imagem chamada hello-java-docker pelo comando:
$ docker build -f Dockerfile.simple -t hello-java-docker .    
E em seguida criamos o container e rodamos a aplicação:
$ docker run -it hello-java-docker    
A aplicação executa com sucesso dentro da imagem hello-java-docker



Entretanto, ao executar o comando docker images, veremos que a imagem hello-java-docker é incrivelmente grande, possuindo o tamanho de quase 624MB:
rafael@rafael-desktop ~/generalProjects/HelloJavaDocker $ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
hello-java-docker   latest              83ce2e26c600        About a minute ago   624MB
openjdk             8                   c14ba9d23b3a        4 weeks ago          624MB
rafael@rafael-desktop ~/generalProjects/HelloJavaDocker $     
Podemos diminuir esse tamanho, alterando o Dockerfile, dividindo a construção da imagem em dois estágios. Afinal, não precisamos de todo ambiente JDK apenas para um Hello World. Precisamos apenas da versão básica da JRE. Então criamos um arquivo chamado Dockerfile.builders com as instruções que seguem:
FROM openjdk:8 AS Builder
ENV APP_HOME=/root/dev/apps/
WORKDIR $APP_HOME
COPY HelloJavaDocker.java $APP_HOME
RUN javac HelloJavaDocker.java


FROM openjdk:8-jre-alpine
WORKDIR root
COPY --from=Builder /root/dev/apps/HelloJavaDocker.class .
ENTRYPOINT ["java", "HelloJavaDocker"]    
  1. A primeira instrução FROM, para a qual demos o alías de Builder, utiliza a imagem openjdk:8.
  2. Criamos uma variável de ambiente com ENV dentro da imagem
  3. Copiamos o arquivo com o código java para APP_HOME
  4. Dessa vez compilamos a classe dentro do container
  5. O próximo FROM cria uma imagem baseada no JRE Linux Alpine
  6. Copiamos o .class gerado em Builder
  7. Executamos a aplicação
Agora, vamos construir uma imagem chamada chamada hello-java-docker-smaller. Em seguida criamos o container:
$ docker build -f Dockerfile.builders -t hello-java-docker-smaller .
$ docker run -it hello-java-docker-smaller    
O resultado da aplicação é o mesmo. Porém ao rodar docker images, veremos que o tamanho da nova imagem hello-java-docker-smaller é de apenas 83MB. Uma queda no tamanho de quase 90%:
rafael@rafael-desktop ~/generalProjects/HelloJavaDocker $ docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
hello-java-docker-smaller   latest              b5daa3a785c1        3 minutes ago       83MB
                                    c1625c3ad04b        3 minutes ago       624MB
hello-java-docker           latest              83ce2e26c600        6 minutes ago       624MB
openjdk                     8                   c14ba9d23b3a        4 weeks ago         624MB
openjdk                     8-jre-alpine        2e01f547f003        7 weeks ago         83MB
rafael@rafael-desktop ~/generalProjects/HelloJavaDocker $ 
    


"Falando novamente ao povo, disse Jesus: “Eu sou a luz do mundo; aquele que me segue, não andará em trevas, mas terá a luz da vida."
Jo 8:12


       

Sunday, November 25, 2018

Máscaras no JasperReport: o jeito certo

Durante o desenvolvimento de relatórios, um trabalho para o qual geralmente é relegado menor prioriade e que, portanto, ao final costumam ser feitos na base da gambiarra, são as máscaras dos campos.

É bastante comum soluções que colocam toda lógica de formatação do valor do campo de texto dentro do próprio campo de texto, como no screenshot abaixo tirado do editor de expressão do JasperStudio:


O código da expressão na imagem é o que segue:
 ($F{telefoneEmpresaAdm} != null && $F{telefoneEmpresaAdm}.length() == 13) ? 
 ("(" + ($F{telefoneEmpresaAdm}.substring(0,4).trim()) + ") " + $F{telefoneEmpresaAdm}.substring(4,9) + "-" + $F{telefoneEmpresaAdm}.substring(9,13)) : 
 ($F{telefoneEmpresaAdm} != null && $F{telefoneEmpresaAdm}.length() == 12) ? 
 "(" + (($F{telefoneEmpresaAdm}.substring(0,4).trim())+ ") " + $F{telefoneEmpresaAdm}.substring(4,8) + "-" + $F{telefoneEmpresaAdm}.substring(8,12)) : 
 ${telefoneEmpresaAdm}
    
Tudo isso dentro de um mísero campo de texto!

Esse código representa a lógica para criar a máscara para um campo de texto cujo conteúdo é um número de telefone. Esta lógica supostamente atende aos diversos cenários em que um número de telefone pode ocorrer: celular ou fixo? contém DDD? em algumas cidades do Brasil o número do  telefone fixo tem mais dígitos do que outras, etc.

Essa abordagem pode até resolver o problema no curto prazo, mas obviamente não é uma boa solução em termos de manutenção do projeto porque a lógica de formatação fica espalhada nos campos. Em projetos com dezenas ou até centenas de relatórios, essa estratégia se torna inviável.

A solução ideal é centralizar tais tarefas em um único local, de modo que todos os relatórios busquem alí a máscara para um respectivo campo. Esse local é uma classe com dezenas de métodos estáticos cada um contendo uma lógica de formatação para um determinado tipo de campo e cenário. Podemos inclusive invocar esses métodos a partir da template .jrxml, seja em produção ou em teste.

Projeto de Exemplo

Para exemplificar esta abordagem criamos um projeto que chama um relatório cuja fonte de dados é um banco de dados relacional. Para criar e popular o banco de dados, execute o seguinte script SQL (o banco de dados que utilizo neste exemplo é o MySQL):
create database if not exists bancoDeDados;
use bancoDeDados;
create table if not exists algumaEntidade(cep varchar(255), 
            telefone varchar(255), 
            data date, 
            valor decimal(5,2), 
            cpf varchar(255));
                                 
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');
insert into algumaEntidade values ('15015015','1234567890', '2000-10-25', 55.35, '89340813502');    
Nesse script criamos colunas com tipos de dados que aparecem com frequência em qualquer relatório e sempre requerem algum tipo de máscaras formatação.

A classe Mascaras assume a responsabilidade de controlar toda lógica de geração de máscaras para todos os relatórios do projeto:
package main;

import java.math.BigDecimal;
import java.sql.Date;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

import javax.swing.text.MaskFormatter;

public class Mascaras {

 public static String getDataAtualCompleta() {
  
  DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd 'de' MMMM 'de' yyyy", new Locale("pt", "BR"));
  
  LocalDate hoje = LocalDate.now();
  return dtf.format(hoje);
 }
 
 public static String getCEP(String cep) {
  
  try {
   MaskFormatter mask = new MaskFormatter("#####-###");
   mask.setValueContainsLiteralCharacters(false);
   return mask.valueToString(cep);
  }
  catch (ParseException ex) {
   ex.printStackTrace();
   return null;
  }
 }
 
 public static String getTelefone(String telefone) {
  
  try {
   String pattern = null;
   
   if(telefone.length() == 10) 
    pattern = "(##) ####-####";  //fixo com ddd   
   
   else if(telefone.length() == 11)
    pattern = "(##) #-####-####"; // celular com ddd
   //else if... outros formatos
   
   MaskFormatter mask = new MaskFormatter(pattern);
   mask.setValueContainsLiteralCharacters(false);
   return mask.valueToString(telefone);
  }
  catch (ParseException ex) {
   ex.printStackTrace();
   return null;
  }
 }
 
 public static String getDataSimples(Date date) {
  
  DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd/MM/yyyy", new Locale("pt", "BR"));  
  LocalDate hoje = date.toLocalDate();
  return dtf.format(date);
 }
 
 public static String getCPF(String cpf) {
  
  try {
   MaskFormatter mask = new MaskFormatter("###.###.###-##");
   mask.setValueContainsLiteralCharacters(false);
   return mask.valueToString(cpf);
  }
  catch (ParseException ex) {
   ex.printStackTrace();
   return null;
  }
 }
 
 public static String getValorMonetario(BigDecimal valor, boolean usaSimboloMoeda) {
  
  NumberFormat format = NumberFormat.getCurrencyInstance(new Locale("pt", "BR"));
  
  DecimalFormatSymbols dfs = ((DecimalFormat)format).getDecimalFormatSymbols();
  
  if(!usaSimboloMoeda)
   dfs.setCurrencySymbol("");
  
  ((DecimalFormat)format).setDecimalFormatSymbols(dfs);
  
  return format.format(valor);  
 }
}    
Em seguida temos a template jrxml que representa o modelo do relatório. Ela contém a query que busca os dados no banco de dados que criamos:
<?xml version="1.0" encoding="UTF-8"?>

<jasperReport name="TesteMascaras">
  
 <queryString language="SQL">
 
  SELECT algumaEntidade.cep,
   algumaEntidade.telefone,
   algumaEntidade.data,
   algumaEntidade.valor,
   algumaEntidade.cpf
  FROM algumaEntidade
 
 </queryString>
 
 <field name="cep" class="java.lang.String"/>
 <field name="telefone" class="java.lang.String"/>
 <field name="data" class="java.sql.Date"/>
 <field name="valor" class="java.math.BigDecimal"/>
 <field name="cpf" class="java.lang.String"/>
 
 <variable name="somaValor" class="java.math.BigDecimal" calculation="Sum">
  <variableExpression>$F{valor}</variableExpression>
 </variable>
 
 <title>
  <band height="46"  >
   <staticText>
    <reportElement x="29" y="8" width="180" height="30" />
    <textElement>
     <font isBold="true"/>
    </textElement>
    <text>MÁSCARAS</text>
   </staticText>
   <textField>
    <reportElement x="370" y="8" width="175" height="30" />
    <textFieldExpression>main.Mascaras.getDataAtualCompleta()</textFieldExpression>
   </textField>
  </band>
 </title>
 <columnHeader>
  <band height="61"  >
   <staticText>
    <reportElement x="0" y="0" width="80" height="30" />
    <text>cep</text>
   </staticText>
   <staticText>
    <reportElement x="90" y="0" width="78" height="30" />
    <text>telefone</text>
   </staticText>
   <staticText>
    <reportElement x="180" y="0" width="100" height="30" />
    <text>data</text>
   </staticText>
   <staticText>
    <reportElement x="422" y="0" width="100" height="30" />
    <text>valor</text>
   </staticText>
   <staticText>
    <reportElement x="297" y="0" width="100" height="30" />
    <text>cpf</text>
   </staticText>
  </band>
 </columnHeader>
 <detail>
  <band height="40"  >
   <textField>
    <reportElement x="0" y="0" width="80" height="30" />
    <textFieldExpression>main.Mascaras.getCEP($F{cep})</textFieldExpression>
   </textField>
   <textField>
    <reportElement x="90" y="0" width="85" height="30" />
    <textFieldExpression>main.Mascaras.getTelefone($F{telefone})</textFieldExpression>
   </textField>
   <textField>
    <reportElement x="180" y="0" width="100" height="30" />
    <textFieldExpression>main.Mascaras.getDataSimples($F{data})</textFieldExpression>
   </textField>
   <textField>
    <reportElement x="422" y="0" width="100" height="30" />
<!--     podmeos inclusive passar parametros que não sejam o campo $F, neste caso passomos um boolean literal -->
    <textFieldExpression>main.Mascaras.getValorMonetario($F{valor}, false)</textFieldExpression>
   </textField>
   <textField>
    <reportElement x="297" y="0" width="100" height="30" />
    <textFieldExpression>main.Mascaras.getCPF($F{cpf})</textFieldExpression>
   </textField>
  </band>
 </detail>
 <summary>
  <band height="44"  >
   <textField>
    <reportElement x="410" y="7" width="134" height="30" />
    <textFieldExpression>main.Mascaras.getValorMonetario($V{somaValor}, true)</textFieldExpression>
   </textField>
  </band>
 </summary>
</jasperReport>
    
E a classe Main que inicia a aplicação e chama o relatório:
package main;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;

import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.view.JasperViewer;

public class Main {

 public static void main(String[] args) throws Exception {

  String url = "jdbc:mysql://localhost/bancoDeDados?useSSL=true&serverTimezone=UTC&user=root&password=root";

  Class.forName("com.mysql.cj.jdbc.Driver");
  
  try (Connection conn = DriverManager.getConnection(url);) {

   InputStream stream = Main.class.getResourceAsStream("TesteMascaras.jxml");
   
   JasperReport jasper = JasperCompileManager.compileReport(stream);
     
   JasperPrint print = JasperFillManager.fillReport(jasper, null, conn);
   
   JasperViewer viewer = new JasperViewer(print);
   viewer.setVisible(true);
  }
 }
}    
O resultado é o relatório com os campos devidamente formatados abaixo.


Esse é o padrão correto para criação de máscaras em relatórios no Java.

"Assim diz Javé dos exércitos: Neste lugar agora arruinado, sem gente e sem criações, e também em todas as suas cidades haverá pastagens, onde os pastores farão suas ovelhas repousar."
Jr 33,12


       

Monday, November 19, 2018

Livro "A Philosophy of Software Design" de John Ousterhout

No livro A Philosophy of Software Design de John Ousterhout, o autor dedica várias páginas sobre comentários nas classes. Pode parecer apenas uma distração enfadonha, até você se dar conta de que comentários são a pedra angular para um conjunto de boas práticas no desenvolvimento de software.

Ousterhout apresenta algumas convenções para fazer comentários, dentre essas ele recomenda que a primeira coisa que você deve fazer ao criar uma classe é escrever comentários, os quais se dividem em dois tipos: alto-nível e baixo-nível.

Comentários de alto-nível são sobre a finalidade da classe e sua relação com outras classes. Esse tipo de comentário deve resumir tudo o que alguém precisa saber sobre a classe, exceto detalhes de implementação.

Comentáros de baixo nível são aqueles espalhados pela classe, devem funcionar como uma lanterna, cuja finalidade é esclarecer o como e o porquê - desde que o código não esteja claro o suficiente para responder a isso por si só.

Abordagem interessante.