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