Skip to content

21. Gerenciamento de acesso entre domínios

21.1. Architecture

Vamos examinar agora o problema das solicitações entre domínios. No documento [Tutoriel AngularJS / Spring 4], desenvolve-se um aplicativo cliente/servidor em que o cliente é um aplicativo AngularJS:

  • as páginas HTML / CSS / JS do aplicativo Angular são originárias do servidor [1];
  • em [2], o serviço [dao] faz uma solicitação a outro servidor, o servidor [2]. Ora, isso é proibido pelo navegador que executa o aplicativo Angular, pois se trata de uma falha de segurança. O aplicativo só pode consultar o servidor de onde ele vem, ou seja, o servidor [1];

Na verdade, não é correto dizer que o navegador proíbe a aplicação Angular de consultar o servidor [2]. O navegador, na verdade, consulta o servidor [2] para saber se ele autoriza que um cliente que não seja dele o consulte. Essa técnica de compartilhamento é chamada de CORS (Cross-Origin Resource Sharing). O servidor [2] dá sua autorização enviando cabeçalhos HTTP específicos.

Para demonstrar os problemas que podem surgir, vamos criar um aplicativo cliente/servidor em que:

  • o servidor será nosso servidor web / jSON seguro;
  • o cliente será uma página simples HTML equipada com um código JavaScript que fará solicitações ao servidor web / jSON;

Vamos implementar a seguinte arquitetura:

  • em [1], um aplicativo web fornece as páginas HTML / jS;
  • em [2], o navegador executa o JavaScript incorporado nas páginas HTML para consultar o serviço web seguro [3];

21.2. O projeto [spring-cors-server-jdbc-generic]

21.2.1. Configuração do ambiente de trabalho

  
  • carregue os projetos acima. Os projetos [spring-cors-*] estarão na pasta [<exemples>\spring-database-generic\spring-cors];
  • pressione Alt-F5 e gere novamente todos os projetos Maven;

Em seguida, execute a configuração de execução denominada [spring-cors-server-jdbc-generic] (os projetos SGBD e MySQL devem ser iniciados), que inicia um serviço web na porta 8081:

 

Preencha a base [dbproduitscategories] com a configuração de execução denominada [spring-jdbc-generic-04-fillDataBase]:

 

Execute a configuração de execução denominada [spring-cors-client-generic], que inicia uma segunda aplicação web (em outro Tomcat) na porta 8082:

 

Usando um navegador, acesse o URL [http://localhost:8082/client.html]:

  • em [1], solicita-se a versão resumida de todas as categorias;
  • em [2], a resposta jSON do servidor;

21.2.2. O projeto do cliente [spring-cors-client-generic]

  

O arquivo [application.properties] nos permite definir a porta do aplicativo web do cliente. Seu conteúdo é o seguinte:


server.port=8082

Portanto:

  • O cliente é um aplicativo web disponível em URL [http://localhost:8082];
  • o servidor é um aplicativo web disponível em URL [http://localhost:8081];

Como o cliente não é acessado pela mesma porta que o servidor, surge o problema das solicitações entre domínios. De fato, [http://localhost:8081] e [http://localhost:8082] são dois domínios diferentes.

21.2.3. Configuração do Maven

O projeto é um projeto Maven com o seguinte arquivo [pom.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<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>dvp.spring.database</groupId>
    <artifactId>spring-cors-client-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-cors-client-generic</name>
    <description>Client cors for webjson server</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
        <relativePath /> <!-- busca do pai no repositório -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <!-- plug-ins -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • linhas 14-19: trata-se de um projeto Spring Boot;
  • linhas 27-30: utiliza-se a dependência [spring-boot-starter-web], que traz consigo um servidor Tomcat e o Spring MVC;

21.2.4. Noções básicas sobre jQuery e JavaScript

A aplicação web exibe a seguinte página única:

 

Ela inclui código JavaScript (jS) executado no navegador. Apresentaremos alguns conceitos básicos de JavaScript que nos permitirão compreender o código. O cliente fará chamadas HTTP utilizando a biblioteca jQuery [https://jquery.com/], que oferece diversas funções que facilitam o desenvolvimento em JavaScript. Criamos um arquivo estático HTML [jQuery.html], que colocamos na pasta [static]:

 

Esse arquivo terá o seguinte conteúdo:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>JQuery-01</title>
  <script type="text/javascript" src="/js/jquery-2.1.3.min.js"></script>
</head>
<body>
  <h3>Rudiments de JQuery</h3>
  <div id="element1">
    Elément 1
  </div>
</body>
</html>
  • linha 6: importação de jQuery;
  • linhas 10-12: um elemento da página com o ID [element1]. Vamos trabalhar com esse elemento.

Precisamos baixar o arquivo [jquery-2.1.3.min.js]. Encontraremos a versão mais recente do jQuery no URL [http://jquery.com/download/]:

Image

Colocaremos o arquivo baixado na pasta [static / js]:

  

Feito isso, acesse a visualização estática [jQuery.html] usando o Chrome [1-2]:

No Google Chrome, digite [Ctrl-Maj-I] para exibir as ferramentas de desenvolvimento [3]. A aba [Console] [4] permite executar código JavaScript. A seguir, apresentamos comandos JavaScript a serem digitados, acompanhados de uma explicação.

JS
résultat
$("#element1")
: retorna a coleção de todos os elementos com o id
[element1], ou seja, normalmente uma coleção
de 0 ou 1 elemento, pois não é possível ter
dois ids idênticos em uma página HTML.
$("#element1").text("blabla")
: atribui o texto [blabla] a todos os elementos da
coleção. Isso tem como efeito alterar o
conteúdo exibido pela página
$("#element1").hide()
oculta os elementos da coleção. O texto
[blabla] não é mais exibido.
$("#element1")
: exibe novamente a coleção. Isso nos
permitir ver que o elemento com id [element1] possui
o atributo CSS style='display: none;', o que faz
que o elemento fique oculto.
$("#element1").show()
: exibe os elementos da coleção. O texto
[blabla] reaparece. É o atributo
CSS style='display: block;' que garante essa
exibição.
$("#element1").attr('style','color: red')
: define um atributo para todos os elementos da
coleção. O atributo aqui é [style] e seu valor
[color: red]. O texto [blabla] fica em vermelho.
Tableau
Dictionnaire

Vale ressaltar que o URL do navegador não sofreu alterações durante todas essas operações. Não houve troca de dados com o servidor web. Tudo ocorre internamente no navegador. Agora, vamos visualizar o código-fonte da página:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>JQuery-01</title>
  <script type="text/javascript" src="/js/jquery-1.11.1.min.js"></script>
</head>
<body>
  <h3>Rudiments de JQuery</h3>
  <div id="element1">
    Elément 1
  </div>
</body>
</html>

Este é o texto inicial. Ele não reflete de forma alguma as alterações que fizemos no elemento nas linhas 10 a 12. É importante lembrar disso ao depurar JavaScript. Portanto, muitas vezes é desnecessário visualizar o código-fonte da página exibida.

21.2.5. O código jS do aplicativo

Voltemos ao código HTML da página do aplicativo cliente que irá consultar o serviço web / jSON:

 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Spring MVC</title>
<script type="text/javascript" src="/js/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="/js/client.js"></script>
</head>
<body>
    <h2>Client du service web / jSON</h2>
    <form id="formulaire">
        <!-- método HTTP -->
        Méthode HTTP :
        <!--  -->
        <input type="radio" id="get" name="method" value="get" checked="checked" />GET
        <!--  -->
        <input type="radio" id="post" name="method" value="post" />POST
        <!--  URL -->
        <br /> <br />URL cible : <input type="text" id="url" size="30"><br />
        <!-- valor lançado -->
        <br /> Chaîne jSON à poster : <input type="text" id="posted" size="50" />
        <!-- botão de confirmação -->
        <br /> <br /> <input type="submit" value="Valider" onclick="javascript:requestServer(); return false;"></input>
    </form>
    <hr />
    <h2>Réponse du serveur</h2>
    <div id="response"></div>
</body>
</html>
  • linha 6: importamos a biblioteca jQuery;
  • linha 7: importa-se um código que iremos escrever;
  • linhas 11, 15, 17, 21: anotamos os identificadores [id] dos componentes da página. O JavaScript faz referência a esses componentes por meio desses identificadores;

O código [client.js] é o seguinte:


// dados gerais
var url;
var posted;
var response;
var method;

function requestServer() {
    // recuperamos as informações do formulário
    var urlValue = url.val();
    var postedValue = posted.val();
    method = document.forms[0].elements['method'].value;
    // faz-se uma chamada Ajax manualmente
    if (method === "get") {
        doGet(urlValue);
    } else {
        doPost(urlValue, postedValue);
    }
}

function doGet(url) {
    // é feita uma chamada Ajax manualmente
    $.ajax({
        headers : {
            ''Authorization': 'Basic YWRtaW46YWRtaW4='
        },
        url : 'http://localhost:8081' + url,
        type : 'GET',
        dataType : 'tex/plain',
        beforeSend : function() {
        },
        success : function(data) {
            // resultado em texto
            response.text(data);
        },
        complete : function() {
        },
        error : function(jqXHR) {
            // erro de sistema
            response.text(jqXHR.responseText);
        }
    })
}

function doPost(url, posted) {
    // fazemos uma chamada Ajax manualmente
    $.ajax({
        headers : {
            'Autorização: 'Basic YWRtaW46YWRtaW4='
        },
        url : 'http://localhost:8081    ' + url,
        type : 'POST',
        contentType : 'application/json',
        data : posted,
        dataType : 'tex/plain',
        beforeSend : function() {
        },
        success : function(data) {
            // resultado em texto
            response.text(data);
        },
        complete : function() {
        },
        error : function(jqXHR) {
            // erro de sistema
            response.text(jqXHR.responseText);
        }
    })
}

// ao carregar o documento
$(document).ready(function() {
    // recuperando as referências dos componentes da página
    url = $("#url");
    posted = $("#posted");
    response = $("#response");
});
  • linhas 71-75: do código jS executado ao final do carregamento do documento no navegador;
  • linhas 73-75: recuperam-se as referências de três das áreas do documento HTML;
  • linhas 2-5: variáveis globais conhecidas em todas as funções definidas no arquivo jS;
  • linha 9: recupera-se o valor digitado pelo usuário no URL;
  • linha 10: recupera-se o valor que ele deseja enviar;
  • linha 11: recupera-se o formato [get] ou [post] a ser usado para solicitar o URL da linha 9:
    • “document” refere-se ao documento carregado pelo navegador, o que é conhecido como DOM (Document Object Model),
    • document.forms[0] refere-se ao primeiro formulário do documento, já que um documento pode conter vários. Neste caso, há apenas um,
    • document.forms[0].elements['method'] refere-se ao elemento do formulário que possui o atributo [name='method']. Existem dois:

<input type="radio" id="get" name="method" value="get" checked="checked" />GET
<input type="radio" id="post" name="method" value="post" />POST
  • (continuação)
    • document.forms[0].elements['method'].value é o valor que será enviado para o componente que possui o atributo [name='method']. Sabe-se que o valor enviado é o valor do atributo [value] do botão de opção marcado. Aqui, será, portanto, uma das cadeias ['get', 'post'];
  • linhas 13-18: dependendo do método HTTP a ser utilizado, executa-se o método [doGet] ou [doPost];
  • o método jQuery [$.ajax] realiza uma chamada ao método HTTP;
  • linhas 23-25: acessa-se um servidor que exige um cabeçalho HTTP [Authorization: Basic code]. Criamos esse cabeçalho para o usuário [admin / admin], que é o único com permissão para consultar o servidor;
  • linha 26: o usuário inserirá URL do tipo [/getAllLongCategories, /saveCategories, ...]. Portanto, é necessário preencher esses URL;
  • linha 27: método HTTP a ser utilizado;
  • linha 28: o servidor retorna jSON. Indica-se o tipo [text/plain] como tipo de resultado para exibi-lo exatamente como foi recebido;
  • linha 33: exibição da resposta de texto do servidor;
  • linha 39: exibição de uma eventual mensagem de erro no formato de texto;
  • linha 44: o método [doPost] recebe um segundo parâmetro, que é o valor a ser enviado;
  • linha 52: para indicar que o valor enviado será na forma de uma string jSON;

21.2.6. Execução do cliente

O aplicativo cliente é um aplicativo de console iniciado pela seguinte classe executável [Client]:

  

package spring.cors.client;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

@EnableAutoConfiguration
public class Client {

    public static void main(String[] args) {
        SpringApplication.run(Client.class, args);
    }
}
  • linha 6: a anotação [@EnableAutoConfiguration] é uma anotação do projeto [Spring Boot] (linha 4). O Spring Boot inspecionará os arquivos presentes no Classpath do projeto. Nesse caso, serão todas as dependências Maven trazidas pela dependência única do arquivo [pom.xml]:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
</dependencies>

Essa dependência traz um grande número de arquivos, incluindo o Spring MVC e um servidor Tomcat. Devido à presença dessas dependências, o Spring Boot configurará, com valores padrão, um projeto Spring MVC a ser executado no Tomcat. O servidor Tomcat é, então, configurado para funcionar na porta 8080. Se quisermos ignorar os valores padrão escolhidos pelo Spring Boot, podemos usar o arquivo [application.properties] na raiz do Classpath (tudo o que está em [src / main / resources] fica na raiz do Classpath):

  

Especificamos que o servidor Tomcat deve operar na porta 8082 da seguinte maneira:


server.port=8082

A lista de parâmetros utilizáveis pode ser encontrada em [application.properties] até URL (junho de 2015) [http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html];

Voltando ao código de [Client.java]:

  • linha 10: o método [SpringApplication.run] irá implantar a página [client.html] no servidor Tomcat presente no Classpath do projeto;

21.2.7. O URL [/getAllShortCategories]

Estamos lançando:

  • o servidor web/json seguro na porta 8081 (configuração [spring-security-server-jdbc-generic]);
  • o cliente desse servidor na porta 8082 (configuração [spring-cors-client-generic]);

em seguida, solicitamos o URL [http://localhost:8082/client.html] [1]:

  • no [2], executamos um GET sobre o URL e o [http://localhost:8081/getAllShortCategories];

Não obtemos resposta do servidor. Ao verificar o console de desenvolvimento do Chrome (Ctrl+Shift+I), encontramos um erro:

  • em [1], estamos na aba [Network];
  • em [2], percebemos que a solicitação HTTP que foi feita não é [GET], mas sim [OPTIONS]. No caso de uma solicitação entre domínios, o navegador verifica junto ao servidor se determinadas condições estão atendidas, enviando-lhe uma solicitação HTTP [OPTIONS]. Nesse caso, as solicitações são aquelas indicadas pelos marcadores [5-6];
  • Em [5], o navegador pergunta se o destino URL pode ser alcançado com um GET. A solicitação [Access-Control-Request-Method] requer uma resposta com um cabeçalho HTTP [Access-Control-Allow-Methods] indicando que o método solicitado foi aceito;
  • em [6], o navegador envia o cabeçalho HTTP [Origin: http://localhost:8081]. Esse cabeçalho solicita uma resposta no cabeçalho HTTP [Access-Control-Allow-Origin] indicando que a origem especificada é aceita;
  • em [7], o navegador pergunta se os cabeçalhos HTTP, [accept] e [authorization] são aceitos. A solicitação [Access-Control-Request-Headers] aguarda uma resposta com um cabeçalho HTTP [Access-Control-Allow-Headers] indicando que os cabeçalhos solicitados são aceitos;
  • ocorre um erro em [3]. Ao clicar no ícone, aparece o erro [4];
  • em [4], a mensagem indica que o servidor não enviou o cabeçalho HTTP [Access-Control-Allow-Origin], que indica se a origem da solicitação foi aceita;
  • em [8], pode-se constatar que o servidor efetivamente não enviou esse cabeçalho. Consequentemente, o navegador recusou-se a realizar a solicitação HTTP GET solicitada inicialmente;

Precisamos modificar o servidor web / jSON.

21.2.8. Um novo serviço web / json

Criamos um novo projeto Maven [spring-cors-server-jdbc-generic]:

 

A configuração do Maven para o novo serviço web é a seguinte:


<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>dvp.spring.database</groupId>
    <artifactId>spring-cors-server-jdbc-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>spring-cors-server-jdbc-generic</name>
    <description>démo spring cors</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <!-- plug-ins -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-security-server-jdbc-generic</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
  • linhas 30-32: aproveitamos todo o trabalho realizado até agora, utilizando o arquivo do servidor web/JSON seguro;

No final, as dependências são as seguintes:

  

A classe de configuração [AppConfig] é a seguinte:

  

package spring.cors.server.config;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.DispatcherServlet;

@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ spring.security.config.AppConfig.class })
public class AppConfig {

    // solicitações entre domínios
    @Bean
    public boolean isCorsEnabled() {
        return true;
    }
...
}
  • linha 12: a classe é uma classe de configuração do Spring;
  • linha 9: outros componentes Spring devem ser buscados no pacote [spring.cors.server.service];
  • linha 14: importam-se os beans do projeto [spring-security-server-jdbc-generic];
  • linhas 18-21: criamos um componente Spring chamado [isCorsEnabled] que indica se aceitamos ou não clientes externos ao domínio do servidor;

21.2.9. Os controladores

O novo serviço web possui quatro controladores:

  
  • [CorsCategorieController] gerencia o URL de processamento de categorias. Ele gerencia apenas os cabeçalhos CORS dos clientes da web. Caso contrário, delega a tarefa ao controlador [CategorieController] da dependência [spring-webjson-server-jdbc-generic];
  • [CorsProduitController] e [CorsAuthenticateController] fazem o mesmo, delegando a tarefa aos controladores [ProduitController] da dependência [spring-webjson-server-jdbc-generic] e [AuthenticateController] da dependência[spring-security-server-jdbc-generic];
  • [CorsController] serve para fatorar o que é comum aos três controladores anteriores;

21.2.9.1. O controlador [CorsController]

A classe [CorsController] é a seguinte:


package spring.cors.server.service;

import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CorsController {

    @Autowired
    private boolean isCorsEnabled;

    // envio das opções ao cliente
    public void sendOptions(String origin, HttpServletResponse response) {
        // CORS permitido?
        if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
            return;
        }
        // define-se o cabeçalho CORS
        response.addHeader("Access-Control-Allow-Origin", origin);
        // autorizando determinados cabeçalhos
        response.addHeader("Access-Control-Allow-Headers", "accept, authorization");
        // autoriza-se o GET
        response.addHeader("Access-Control-Allow-Methods", "GET");
    }
}
  • linha 8: a classe [CorsController] é um controlador Spring;
  • linhas 11-12: injeção do bean [isCorsEnabled], que indica se os cabeçalhos CORS devem ou não ser gerenciados;
  • linhas 15-26: o método [sendOptions] é responsável por responder aos clientes que enviam cabeçalhos CORS;
  • linhas 17-19: se o aplicativo estiver configurado para aceitar solicitações entre domínios e se o remetente tiver enviado o cabeçalho HTTP [Origin] e se essa origem começar com [http://localhost], então aceitaremos a solicitação entre domínios; caso contrário, a rejeitaremos;
  • linha 21: se o cliente estiver no domínio [http://localhost:port], enviamos o cabeçalho HTTP:
Access-Control-Allow-Origin:  http://localhost:porta

o que significa que o servidor aceita a origem do cliente;

  • linhas 22-25: indicamos dois cabeçalhos HTTP específicos na solicitação HTTP [OPTIONS]:
Access-Control-Request-Method: GET
Access-Control-Request-Headers: accept, authorization

Para os cabeçalhos HTTP e [Access-Control-Request-X], o servidor responde com os cabeçalhos HTTP e [Access-Control-Allow-X], nos quais indica o que está autorizado. As linhas 22 a 25 limitam-se a repetir a solicitação do cliente para indicar que ela foi aceita;

21.2.9.2. O controlador [CorsCategorieController]


package spring.cors.server.service;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import spring.jdbc.entities.Categorie;
import spring.webjson.server.entities.CoreCategorie;
import spring.webjson.server.service.CategorieController;
import spring.webjson.server.service.Response;

@RestController
public class CorsCategorieController extends CorsController {

    @Autowired
    private CategorieController categorieController;

    @RequestMapping(value = "/cors-getAllShortCategories", method = RequestMethod.OPTIONS)
    public void corsGetAllShortCategories(@RequestHeader(value = "Origin", required = false) String origin,
            HttpServletResponse response) {
        sendOptions(origin, response);
    }

    @RequestMapping(value = "/cors-getAllShortCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllShortCategories(
            @RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse response) {
        // método de origem
        return categorieController.getAllShortCategories();
    }

...
}
  • linha 19: a anotação [@RestController] torna a classe tanto um componente Spring quanto um controlador MVC, que envia ele mesmo suas respostas ao cliente;
  • linha 20: a classe [CorsCategorieController] estende a classe [CorsController] que acabamos de ver;
  • linhas 22-23: injeção do controlador [CategorieController categorieController] a partir da dependência [spring-webjson-server-jdbc-generic];
  • linhas 25-29: processam o URL [/cors-getAllShortCategories] quando solicitado com o comando HTTP [OPTIONS]. Por convenção, decidimos que os clientes da web que desejarem chamar o URL [/U] do serviço web seguro deverão, na verdade, chamar o URL [/cors-U]. O serviço web implantado terá, assim, dois tipos de URL:
    • [/U]: para clientes que não são da web;
    • [/cors-U]: para clientes web;
  • linha 25: o método [/cors-getAllShortCategories] aceita como parâmetros:
    • o objeto [@RequestHeader(value = "Origin", required = false)], que irá recuperar o cabeçalho HTTP [Origin] da solicitação. Esse cabeçalho foi enviado pelo remetente da solicitação:
Origin:http://localhost:8082

Indica-se que o cabeçalho HTTP [Origin] é opcional [required = false]. Nesse caso, se o cabeçalho estiver ausente, o parâmetro [String origin] assumirá o valor null. Com [required = true], que é o valor padrão, uma exceção é lançada se o cabeçalho estiver ausente. Pretendemos evitar essa situação;

  • (continuação)
    • o objeto [HttpServletResponse response], que será devolvido ao cliente que fez a solicitação;

Esses dois parâmetros são injetados pelo Spring;

  • linha 28: delegamos o processamento da solicitação ao método [sendOptions] da classe pai [CorsController];
  • linhas 31-36: o método [getAllShortCategories] processa o URL e o [/cors-getAllShortCategories] quando é chamado com um GET;
  • linha 35: a tarefa é delegada ao método [CategorieController.getAllShortCategories] da dependência [spring-webjson-server-jdbc-generic];

Agora estamos prontos para novos testes. Lançamos a nova versão do serviço web e descobrimos que o problema persiste. Nada mudou. Se, na linha 28 acima, colocarmos uma saída de console, ela nunca será exibida, o que demonstra que o método [corsGetAllShortCategories] da linha 25 nunca é chamado.

Após algumas pesquisas, descobrimos que o Spring MVC processa por conta própria os comandos HTTP e [OPTIONS] com um tratamento padrão. Portanto, é sempre o Spring que responde, e nunca o método [corsGetAllShortCategories] da linha 25. Esse comportamento padrão do Spring MVC pode ser alterado. Modificamos a classe [AppConfig] existente:

  

package spring.cors.server.config;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.DispatcherServlet;

@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ spring.security.config.AppConfig.class })
public class AppConfig {

    // solicitações entre domínios
    @Bean
    public boolean isCorsEnabled() {
        return true;
    }
    
    @Autowired
    private DispatcherServlet dispatcherServlet;
    
    @PostConstruct
    public void init(){
        // o próprio aplicativo processa as solicitações HTTP [OPTIONS]
        dispatcherServlet.setDispatchOptionsRequest(true);
    }
}

  • linhas 23-24: injetamos o componente [DispatcherServlet dispatcherServlet], que foi definido na dependência [spring-webjson-server-jdbc-generic];
  • linhas 26-30: a anotação [@PostConstruct] faz com que o método [init] seja executado após a instanciação da classe [AppConfig] e após as injeções feitas pelo Spring;
  • linha 29: solicita-se que o servlet encaminhe os comandos HTTP e [OPTIONS] para a aplicação;

Repetimos os testes com essa nova configuração. Obtemos o seguinte resultado:

  • em [1], observamos que há duas solicitações HTTP para URL e [http://localhost:8080/getAllCategories];
  • em [2], a solicitação [OPTIONS];
  • no [3], os três cabeçalhos HTTP que acabamos de configurar na resposta do servidor;

Vamos agora examinar a segunda solicitação:

  • em [1], a solicitação analisada;
  • em [2], que é a solicitação GET. Graças à primeira solicitação [OPTIONS], o navegador recebeu as informações que solicitava. Agora, ele realiza a solicitação [GET], solicitada inicialmente;
  • em [3], a resposta do servidor;
  • em [4], o servidor envia jSON;
  • em [5], ocorreu um erro;
  • em [6], a mensagem de erro;

É mais difícil explicar o que aconteceu aqui. A resposta [3] do servidor é normal, [HTTP/1.1 200 OK]. Portanto, deveríamos ter o documento solicitado. É possível que o servidor tenha realmente enviado o documento, mas que seja o navegador que esteja impedindo seu uso porque ele exige que, para a solicitação GET também, a resposta inclua o cabeçalho HTTP [Access-Control-Allow-Origin:http://localhost:8081].

Alteramos o método que processa o GET do URL [/cors-getAllShortCategories]:


    @RequestMapping(value = "/cors-getAllShortCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllShortCategories(
            @RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse response) {
        // cabeçalhos CORS
        sendOptions(origin, response);
        // método de origem
        return categorieController.getAllShortCategories();
}
  • linha 5: assim como na solicitação HTTP [OPTIONS], o servidor enviará os cabeçalhos HTTP CORS para uma solicitação HTTP [GET];

Após essa modificação, os resultados são os seguintes:

 

Conseguimos, de fato, a versão resumida de todas as categorias.

21.2.9.3. Os URL [GET]

Nos controladores [CorsCategorieController, CorsProduitController, CorsAuthenticateController], o código das ações que processam os URL solicitados com um [GET] segue o modelo das ações que processaram anteriormente os URL e [/cors-getAllShortArticles]. O leitor pode verificar o código nos exemplos fornecidos com este documento. Aqui está um exemplo para o URL e o [/cors-getAllLongProduits] do controlador [CorsProduitController]:


package spring.cors.server.service;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import spring.jdbc.entities.Produit;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.service.ProduitController;
import spring.webjson.server.service.Response;

@RestController
public class CorsProduitController extends CorsController {

    @Autowired
    private ProduitController produitController;

@RequestMapping(value = "/cors-getAllLongProduits", method = RequestMethod.GET)
    public Response<List<Produit>> getAllLongProduits(@RequestHeader(value = "Origin", required = false) String origin,HttpServletResponse response) {
        // cabeçalhos CORS
        sendOptions(origin, response);
        // método de origem
        return produitController.getAllLongProduits();

    }

    @RequestMapping(value = "/cors-getAllLongProduits", method = RequestMethod.OPTIONS)
    public void corsGetAllLongProduits(@RequestHeader(value = "Origin", required = false) String origin,
            HttpServletResponse response) {
        sendOptions(origin, response);
    }
...
}
 

21.2.9.4. Os URL e [POST]

Vamos examinar o seguinte caso:

  • fazemos um POST [1] para o URL [2];
  • em [3], o valor lançado. Trata-se da sequência jSON de uma categoria sem produtos;
  • por fim, buscamos criar uma categoria chamada [categorie[2]];

Por enquanto, não alteramos nenhum código. O resultado obtido é o seguinte:

  • em [1], assim como nas solicitações [GET], o navegador realiza uma solicitação [OPTIONS];
  • em [2], ele solicita autorização de acesso para uma solicitação [POST]. Anteriormente, era [GET];
  • em [3], ele solicita autorização para enviar os cabeçalhos HTTP e [accept, authorization, content-type]. Anteriormente, havia apenas os dois primeiros cabeçalhos;
  • em [4], o serviço web não concede todas as autorizações solicitadas, o que provoca o erro [5];

Modificamos o método [CorsController.sendOptions] da seguinte maneira:


    public void sendOptions(String origin, HttpServletResponse response) {
        // Cors permitido?
        if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
            return;
        }
        // definimos o cabeçalho CORS
        response.addHeader("Access-Control-Allow-Origin", origin);
        // autoriza-se determinados cabeçalhos
        response.addHeader("Access-Control-Allow-Headers", "accept, authorization, content-type");
        // autoriza-se o GET e o POST
        response.addHeader("Access-Control-Allow-Methods", "GET, POST");
    }
}
  • linha 9: adicionamos o cabeçalho HTTP [Content-Type] (não importa se está em maiúsculas ou minúsculas);
  • linha 11: adicionamos o método HTTP [POST];

Com isso, os métodos [POST] são tratados da mesma forma que as solicitações [GET]. Veja o exemplo dos métodos URL e [/cors-saveCategories] no controlador [CorsCategorieController]:


    @RequestMapping(value = "/cors-saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request,
            @RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse response) {
        // cabeçalhos CORS
        sendOptions(origin, response);
        // método de origem
        return categorieController.saveCategories(request);
    }

    @RequestMapping(value = "/cors-saveCategories", method = RequestMethod.OPTIONS)
    public void corsSaveCategories(@RequestHeader(value = "Origin", required = false) String origin,
            HttpServletResponse response) {
        sendOptions(origin, response);
}

Após essas alterações, o resultado obtido é o seguinte:

 

A categoria [categorie[2]] foi adicionada corretamente ao banco de dados. O SGBD atribuiu a ela a chave primária 226. Isso pode ser verificado com o método GET [/cors-getAllShortCategories]:

 

21.2.10. Conclusão

Nosso aplicativo agora suporta consultas entre domínios. Essas consultas podem ser autorizadas ou não por meio da configuração na classe [AppConfig]:


package spring.cors.server.config;

...

@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ spring.security.config.AppConfig.class })
public class AppConfig {

    // solicitações entre domínios
    @Bean
    public boolean isCorsEnabled() {
        return true;
    }
...
}

21.3. O projeto Eclipse [spring-cors-server-jpa-generic]

O serviço web CORS será agora implementado pelo projeto [spring-cors-server-jpa-generic], que se baseia no projeto [spring-security-server-jpa-generic], responsável pelo gerenciamento do acesso ao banco de dados com o Spring Data JPA:

O projeto [spring-cors-server-jpa-generic] é obtido por meio de uma cópia do projeto analisado anteriormente, o [spring-cors-server-jdbc-generic].

  

Em seguida, há duas alterações a serem feitas. A primeira está no arquivo [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>dvp.spring.database</groupId>
    <artifactId>spring-cors-server-jpa-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>spring-cors-server-jpa-generic</name>
    <description>démo spring cors</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <!-- plug-ins -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-security-server-jpa-generic</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
  • linhas 30-32: a dependência do serviço web seguro [spring-security-server-jpa-generic];

No final, as dependências do projeto são as seguintes:

  

Observação: pressione Alt-F5 e, em seguida, gere novamente todos os projetos

A segunda alteração consiste em atualizar as importações nas classes que apresentam erros [Alt-Maj-O].

É isso. Iniciamos o serviço web CORS com a configuração de execução [spring-cors-server-jpa-generic-hibernate-eclipselink]:

Em seguida, iniciamos o cliente genérico:

e, em um navegador, solicitamos o URL, [1] e GET. No [2], percebe-se que a versão resumida das categorias retornadas contém o campo [entityType], que não estava presente na versão anterior, JDBC.

Vamos abordar outras duas arquiteturas CORS:

  • arquitetura CORS / JPA EclipseLink / DB2;
  • arquitetura CORS / JPA OpenJpa / Firebird;

Vamos implementar a seguinte arquitetura:

Carregamos os seguintes projetos:

  

Observação: pressione Alt-F5 e gere novamente todos os projetos Maven.

Execute o SGBD e o DB2 e verifique se o banco de dados [dbproduitscategories] realmente existe. Caso contrário, crie-o (parágrafo 12.1.2).

Os usuários são criados no banco de dados [dbproduitscategories] com a configuração de execução [spring-security-create-users-hibernate-eclipselink]:

Image

Em seguida, execute o serviço web CORS com a configuração de execução denominada [spring-cors-server-jpa-generic-hibernate-eclipselink] e seu cliente denominado [spring-cors-client-generic]:

Preencha a base de dados [dbproduitscategories] com valores utilizando a configuração de execução [spring-jdbc-generic-04-fillDataBase]:

 

Por fim, acesse em um navegador o seguinte URL:

 

21.5. Arquitetura CORS / JPA OpenJPA / Firebird

Vamos agora implementar a seguinte arquitetura:

Carregamos os seguintes projetos:

  

Observação: pressione Alt-F5 e gere novamente todos os projetos Maven.

Inicie o SGBD Firebird e verifique se o banco de dados [dbproduitscategories] realmente existe. Caso contrário, crie-o (parágrafo 14.1.2).

Criamos os usuários no banco de dados [dbproduitscategories] com a configuração de execução [spring-security-create-users-openjpa]:

Image

Em seguida, inicie o serviço web CORS com a configuração de execução denominada [spring-cors-server-jpa-generic-openjpa]:

Inicie o cliente CORS com a configuração [spring-cors-client-generic]:

 

Preencha a base de dados [dbproduitscategories] com valores usando a configuração de execução [spring-jdbc-generic-04-fillDataBase]:

 

Por fim, acesse a seguinte URL em um navegador: