18. [Cours]: Gestão de acessos entre domínios
Palavras-chave: CORS (Cross-Origin Resource Sharing).
Este capítulo afasta-se um pouco do TD. Foi mantido porque introduz a programação web e a programação em JavaScript. É importante recordar aqui que um dos objetivos deste TD é apresentar os conceitos frequentemente utilizados no desenvolvimento JEE, ou seja, o desenvolvimento web baseado em frameworks Java. Completa-se aqui o servidor web utilizado no estudo da base de dados de produtos e categorias, para que este possa aceitar pedidos entre domínios.
No documento [Tutoriel AngularJS / Spring 4], desenvolve-se 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, isso é proibido pelo navegador que executa a aplicação Angular, porque constitui uma falha 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, a aplicação consulta-o para perguntar se este autoriza um cliente que não provém do seu próprio domínio a consultá-lo. A esta técnica de partilha chama-se CORS (Cross-Origin Resource Sharing). O servidor [2] dá o seu consentimento enviando cabeçalhos HTTP específicos.
Vamos criar 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];
18.1. Support
![]() |
Os projetos deste capítulo encontram-se na pasta [support / chap-18].
18.2. O projeto do cliente
Criamos o seguinte projeto no Eclipse:
![]() |
18.3. Configuração do Maven
O projeto é um projeto Maven com o seguinte 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>istia.st.webjson</groupId>
<artifactId>intro-server-webjson-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>intro-server-webjson-01</name>
<description>démo spring mvc</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>istia.st.springdata</groupId>
<artifactId>intro-spring-data-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- linhas 11-15: trata-se de um projeto Spring Boot;
- linhas 23-26: utiliza-se a dependência [spring-boot-starter-web], que inclui um servidor Tomcat e o Spring MVC;
18.4. Configuração do Spring
![]() |
A classe [WebConfig] que configura o projeto Spring é a seguinte:
package spring.cors.client.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// -------------------------------- configuração da camada [web]
@Autowired
private ApplicationContext context;
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet servlet = new DispatcherServlet((WebApplicationContext) context);
return servlet;
}
@Bean
public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new ServletRegistrationBean(dispatcherServlet, "/*");
}
@Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory("", 8081);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/*.html").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/*.js").addResourceLocations("classpath:/static/js/");
}
}
- linha 15: a classe configura um projeto Spring MVC;
- linha 16: a classe estende a classe [WebMvcConfigurerAdapter] para redefinir alguns dos seus métodos;
- linhas 18-36: já nos deparámos com estes beans, por exemplo, no parágrafo 13.5.3.1. Note-se, na linha 35, que o serviço web funcionará na porta 8081;
- linhas 38-42: o método [addResourceHandlers] permite definir recursos estáticos, ou seja, recursos não processados pelo [DispatcherServlet] da linha 23;
- linha 40: qualquer pedido de um recurso com a extensão .html terá como resposta o ficheiro solicitado pelo pedido e encontrado na pasta [static] do Classpath do projeto;
- linha 41: qualquer pedido de um recurso com a extensão .js terá como resposta o ficheiro JavaScript solicitado pelo pedido e encontrado na pasta [static/js] do Classpath do projeto;
![]() |
18.5. Noções básicas sobre jQuery e JavaScript
A página HTML do cliente será a seguinte:
![]() |
Esta página incluirá código JavaScript (jS) executado no navegador. Vamos apresentar alguns conceitos básicos de JavaScript que nos permitirão compreender o código. O cliente irá efetuar chamadas HTTP utilizando a biblioteca jQuery [https://jquery.com/], que disponibiliza inúmeras funções que facilitam o desenvolvimento em JavaScript. Criamos um ficheiro estático HTML [jQuery.html] que colocamos 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="/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 experimentar com este elemento.
Temos de descarregar o ficheiro [jquery-2.1.3.min.js]. A versão mais recente do jQuery encontra-se no URL [http://jquery.com/download/]:

Colocaremos o ficheiro descarregado na pasta [static / js] e alteraremos a linha 6 do ficheiro HTML de acordo com a versão instalada.
Feito isto, acedemos à vista estática [jQuery.html] com o Chrome [1-2]:
![]() |
No Google Chrome, execute o comando [Ctrl-Maj-I] para aceder às ferramentas de desenvolvimento [3]. O separador [Console] [4] permite executar código JavaScript. Apresentamos a seguir alguns comandos JavaScript a introduzir, acompanhados de uma explicação.
|
: torna a coleção de todos os elementos com o id [element1], ou seja, 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 tem como efeito alterar o conteúdo exibido pela página | ![]() |
|
oculta os elementos da coleção. O texto [blabla] já não é apresentado. | ![]() |
|
: volta a exibir a coleção. Isto permite-nos permite ver que o elemento com o ID [element1] tem o atributo CSS style='display: none;' que faz que o elemento fique oculto. | |
|
: exibe os elementos da coleção. O texto [blabla] volta a aparecer. É o atributo CSS style='display: block;' que garante esta exibição. | ![]() |
|
: define um atributo para todos os elementos da coleção. O atributo é, neste caso, [style] e o seu valor [color: red]. O texto [blabla] fica a vermelho. | ![]() |
![]() | |
![]() |
Note-se que o URL do navegador não se alterou durante todas estas operações. Não houve qualquer comunicação com o servidor web. Tudo decorre no interior do navegador. Agora, vamos visualizar o código-fonte da página:
![]() | ![]() |
Este é o texto inicial. Não reflete de forma alguma as alterações que fizemos no elemento nas linhas 10 a 12. É importante ter isto em conta ao depurar JavaScript. Por isso, muitas vezes é inútil visualizar o código-fonte da página apresentada.
18.6. O código JavaScript da aplicação
Voltemos à página da aplicação cliente que irá consultar o serviço web / jSON:
![]() |
![]() |
O código HTML desta página é o seguinte:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Spring MVC</title>
<script type="text/javascript" src="/jquery-2.1.3.min.js"></script>
<script type="text/javascript" src="/client.js"></script>
</head>
<body>
<h2>Client du service web / jSON</h2>
<form id="formulaire">
<!-- identificador -->
Identifiant :
<!-- -->
<input type="text" id="identifiant" name="identifiant" value="" />
<!-- palavra-passe -->
<br /> <br /> Mot de passe :
<!-- -->
<input type="text" id="password" name="password" value="" />
<!-- método HTTP -->
<br /> <br /> 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 (commençant par /): <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 validação -->
<br /> <br /> <input type="button" value="Valider"
onclick="javascript:requestServer()"></input>
</form>
<hr />
<h2>Réponse du serveur</h2>
<div id="response"></div>
</body>
</html>
- linha 6: importa-se a biblioteca jQuery;
- linha 7: importa-se um código que iremos escrever;
- linhas 15, 19, 26, 29, 31: anotamos os 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:
// dados globais
var url;
var posted;
var response;
var method;
var baseUrl = 'http://localhost:8080';
var identifiant;
var password;
var authorizationHeader;
function requestServer() {
// recuperam-se as informações
var urlValue = url.val();
var postedValue = posted.val();
var identifiantValue = identifiant.val();
var passwordValue = password.val();
var method = document.forms[0].elements['method'].value;
authorizationCode = btoa(identifiantValue + ':' + passwordValue);
// apaga-se a resposta anterior
response.text("");
// faz-se uma chamada Ajax manualmente
if (method === "get") {
doGet(urlValue);
} else {
doPost(urlValue, postedValue);
}
}
function doGet(url) {
// efetua-se uma chamada Ajax manualmente
$.ajax({
headers : {
'Autorização: 'Basic '+authorizationCode
},
url : baseUrl + url,
type : 'GET',
dataType : 'text',
beforeSend : function() {
},
success : function(data) {
// resultado em texto
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// erro de sistema
response.text(JSON.stringify(jqXHR.statusCode()));
}
})
}
function doPost(url, posted) {
// está a ser efetuada uma chamada Ajax manualmente
$.ajax({
headers : {
'Autorização':'Basic '+authorizationCode
},
url : baseUrl + url,
type : 'POST',
contentType : 'application/json; charset=UTF-8',
data : posted,
dataType : 'text',
beforeSend : function() {
},
success : function(data) {
// resultado de texto
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// erro do sistema
response.text(JSON.stringify(jqXHR.statusCode()));
}
})
}
// ao carregar o documento
$(document).ready(function() {
// recuperam-se as referências dos componentes da página
identifiant = $("#identifiant");
password = $("#password");
url = $("#url");
posted = $("#posted");
response = $("#response");
});
- linhas 80-87: do código jS executado no final do carregamento do documento no navegador;
- linhas 81-86: recuperam-se as referências dos diferentes elementos do documento HTML, através do seu identificador [id];
- linhas 2-9: variáveis globais conhecidas em todas as funções definidas no ficheiro jS;
- linha 13: recupera-se o valor URL introduzido pelo utilizador;
- linha 14: recupera-se o valor que o utilizador pretende enviar (vazio se for a operação GET);
- linha 15: recupera-se o identificador introduzido pelo utilizador;
- linha 16: recupera-se a sua palavra-passe;
- linha 17: recupera-se o método [get] ou [post] a utilizar para solicitar o URL da linha 9:
- [document] designa o documento carregado pelo navegador, o que se denomina DOM (Document Object Model),
- [document.forms[0]] designa o primeiro formulário do documento, podendo um documento conter vários. Neste caso, existe apenas um,
- [document.forms[0].elements['method']] designa o 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 selecionado. Neste caso, será, portanto, uma das cadeias ['get', 'post'];
- linha 18: constrói-se a codificação Base74 da cadeia «identifiant:password». Esta cadeia codificada será utilizada no cabeçalho HTTP [Authorization] que iremos enviar ao servidor para autenticar o pedido;
- linhas 22-26: dependendo do método HTTP a utilizar, executa-se o método [doGet] ou [doPost];
- o método jQuery [$.ajax] efetua uma chamada ao método HTTP;
- linhas 32-34: contacta-se um servidor que exige um cabeçalho HTTP ou [Authorization: Basic code];
- linha 35: o utilizador irá introduzir URL do tipo [/cors-getAllCategories,/cors-addProduits, ...]. Por isso, é necessário completar estes URL com o URL do servidor da linha 6;
- linha 36: método HTTP a utilizar;
- linha 37: o servidor devolve o jSON. Indica-se o tipo [text] como tipo de resultado, para que seja apresentado tal como foi recebido;
- linha 42: exibição da resposta de texto do servidor;
- linhas 48-49: exibição de uma eventual mensagem de erro;
- linha 53: o método [doPost] recebe um segundo parâmetro, que é o valor a enviar;
- linha 61: para indicar que o valor enviado será na forma de uma cadeia jSON;
18.7. Execução do cliente
A aplicação cliente é uma aplicação Spring Boot iniciada pela seguinte classe executável [Boot]:
![]() |
package spring.cors.client.boot;
import org.springframework.boot.SpringApplication;
import spring.cors.client.config.WebConfig;
public class Boot {
public static void main(String[] args) {
SpringApplication.run(WebConfig.class, args);
}
}
- linha 10: o método [SpringApplication.run] utiliza o ficheiro de configuração [WebConfig]. A página [client.html] será implementada no servidor Tomcat presente no Classpath do projeto;
18.8. O URL [/getAllCategories]
Estamos a lançar:
- o servidor web/json na porta 8080;
- o cliente deste servidor na porta 8081;
depois solicitamos o URL [http://localhost:8081/client.html] [1]:
![]() |
- em [2], fazemos um GET no URL [http://localhost:8080/getAllCategories];
Não obtemos resposta do servidor. Ao consultar a consola de desenvolvimento do Chrome (Ctrl+Shift+I), descobrimos um erro:
![]() |
- em [1], estamos no separador [Network];
- em [2], verifica-se que a solicitação HTTP que foi efetuada não é [GET], mas sim [OPTIONS]. No caso de um pedido entre domínios, o navegador verifica junto do servidor se um determinado número de condições está preenchido, enviando-lhe um pedido HTTP [OPTIONS]. Neste caso, as solicitações são as indicadas pelos marcadores [5-6];
- em [5], o navegador pergunta se o destino URL pode ser alcançado com um GET. O cabeçalho da solicitação [Access-Control-Request-Method] solicita uma resposta com um cabeçalho HTTP [Access-Control-Allow-Methods] indicando que o método solicitado é aceite;
- 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 indicada é aceite;
- em [7], o navegador pergunta se os cabeçalhos HTTP, [accept] e [authorization] são aceites. O cabeçalho do pedido [Access-Control-Request-Headers] aguarda 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]. Ao clicar no ícone, surge 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 do pedido é aceite;
- em [8], verifica-se que o servidor efetivamente não enviou esse cabeçalho. Consequentemente, o navegador recusou-se a efetuar a solicitação HTTP GET inicialmente solicitada;
Temos de alterar o servidor web / jSON.
18.9. O novo serviço web / json
Criamos um novo projeto Maven [intro-spring-cors-server-jpa]:
![]() | ![]() |
18.9.1. Configuração do Maven
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>istia.st.cors</groupId>
<artifactId>spring-cors-server-jpa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cors-server-jpa</name>
<description>démo spring cors</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>istia.st.spring.security</groupId>
<artifactId>intro-spring-security-server-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- plugins -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- linhas 23-27: aproveitamos todo o trabalho realizado até agora, recorrendo ao arquivo do servidor web / json seguro;
18.9.2. Configuração do Spring
A classe de configuração [AppConfig] é a seguinte:
![]() |
package spring.cors.server.config;
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 spring.security.config.SecurityConfig;
@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ SecurityConfig.class })
public class AppConfig {
// solicitações entre domínios
@Bean
public boolean isCorsEnabled() {
return true;
}
}
- linha 10: a classe é uma classe de configuração do Spring;
- linha 11: outros componentes Spring devem ser procurados no pacote [spring.cors.server.service];
- linhas 16-19: criamos um componente Spring denominado [isCorsEnabled] que indica se aceitamos ou não clientes externos ao domínio do servidor;
18.9.3. A classe [AbstractCorsController]
A classe [AbstractCorsController], que será a classe pai de todos os controladores desta aplicação:
![]() |
O seu código é o seguinte:
package spring.cors.server.service;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class AbstractCorsController {
@Autowired
private boolean isCorsEnabled;
// envio das opções para o cliente
public void setHeaders(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);
// autorizam-se determinados cabeçalhos
response.addHeader("Access-Control-Allow-Headers", "accept, authorization");
// autoriza-se o GET
response.addHeader("Access-Control-Allow-Methods", "GET");
}
}
- linha 7: a classe [CorsController] é abstrata, pois foi concebida para ser estendida e não instanciada;
- linhas 13-24: o método [setHeaders] insere na resposta [HttpServletResponse response] (linha 13) enviada ao cliente os cabeçalhos HTTP exigidos pelas solicitações entre domínios;
- linha 33: o método [/setHeaders] aceita como parâmetros:
- a cadeia [origin] presente nos cabeçalhos HTTP e [Origin] das solicitações entre domínios:
Neste caso, o parâmetro [origin] da linha 13 teria o valor [http://localhost:8081]. Caso a solicitação não contenha o cabeçalho HTTP [Origin], providenciar-se-á para que se tenha [origin==null];
- (continuação)
- o objeto [HttpServletResponse response] que será devolvido ao cliente que efetuou o pedido;
Estes dois parâmetros são inseridos pelo Spring;
- linhas 15-175: 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 aceitar-se-á a solicitação entre domínios; caso contrário, rejeitar-se-á;
- linha 19: se o cliente estiver no domínio [http://localhost:port], enviamos o cabeçalho HTTP:
o que significa que o servidor aceita a origem do cliente;
- linha 21: indicámos dois cabeçalhos HTTP específicos na solicitação HTTP [OPTIONS]:
Em resposta aos 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 20-23 limitam-se a repetir o pedido do cliente para indicar que este foi aceite;
18.9.4. O controlador [MyControllerWithHttpOptions]
Para não ter de alterar o servidor web não seguro / jSON [intro-server-webjson-01] analisado no parágrafo 13.5.3, vamos criar um novo controlador que, nos casos em que o servidor não seguro processa o URL [/url], o novo controlador processará os URL e [/cors-url], e este URL aceitará os pedidos entre domínios.
A classe [MyControllerWithHttpOptions] é o controlador que irá processar os pedidos HTTP do tipo [OPTIONS]:
![]() |
package spring.cors.server.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.fasterxml.jackson.core.JsonProcessingException;
@Controller
public class MyControllerWithHttpOptions extends AbstractCorsController {
@RequestMapping(value = "/cors-getAllCategories", method = RequestMethod.OPTIONS)
public void getAllCategories(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse){
// cabeçalhos CORS
setHeaders(origin, httpServletResponse);
}
...
- linha 14: a classe é um controlador Spring MVC;
- linha 15: a classe [MyControllerWithHttpOptions] estende a classe [AbstractCorsController] que acabámos de descrever;
- linhas 17-18: o método [getAllCategories] (linha 18) processa o URL ["/cors-getAllCategories"] quando é solicitado com o método HTTP [OPTIONS];
- linha 18: o método [getAllCategories] aceita dois parâmetros:
- [@RequestHeader(value = "Origin", required = false) String origin] para recuperar o valor do cabeçalho HTTP [Origin:http://localhost:8081], quando este estiver presente. Neste exemplo, o parâmetro [String origin] receberá o valor [http://localhost:8081]. Este cabeçalho não é obrigatório [required = false]. Quando não estiver presente, o parâmetro [String origin] terá o valor null;
- [HttpServletResponse httpServletResponse]: a resposta que será enviada ao cliente;
- linha 21: enviam-se os cabeçalhos HTTP que permitem as solicitações entre domínios. O método [setHeaders] está definido na classe pai [AbstractCorsController];
O mesmo se aplica a todos os URL expostos pelo servidor web / jSON não seguro [intro-server-webjson-01] analisado no parágrafo 13.5.3. Quando este serviço expõe o URL e o [/url], a classe [MyControllerWithHttpOptions] acima referida expõe o URL e o [/cors-url].
18.9.5. O controlador [MyControllerWithCors]
![]() |
A classe [MyControllerWithCors] é o controlador que irá processar as solicitações HTTP dos tipos [GET] e [POST]:
package spring.cors.server.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
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.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import spring.webjson.service.MyController;
@Controller
public class MyControllerWithCors extends AbstractCorsController {
// dependências do Spring
@Autowired
private MyController myController;
...
@RequestMapping(value = "/cors-getAllCategories", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getAllCategories(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse) throws JsonProcessingException {
// resposta
return myController.getAllCategories();
}
...
- linha 17: a classe [MyControllerWithCors] é um controlador Spring MVC
- linha 18: estende a classe [AbstractCorsController];
- linhas 21-22: injeção do controlador [MyController] do servidor web / jSON não seguro [intro-server-webjson-01] analisado no parágrafo 13.5.3;
- linhas 25-27: o método [getAllCategories] processa o URL [/cors-getAllCategories] (linha 28) quando solicitado através do método HTTP [GET];
- linha 26: o resultado do método [getAllCategories] será enviado ao cliente. Este resultado é um fluxo jSON (atributo [produces] da linha 27 e tipo [String] do resultado da linha 25);
- linha 27: o método recebe os mesmos parâmetros que o método [getAllCategories] do controlador [MyControllerWithHttpOptions] que acabámos de analisar;
- linha 30: solicita-se ao método [myController.getAllCategories()] que envie a resposta;
No final, é o método [myController.getAllCategories()] do servidor não seguro que envia a resposta. Simplesmente enriquecemos a sua resposta com os cabeçalhos necessários para as solicitações entre domínios.
Isto é feito para todos os URL expostos pelo servidor web / jSON não seguro [intro-server-webjson-01] analisado no parágrafo 13.5.3. Quando este serviço expõe o URL e o [/url], a classe [MyControllerWithCors] acima referida expõe o URL e o [/cors-url].
Uma solicitação entre domínios decorrerá da seguinte forma:
- o código JS do cliente solicita oURL e [/cors-url] com uma solicitação HTTP, GET ou POST;
- o navegador que executa este código intercepta este pedido e solicita primeiro oURL [/cors-url] com uma solicitação HTTP OPTIONS para verificar se o serviço web de destino aceita solicitações entre domínios;
- um dos métodos do controlador [MyControllerWithHttpOptions] envia os cabeçalhos interdomínios esperados pelo navegador;
- o navegador solicita então a resposta inicial URL ([/cors-url]) com uma solicitação HTTP, GET ou POST;
- um dos métodos do controlador [MyControllerWithCors] responde-lhe então;
18.9.6. Testes
A classe de inicialização do projeto [intro-spring-cors-server-jpa] é a seguinte:
![]() |
package spring.cors.server.boot;
import org.springframework.boot.SpringApplication;
import spring.cors.server.config.AppConfig;
public class Boot {
public static void main(String[] args) {
SpringApplication.run(AppConfig.class, args);
}
}
- linha 10: o método estático [SpringApplication.run] é executado com a configuração Spring [AppConfig]. Devido a esta configuração, o servidor Tomcat incorporado nos arquivos do projeto é executado e a aplicação web [intro-spring-cors-server-jpa] é implementada nele. A aplicação web do servidor não seguro [intro-server-webjson-01], que faz parte dos arquivos do projeto, também é implementada nesse servidor. Como o projeto [intro-spring-security-server-01] também faz parte dos arquivos, acabam por ser expostos dois tipos de URL:
- as do serviço web seguro: /url;
- as do serviço web que aceita pedidos entre domínios: /cors-url;
Estamos agora 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 7 abaixo, colocarmos uma saída de consola, esta nunca é exibida, o que demonstra que o método [getAllCategories] da classe [MyControllerWithHttpOptions] nunca é chamado;
@Controller
public class MyControllerWithHttpOptions extends AbstractCorsController {
@RequestMapping(value = "/cors-getAllCategories", method = RequestMethod.OPTIONS)
public void getAllCategories(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse){
System.out.println(un_texte) ;
// cabeçalhos CORS
setHeaders(origin, httpServletResponse);
}
Após algumas pesquisas, descobrimos que, por predefinição, o Spring MVC trata ele próprio os comandos HTTP e [OPTIONS]. Assim, é sempre o Spring que responde e nunca o método [getAllCategories] da linha 5 acima. Este comportamento por predefiniçã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;
import spring.security.config.SecurityConfig;
@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ SecurityConfig.class })
public class AppConfig {
// pedidos entre domínios
@Bean
public boolean isCorsEnabled() {
return true;
}
@Autowired
private DispatcherServlet dispatcherServlet;
@PostConstruct
public void init() {
// a própria aplicação processa os pedidos HTTP [OPTIONS]
dispatcherServlet.setDispatchOptionsRequest(true);
}
}
- linhas 25-26: injeção do bean [dispatcherServlet], que gere os pedidos dos clientes. Este bean foi definido na configuração do servidor web / jSON não seguro [intro-server-webjson-01], analisado no parágrafo 13.5.3;
- linhas 28-29: o método [init] (linha 29) será executado assim que a classe [AppConfig] tiver sido instanciada e as injeções do Spring tiverem sido realizadas. Assim, quando for executado, o campo da linha 26 já terá sido inicializado;
- linha 31: configuramos o bean [dispatcherServlet] para que permita que a aplicação web processe ela própria os comandos HTTP e [OPTIONS];
Repetimos os testes com esta nova configuração. Obtemos o seguinte resultado:
![]() |
- em [1], verificamos que existem duas solicitações HTTP para o URL [http://localhost:8080/cors-getAllCategories];
- em [2], a solicitação [OPTIONS];
- no [3], os três cabeçalhos HTTP que acabámos de configurar na resposta do servidor;
Vamos agora analisar a segunda solicitação:
![]() |
- em [1], a solicitação analisada;
- em [2], trata-se da solicitação GET. Graças à primeira solicitação [OPTIONS], o navegador recebeu as informações que solicitava. Agora, efetua a solicitação [GET] 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 [3] do servidor é normal, [HTTP/1.1 200 OK]. Por isso, deveríamos ter o documento solicitado. É possível que o servidor tenha efetivamente enviado o documento, mas que seja o navegador 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].
Alteramos, então, o controlador [MyControllerWithCors] para que também envie os cabeçalhos necessários às solicitações entre domínios:
@RequestMapping(value = "/cors-getAllCategories", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getAllCategories(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse) throws JsonProcessingException {
// cabeçalhos CORS
setHeaders(origin, httpServletResponse);
// resposta
return myController.getAllCategories();
}
- linha 6: os cabeçalhos necessários para as solicitações entre domínios estão incluídos na resposta;
Após esta alteração, os resultados são os seguintes:
![]() |
Conseguimos, de facto, obter a lista de categorias.
18.10. Os outros URL [GET]
Nos controladores [MyControllerWithCors, MyControllerWithHttpOptions], o código das ações que processam os URL solicitados com um [GET] segue o modelo das ações que processaram anteriormente o URL e o [/cors-getAllCategories]. O leitor pode verificar o código nos exemplos fornecidos com este documento. Aqui está um exemplo para o URL e o [/cors-getAllProduits]:
no [MyControllerWithHttpOptions]
@RequestMapping(value = "/cors-getAllProduits", method = RequestMethod.OPTIONS)
public void getAllProduits(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse) {
// cabeçalhos CORS
setHeaders(origin, httpServletResponse);
}
em [MyControllerWithCors]
@RequestMapping(value = "/cors-getAllProduits", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getAllProduits(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse) throws JsonProcessingException {
// cabeçalhos CORS
setHeaders(origin, httpServletResponse);
// resposta
return myController.getAllProduits();
}
O resultado obtido é o seguinte:
![]() |
18.11. Os URL [POST]
Analisemos o seguinte caso:
![]() |
- faz-se um POST [1] para o URL [2];
- no [3], o valor lançado. Trata-se de uma cadeia jSON;
- no total, pretendemos criar uma categoria denominada [categorie2];
Por enquanto, não alteramos nenhum código. O resultado obtido é o seguinte:
![]() |
- em [1], tal como nas solicitações [GET], o navegador efetua uma solicitação [OPTIONS];
- em [2], solicita uma autorização de acesso para uma solicitação [POST]. Anteriormente, era [GET];
- em [3], solicita autorização para enviar os cabeçalhos HTTP e [accept, authorization, content-type]. Anteriormente, existiam 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];
Alteramos o método [AbstractController.sendHeaders] da seguinte forma:
package spring.cors.server.service;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class AbstractCorsController {
@Autowired
private boolean isCorsEnabled;
// envio das opções ao cliente
public void setHeaders(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);
// autorizam-se determinados cabeçalhos
response.addHeader("Access-Control-Allow-Headers", "accept, authorization, content-type");
// autorizam-se o GET e o POST
response.addHeader("Access-Control-Allow-Methods", "GET, POST");
}
}
- linha 21: adicionámos o cabeçalho HTTP [Content-Type] (não importa se é maiúsculo ou minúsculo);
- linha 23: adicionámos o método HTTP [POST];
Com isto, os métodos [POST] são tratados da mesma forma que as solicitações [GET]. Aqui está o exemplo do URL [/cors-addArticles]:
em [MyControllerWithCors]
@RequestMapping(value = "/cors-addCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
@ResponseBody
public String addCategories(HttpServletRequest request,
@RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse httpServletResponse)
throws JsonProcessingException {
// cabeçalhos CORS
setHeaders(origin, httpServletResponse);
// resposta
return myController.addCategories(request);
}
em [MyControllerWithHttpOptions]
@RequestMapping(value = "/cors-addCategories", method = RequestMethod.OPTIONS)
public void addCategories(HttpServletRequest request,
@RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse httpServletResponse)
throws JsonProcessingException {
// cabeçalhos CORS
setHeaders(origin, httpServletResponse);
}
O resultado obtido é o seguinte:
![]() |
A categoria [categorie2] foi efetivamente adicionada à base de dados. O SGBD atribuiu-lhe a chave primária 1729.
18.12. O controlador [AuthenticateCorsController]
![]() |
O controlador [AuthenticateCorsController] existe para fornecer oURL [/cors-authenticate], que permite chamar o URL [/authenticate] já existente, com uma consulta entre domínios. O seu código é o seguinte:
package spring.cors.server.service;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import spring.security.service.AuthenticateController;
@Controller
public class AuthenticateCorsController extends AbstractCorsController {
@Autowired
private AuthenticateController authenticateController;
@RequestMapping(value = "/cors-authenticate", method = RequestMethod.GET)
@ResponseBody
public String authenticate(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse response) throws JsonProcessingException {
// cabeçalhos CORS
setHeaders(origin, response);
// método de origem
return authenticateController.authenticate();
}
@RequestMapping(value = "/cors-authenticate", method = RequestMethod.OPTIONS)
public void corsAuthenticate(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse response) {
// cabeçalhos CORS
setHeaders(origin, response);
}
}
Eis dois exemplos:
![]() |
- As respostas apresentadas são geradas pelo código jS, conforme se segue:
function doGet(url) {
// faz-se uma chamada Ajax manualmente
$.ajax({
headers : {
'Authorization':'Basic '+authorizationCode
},
url : baseUrl + url,
type : 'GET',
dataType : 'text',
beforeSend : function() {
},
success : function(data) {
// resultado em texto
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// erro de sistema
response.text(JSON.stringify(jqXHR.statusCode()));
}
})
}
- a resposta [1] é apresentada pela linha 14 da função [success];
- a resposta [2] é apresentada pela linha 20 da função [error]. A função [JSON.stringify] cria a cadeia jSON do objeto [jqXHR.statusCode()], que é o objeto que encapsula o erro que ocorreu. Este objeto fornece poucas informações. É possível utilizar outros métodos do objeto [jqXHR] para obter, por exemplo, os cabeçalhos HTTP devolvidos pelo servidor;
18.13. Conclusion
A nossa aplicação suporta agora pedidos entre domínios. Estes podem ser autorizados ou não através da configuração na classe [AppConfig]:
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ SecurityConfig.class })
public class AppConfig {
// solicitações entre domínios
@Bean
public boolean isCorsEnabled() {
return true;
}
@Autowired
private DispatcherServlet dispatcherServlet;
@PostConstruct
public void init() {
// a própria aplicação processa os pedidos HTTP [OPTIONS]
dispatcherServlet.setDispatchOptionsRequest(true);
}
}







































