21. Gestão de acesso entre domínios
21.1. Arquitetura
Vamos agora analisar a questão dos pedidos entre domínios. No documento [Tutorial AngularJS / Spring 4], desenvolvemos uma aplicação cliente/servidor em que o cliente é uma aplicação AngularJS:
![]() |
- as páginas HTML/CSS/JS da aplicação Angular provêm do servidor [1];
- em [2], o serviço [dao] faz uma solicitação a outro servidor, o servidor [2]. Ora, isto é proibido pelo navegador que executa a aplicação Angular, pois constitui uma vulnerabilidade de segurança. A aplicação só pode consultar o servidor de onde provém, ou seja, o servidor [1];
Na verdade, não é correto dizer que o navegador impede a aplicação Angular de consultar o servidor [2]. Na realidade, o navegador consulta o servidor [2] para determinar se este permite que um cliente que não seja originário do seu próprio domínio o consulte. Esta técnica de partilha é chamada CORS (Cross-Origin Resource Sharing). O servidor [2] concede permissão enviando cabeçalhos HTTP específicos.
Para demonstrar os problemas que podem surgir, vamos criar uma aplicação cliente/servidor em que:
- o servidor será o nosso servidor web/JSON seguro;
- o cliente será uma página HTML simples equipada com código JavaScript que fará pedidos ao servidor web/JSON;
Iremos implementar a seguinte arquitetura:
![]() |
- em [1], uma aplicação web fornece 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. Configurar o ambiente de desenvolvimento
![]() |
- Faça o download dos projetos listados acima. Os projetos [spring-cors-*] podem ser encontrados na pasta [<examples>\spring-database-generic\spring-cors];
- Prima Alt-F5 e recompile todos os projetos Maven;
Em seguida, execute a configuração de execução denominada [spring-cors-server-jdbc-generic] (a base de dados MySQL deve estar em execução), que inicia um serviço web na porta 8081:
![]() |
Preencha a base de dados [dbproduitscategories] utilizando a configuração de tempo de execução denominada [spring-jdbc-generic-04-fillDataBase]:
![]() |
Execute a configuração de tempo de execução denominada [spring-cors-client-generic], que inicia uma segunda aplicação web (numa outra instância do Tomcat) na porta 8082:
![]() |
Utilizando um navegador, aceda ao URL [http://localhost:8082/client.html]:
![]() |
- Em [1], solicitamos a versão resumida de todas as categorias;
- em [2], a resposta JSON do servidor;
21.2.2. O projeto cliente [spring-cors-client-generic]
![]() |
![]() |
O ficheiro [application.properties] permite-nos definir a porta para a aplicação web do cliente. O seu conteúdo é o seguinte:
server.port=8082
Assim:
- o cliente é uma aplicação web disponível no URL [http://localhost:8082];
- o servidor é uma aplicação web disponível no URL [http://localhost:8081];
Como o cliente não é acedido através da mesma porta que o servidor, surge a questão dos pedidos entre domínios. Com efeito, [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 ficheiro [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 /> <!-- lookup parent from repository -->
</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>
<!-- plugins -->
<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: este é um projeto Spring Boot;
- linhas 27–30: usamos a dependência [spring-boot-starter-web], que inclui um servidor Tomcat e o Spring MVC;
21.2.4. Noções básicas de jQuery e JavaScript
![]() |
A aplicação web apresenta a seguinte página única:
![]() |
Inclui código JavaScript (JS) que é executado no navegador. Abordaremos alguns conceitos básicos de JavaScript para nos ajudar a compreender o código. O cliente irá efetuar pedidos HTTP utilizando a biblioteca jQuery [https://jquery.com/], que fornece muitas funções que simplificam o desenvolvimento em JavaScript. Criamos um ficheiro HTML estático [jQuery.html] e colocamo-lo na pasta [static]:
![]() |
Este ficheiro 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 do jQuery;
- linhas 10–12: um elemento da página com o ID [element1]. Vamos trabalhar com este elemento.
Precisamos de descarregar o ficheiro [jquery-2.1.3.min.js]. Pode encontrar a versão mais recente do jQuery no URL [http://jquery.com/download/]:

Vamos colocar o ficheiro descarregado na pasta [static/js]:
![]() |
Depois de fazer isso, abra a visualização estática [jQuery.html] no Chrome [1-2]:
![]() |
No Google Chrome, prima [Ctrl-Shift-I] para abrir as ferramentas de programador [3]. O separador [Console] [4] permite-lhe executar código JavaScript. Abaixo, fornecemos comandos JavaScript para digitar e explicamos o que fazem.
|
: devolve a coleção de todos os elementos com o id [element1], pelo que normalmente é uma coleção de 0 ou 1 elemento, porque não é possível ter dois IDs idênticos numa página HTML. | ![]() |
|
: atribui o texto [blabla] a todos os elementos da coleção. Isto altera o conteúdo exibido pela página | ![]() |
|
oculta os elementos da coleção. O texto [blabla] deixa de ser exibido. | ![]() |
|
: exibe a coleção novamente. Isto permite-nos ver que o elemento com o ID [element1] tem o atributo CSS style='display: none;', o que faz com que o elemento fique oculto. | |
|
: exibe os elementos da coleção. O texto [blabla] aparece novamente. É o style='display: block;' que garante esta exibição. | ![]() |
|
: define um atributo em todos os elementos da coleção. O atributo aqui é [style] e o seu valor [color: red]. O texto [blabla] fica vermelho. | ![]() |
![]() | |
![]() |
Note que o URL do navegador não mudou durante todas estas operações. Não houve comunicação com o servidor web. Tudo acontece dentro do navegador. Agora, vamos ver 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 original. Não reflete as alterações feitas ao elemento nas linhas 10–12. É importante ter isto em conta ao depurar JavaScript. Nesses casos, muitas vezes não é necessário visualizar o código-fonte da página apresentada.
21.2.5. O código JavaScript da aplicação
Voltemos ao código HTML da página da aplicação 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">
<!-- method 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 />
<!-- posted value -->
<br /> Chaîne jSON à poster : <input type="text" id="posted" size="50" />
<!-- validation button -->
<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: importamos o código que iremos escrever;
- Linhas 11, 15, 17, 21: repare nos identificadores [id] dos componentes da página. O JavaScript faz referência a estes componentes através destes identificadores;
O código [client.js] é o seguinte:
// global data
var url;
var posted;
var response;
var method;
function requestServer() {
// retrieve information from the form
var urlValue = url.val();
var postedValue = posted.val();
method = document.forms[0].elements['method'].value;
// make a manual Ajax call
if (method === "get") {
doGet(urlValue);
} else {
doPost(urlValue, postedValue);
}
}
function doGet(url) {
// make a manual Ajax call
$.ajax({
headers : {
'Authorization' : 'Basic YWRtaW46YWRtaW4='
},
url : 'http://localhost:8081' + url,
type : 'GET',
dataType : 'tex/plain',
beforeSend : function() {
},
success : function(data) {
// text result
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// system error
response.text(jqXHR.responseText);
}
})
}
function doPost(url, posted) {
// make a manual Ajax call
$.ajax({
headers : {
'Authorization' : 'Basic YWRtaW46YWRtaW4='
},
url : 'http://localhost:8081 ' + url,
type : 'POST',
contentType : 'application/json',
data : posted,
dataType : 'tex/plain',
beforeSend : function() {
},
success : function(data) {
// text result
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// system error
response.text(jqXHR.responseText);
}
})
}
// document loading
$(document).ready(function() {
// retrieve page component references
url = $("#url");
posted = $("#posted");
response = $("#response");
});
- linhas 71–75: código JavaScript executado após o documento ter terminado de carregar no navegador;
- linhas 73-75: recupera as referências de três elementos no documento HTML;
- linhas 2-5: variáveis globais conhecidas em todas as funções definidas no ficheiro JavaScript;
- linha 9: recupera o URL introduzido pelo utilizador;
- linha 10: recupera o valor que o utilizador pretende enviar;
- linha 11: recupera o método [get] ou [post] a utilizar ao solicitar a URL da linha 9:
- document refere-se ao documento carregado pelo navegador, conhecido como DOM (Document Object Model),
- document.forms[0] refere-se ao primeiro formulário no documento; um documento pode conter vários formulários. Aqui, existe apenas um;
- document.forms[0].elements['method'] refere-se ao elemento do formulário com 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 com o atributo [name='method']. Sabemos que o valor enviado é o valor do atributo [value] do botão de opção selecionado. Aqui, será, portanto, uma das cadeias de caracteres ['get', 'post'];
- Linhas 13–18: Dependendo do método HTTP a ser utilizado, é executado o método [doGet] ou [doPost];
- O método jQuery [$.ajax] efetua um pedido HTTP;
- linhas 23–25: Estamos a comunicar com um servidor que requer um cabeçalho HTTP [Authorization: Basic code]. Criamos este cabeçalho para o utilizador [admin / admin], que é o único autorizado a consultar o servidor;
- linha 26: o utilizador irá introduzir URLs do tipo [/getAllLongCategories, /saveCategories, ...]. Estas URLs devem, portanto, ser preenchidas;
- linha 27: método HTTP a utilizar;
- linha 28: o servidor devolve JSON. Especificamos o tipo [text/plain] como tipo de resposta para que seja apresentado exatamente como recebido;
- linha 33: exibir a resposta de texto do servidor;
- linha 39: exibe qualquer mensagem de erro em 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
A aplicação cliente é uma aplicação de consola lançada 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 irá inspecionar os arquivos presentes no classpath do projeto. Neste caso, serão todas as dependências Maven fornecidas pela única dependência no ficheiro [pom.xml]:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Esta dependência inclui um grande número de bibliotecas, nomeadamente o Spring MVC e um servidor Tomcat. Devido a estas dependências, o Spring Boot irá configurar um projeto Spring MVC a ser executado no Tomcat utilizando valores predefinidos. O servidor Tomcat é então configurado para funcionar na porta 8080. Se pretender substituir os valores predefinidos escolhidos pelo Spring Boot, pode utilizar o ficheiro [application.properties] na raiz do classpath (tudo o que se encontra em [src/main/resources] está na raiz do classpath):
![]() |
Especificamos que o servidor Tomcat deve ser executado na porta 8082 da seguinte forma:
server.port=8082
Uma lista de parâmetros que podem ser utilizados no ficheiro [application.properties] está disponível na URL (junho de 2015) [http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html];
De volta ao código em [Client.java]:
- linha 10: o método [SpringApplication.run] irá implementar a página [client.html] no servidor Tomcat presente no classpath do projeto;
21.2.7. A URL [/getAllShortCategories]
![]() |
Iniciamos:
- o servidor web/JSON seguro na porta 8081 (configuração [spring-security-server-jdbc-generic]);
- o cliente para este servidor na porta 8082 (configuração [spring-cors-client-generic]);
depois solicitamos a URL [http://localhost:8082/client.html] [1]:
![]() |
- em [2], efetuamos uma solicitação GET na URL [http://localhost:8081/getAllShortCategories];
Não recebemos uma resposta do servidor. Quando consultamos a consola de programadores do Chrome (Ctrl-Shift-I), vemos um erro:
![]() |
- em [1], estamos no separador [Rede];
- Em [2], vemos que o pedido HTTP efetuado não é [GET], mas sim [OPTIONS]. No caso de um pedido entre domínios, o navegador verifica junto do servidor se determinadas condições estão preenchidas, enviando um pedido HTTP [OPTIONS]. Neste caso, os pedidos são aqueles indicados pelos pontos [5-6];
- Em [5], o navegador pergunta se o URL de destino pode ser alcançado com um GET. A solicitação [Access-Control-Request-Method] pede uma resposta com um cabeçalho HTTP [Access-Control-Allow-Methods] indicando que o método solicitado é aceito;
- em [6], o navegador envia o cabeçalho HTTP [Origin: http://localhost:8081]. Este cabeçalho solicita uma resposta num cabeçalho HTTP [Access-Control-Allow-Origin] indicando que a origem especificada é aceite;
- Em [7], o navegador pergunta se os cabeçalhos HTTP [Accept] e [Authorization] são aceites. A solicitação [Access-Control-Request-Headers] espera uma resposta com um cabeçalho HTTP [Access-Control-Allow-Headers] indicando que os cabeçalhos solicitados são aceites;
- ocorre um erro em [3]. Clicar no ícone resulta no erro [4];
- Em [4], a mensagem indica que o servidor não enviou o cabeçalho HTTP [Access-Control-Allow-Origin], que especifica se a origem da solicitação é aceita;
- em [8], podemos ver que o servidor, de facto, não enviou este cabeçalho. Como resultado, o navegador recusou-se a efetuar a solicitação HTTP GET que foi inicialmente solicitada;
Precisamos de modificar o servidor web / JSON.
21.2.8. Um novo serviço web / JSON
Estamos a criar 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>
<!-- plugins -->
<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: Recuperamos todos os dados do trabalho realizado até ao momento, acedendo ao arquivo JSON seguro no servidor web;
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 {
// cross-domain queries
@Bean
public boolean isCorsEnabled() {
return true;
}
...
}
- linha 12: a classe é uma classe de configuração Spring;
- linha 9: outros componentes Spring podem ser encontrados no pacote [spring.cors.server.service];
- linha 14: importamos os beans do projeto [spring-security-server-jdbc-generic];
- linhas 18–21: criamos um componente Spring chamado [isCorsEnabled] que indica se clientes fora do domínio do servidor são aceites ou não;
21.2.9. Os controladores
O novo serviço web tem quatro controladores:
![]() |
- O [CorsCategorieController] trata das URLs de processamento de categorias. Ele apenas gere os cabeçalhos CORS provenientes de clientes web. Caso contrário, delega o trabalho ao [CategorieController] na dependência [spring-webjson-server-jdbc-generic];
- [CorsProductController] e [CorsAuthenticateController] fazem o mesmo, delegando o trabalho ao [ProductController] na dependência [spring-webjson-server-jdbc-generic] e ao [AuthenticateController] na dependência [spring-security-server-jdbc-generic];
- O [CorsController] é utilizado para extrair o que é comum aos três controladores anteriores;
21.2.9.1. O [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;
// sending options to the customer
public void sendOptions(String origin, HttpServletResponse response) {
// Cors allowed ?
if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
return;
}
// set header CORS
response.addHeader("Access-Control-Allow-Origin", origin);
// certain headers are allowed
response.addHeader("Access-Control-Allow-Headers", "accept, authorization");
// we authorize 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 deve ou não tratar cabeçalhos CORS;
- linhas 15–26: o método [sendOptions] trata as respostas aos clientes que enviam cabeçalhos CORS;
- linhas 17-19: se a aplicação estiver configurada para aceitar pedidos entre domínios, e se o remetente tiver enviado o cabeçalho HTTP [Origin], e se essa origem começar por [http://localhost], então o pedido entre domínios é aceite; caso contrário, é rejeitado;
- linha 21: se o cliente estiver no domínio [http://localhost:port], enviamos o cabeçalho HTTP:
Access-Control-Allow-Origin: http://localhost:port
o que significa que o servidor aceita a origem do cliente;
- linhas 22–25: especificámos dois cabeçalhos HTTP específicos na solicitação HTTP [OPTIONS]:
Em resposta ao cabeçalho HTTP [Access-Control-Request-X], o servidor responde com um cabeçalho HTTP [Access-Control-Allow-X] especificando o que é permitido. As linhas 22–25 simplesmente repetem o pedido do cliente para indicar que este foi aceite;
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) {
// original method
return categorieController.getAllShortCategories();
}
...
}
- linha 19: a anotação [@RestController] torna a classe tanto um componente Spring como um controlador MVC que envia as suas próprias respostas ao cliente;
- Linha 20: A classe [CorsCategorieController] estende a classe [CorsController] que acabámos de ver;
- linhas 22–23: injeção do controlador [CategorieController] a partir da dependência [spring-webjson-server-jdbc-generic];
- linhas 25–29: tratam a URL [/cors-getAllShortCategories] quando esta é solicitada com o método HTTP [OPTIONS]. Por convenção, decidimos que os clientes web que pretendam chamar a URL [/U] do serviço web seguro devem, na verdade, chamar a URL [/cors-U]. O serviço web implementado terá, assim, dois tipos de URLs:
- [/U]: para clientes não web;
- [/cors-U]: para clientes web;
- linha 25: o método [/cors-getAllShortCategories] aceita os seguintes parâmetros:
- o objeto [@RequestHeader(value = "Origin", required = false)], que recupera o cabeçalho HTTP [Origin] da solicitação. Este cabeçalho foi enviado pela origem da solicitação:
Especificamos que o cabeçalho HTTP [Origin] é opcional [required = false]. Neste caso, se o cabeçalho estiver em falta, o parâmetro [String origin] terá um valor nulo. Com [required = true], que é o valor padrão, é lançada uma exceção se o cabeçalho estiver em falta. Queríamos evitar este cenário;
- (continuação)
- o objeto [HttpServletResponse response] que será devolvido ao cliente que efetuou o pedido;
Estes dois parâmetros são injetados pelo Spring;
- linha 28: delegamos o tratamento da solicitação ao método [sendOptions] da classe pai [CorsController];
- linhas 31–36: o método [getAllShortCategories] trata da URL [/cors-getAllShortCategories] quando esta é solicitada com um GET;
- Linha 35: O trabalho é delegado ao método [CategorieController.getAllShortCategories] da dependência [spring-webjson-server-jdbc-generic];
Estamos agora prontos para realizar mais testes. Lançamos a nova versão do serviço web e verificamos que o problema permanece inalterado. Nada mudou. Se adicionarmos uma saída de consola na linha 28 acima, esta nunca é apresentada, o que indica que o método [corsGetAllShortCategories] na linha 25 nunca é chamado.
Após alguma pesquisa, descobrimos que o Spring MVC lida com os pedidos HTTP [OPTIONS] por si próprio, utilizando o tratamento padrão. Por conseguinte, é sempre o Spring que responde, e nunca o método [corsGetAllShortCategories] na linha 25. Este 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 {
// cross-domain queries
@Bean
public boolean isCorsEnabled() {
return true;
}
@Autowired
private DispatcherServlet dispatcherServlet;
@PostConstruct
public void init(){
// the application processes requests itself 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] garante que o método [init] será executado após a instância da classe [AppConfig] e após o Spring ter realizado as suas injeções;
- linha 29: configuramos o servlet para reencaminhar os pedidos HTTP [OPTIONS] para a aplicação;
Executamos novamente os testes com esta nova configuração. Obtemos o seguinte resultado:
![]() |
- em [1], vemos que existem duas solicitações HTTP para a URL [http://localhost:8080/getAllCategories];
- em [2], a solicitação [OPTIONS];
- em [3], os três cabeçalhos HTTP que acabámos de configurar na resposta do servidor;
Vamos agora examinar a segunda alegação:
![]() |
- em [1], o pedido em questão;
- em [2], trata-se da solicitação GET. Graças à primeira solicitação [OPTIONS], o navegador recebeu as informações que solicitou. Agora, ele executa a solicitação [GET] que foi inicialmente solicitada;
- 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 do servidor [3] é normal [HTTP/1.1 200 OK]. Devemos, portanto, ter o documento solicitado. É possível que o servidor tenha de facto enviado o documento , mas que o navegador esteja a impedir a sua utilização porque exige que, também para o pedido GET, a resposta inclua o cabeçalho HTTP [Access-Control-Allow-Origin:http://localhost:8081].
Modificamos o método que trata da solicitação GET para a URL [/cors-getAllShortCategories]:
@RequestMapping(value = "/cors-getAllShortCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllShortCategories(
@RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse response) {
// headers CORS
sendOptions(origin, response);
// original method
return categorieController.getAllShortCategories();
}
- linha 5: tal como na solicitação HTTP [OPTIONS], o servidor enviará os cabeçalhos HTTP CORS para uma solicitação HTTP [GET];
Após esta alteração, os resultados são os seguintes:
![]() |
Conseguimos obter a versão curta de todas as categorias.
21.2.9.3. As URLs [GET]
Nos controladores [CorsCategorieController, CorsProduitController, CorsAuthenticateController], o código das ações que tratam das URLs [GET] solicitadas segue o padrão das ações que anteriormente tratavam da URL [/cors-getAllShortArticles]. O leitor pode verificar o código nos exemplos fornecidos com este documento. Aqui está um exemplo para a URL [/cors-getAllLongProduits] no 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) {
// headers CORS
sendOptions(origin, response);
// original method
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. URLs [POST]
Vamos considerar o seguinte cenário:
![]() |
- Fazemos um POST [1] para a URL [2];
- em [3], o valor enviado. Esta é a string JSON para uma categoria sem produtos;
- Em última análise, queremos criar uma categoria chamada [category[2]];
Não estamos a modificar nenhum código nesta fase. O resultado obtido é o seguinte:
![]() |
- em [1], tal como nas solicitações [GET], o navegador efetua uma solicitação [OPTIONS];
- em [2], solicita autorização de acesso para uma solicitação [POST]. Anteriormente, era [GET];
- Em [3], solicita autorização para enviar os cabeçalhos HTTP [accept, authorization, content-type]. Anteriormente, só tínhamos os dois primeiros cabeçalhos;
- em [4], o serviço web não concede todas as permissões solicitadas, o que causa o erro [5];
Modificamos o método [CorsController.sendOptions] da seguinte forma:
public void sendOptions(String origin, HttpServletResponse response) {
// Cors allowed ?
if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
return;
}
// set header CORS
response.addHeader("Access-Control-Allow-Origin", origin);
// certain headers are allowed
response.addHeader("Access-Control-Allow-Headers", "accept, authorization, content-type");
// we authorize GET and POST
response.addHeader("Access-Control-Allow-Methods", "GET, POST");
}
}
- linha 9: adicionámos o cabeçalho HTTP [Content-Type] (não distingue maiúsculas de minúsculas);
- linha 11: adicionámos o método HTTP [POST];
Isto significa que os métodos [POST] são tratados da mesma forma que os pedidos [GET]. Aqui está um exemplo da URL [/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) {
// headers CORS
sendOptions(origin, response);
// original method
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);
}
Com estas alterações feitas, o resultado é o seguinte:
![]() |
A categoria [categorie[2]] foi adicionada com sucesso à base de dados. O SGBD atribuiu-lhe a chave primária 226. Isto pode ser verificado utilizando o método GET [/cors-getAllShortCategories]:
![]() |
21.2.10. Conclusão
A nossa aplicação suporta agora pedidos entre domínios. Estes podem ser ativados ou desativados através 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 {
// cross-domain queries
@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], o qual gere o acesso à base de dados utilizando o Spring Data JPA:
![]() |
O projeto [spring-cors-server-jpa-generic] é criado através da clonagem do projeto [spring-cors-server-jdbc-generic] anteriormente estudado.
![]() |
A seguir, há duas alterações a fazer. A primeira é no ficheiro [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>
<!-- plugins -->
<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:
![]() |
Nota: Prima Alt-F5 e, em seguida, regenera todos os projetos
A segunda alteração consiste em atualizar as importações nas classes que estão a apresentar erros [Alt-Shift-O].
É isso. Iniciamos o serviço web CORS com a configuração de tempo de execução [spring-cors-server-jpa-generic-hibernate-eclipselink]:
![]() | ![]() |
Em seguida, inicie o cliente genérico:
![]() |
e, utilizando um navegador, solicite o URL [1] com um GET. Em [2], vemos que a versão resumida das categorias devolvidas tem o campo [entityType], que faltava na versão JDBC anterior.
Vamos agora abordar duas outras arquiteturas CORS:
- Arquitetura CORS / JPA EclipseLink / DB2;
- Arquitetura CORS / JPA EclipseLink / Firebird;
21.4. Arquitetura CORS / JPA EclipseLink / DB2
Iremos implementar a seguinte arquitetura:
![]() |
Carregue os seguintes projetos:
![]() |
Nota: Prima Alt-F5 e regenera todos os projetos Maven.
Inicie a base de dados DB2 e verifique se a base de dados [dbproduitscategories] existe. Caso contrário, crie-a (secção 12.1.2).
Criamos utilizadores na base de dados [dbproduitscategories] utilizando a configuração de tempo de execução [spring-security-create-users-hibernate-eclipselink]:
![]() | ![]() |

Em seguida, inicie o serviço web CORS utilizando a configuração de tempo de execução denominada [spring-cors-server-jpa-generic-hibernate-eclipselink] e o seu cliente denominado [spring-cors-client-generic]:
![]() | ![]() |
Preencha a base de dados [dbproduitscategories] com valores utilizando a configuração de tempo de execução [spring-jdbc-generic-04-fillDataBase]:
![]() |
Por fim, aceda à seguinte URL num navegador:
![]() |
21.5. CORS / JPA OpenJPA / Arquitetura Firebird
Vamos agora implementar a seguinte arquitetura:
![]() |
Carregue os seguintes projetos:
![]() |
Nota: Prima Alt-F5 e regenera todos os projetos Maven.
Inicie o SGBD Firebird e verifique se o banco de dados [dbproduitscategories] existe. Caso contrário, crie-o (secção 14.1.2).
Criamos utilizadores na base de dados [dbproduitscategories] utilizando a configuração de tempo de execução [spring-security-create-users-openjpa]:
![]() | ![]() |

Em seguida, inicie o serviço web CORS com a configuração de tempo de execução denominada [spring-cors-server-jpa-generic-openjpa]:
![]() | ![]() |
Inicie o cliente CORS utilizando a configuração [spring-cors-client-generic]:
![]() |
Preencha a base de dados [dbproduitscategories] com valores utilizando a configuração de tempo de execução [spring-jdbc-generic-04-fillDataBase]:
![]() |
Por fim, aceda à seguinte URL num navegador:
![]() |


























































