18. [Corso]: Condivisione delle risorse tra origini diverse
Parole chiave: CORS (Cross-Origin Resource Sharing).
Questo capitolo esula in parte dall'ambito del tutorial. È stato incluso perché introduce la programmazione web e la programmazione JavaScript. È importante ricordare che uno degli obiettivi di questo tutorial è presentare concetti frequentemente utilizzati nello sviluppo JEE, ovvero lo sviluppo web basato su framework Java. Qui estendiamo il server web utilizzato nello studio del database di prodotti e categorie per consentirgli di accettare richieste cross-domain.
Nel documento [Tutorial AngularJS / Spring 4], sviluppiamo un'applicazione client/server in cui il client è un'applicazione AngularJS:
![]() |
- le pagine HTML/CSS/JS dell'applicazione Angular provengono dal server [1];
- in [2], il servizio [dao] effettua una richiesta a un altro server, il server [2]. Tuttavia, ciò è vietato dal browser che esegue l'applicazione Angular poiché costituisce una vulnerabilità di sicurezza. L'applicazione può interrogare solo il server da cui ha origine, ovvero il server [1];
In realtà, non è corretto dire che il browser impedisca all'applicazione Angular di interrogare il server [2]. In realtà lo interroga per chiedere se consente a un client che non proviene da esso di interrogarlo. Questa tecnica di condivisione è chiamata CORS (Cross-Origin Resource Sharing). Il server [2] concede l'autorizzazione inviando specifici header HTTP.
Creeremo la seguente architettura:
![]() |
- in [1], un'applicazione web fornisce pagine HTML/JS;
- in [2], il browser esegue il JavaScript incorporato nelle pagine HTML per interrogare il servizio web sicuro [3];
18.1. Supporto
![]() |
I progetti relativi a questo capitolo si trovano nella cartella [support / chap-18].
18.2. Il progetto client
Creare il seguente progetto Eclipse:
![]() |
18.3. Configurazione Maven
Il progetto è un progetto Maven con il seguente file [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>
- righe 11–15: questo è un progetto Spring Boot;
- righe 23–26: utilizziamo la dipendenza [spring-boot-starter-web], che include un server Tomcat e Spring MVC;
18.4. Configurazione di Spring
![]() |
La classe [WebConfig] che configura il progetto Spring è la seguente:
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 {
// -------------------------------- layer configuration [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/");
}
}
- riga 15: la classe configura un progetto Spring MVC;
- riga 16: la classe estende la classe [WebMvcConfigurerAdapter] per sovrascrivere alcuni dei suoi metodi;
- righe 18–36: abbiamo già incontrato questi bean, ad esempio nella sezione 13.5.3.1. Si noti, alla riga 35, che il servizio web verrà eseguito sulla porta 8081;
- righe 38–42: il metodo [addResourceHandlers] consente di definire risorse statiche, ovvero risorse non gestite dal [DispatcherServlet] alla riga 23;
- riga 40: qualsiasi richiesta di una risorsa con estensione .html restituirà il file richiesto dalla richiesta e presente nella cartella [static] del classpath del progetto;
- riga 41: qualsiasi richiesta di una risorsa con estensione .js restituirà il file JavaScript richiesto dalla richiesta e presente nella cartella [static/js] del classpath del progetto;
![]() |
18.5. Nozioni di base su jQuery e JavaScript
La pagina HTML del cliente sarà la seguente:
![]() |
Includerà codice JavaScript (JS) che verrà eseguito nel browser. Tratteremo alcune nozioni di base su JavaScript per aiutarci a comprendere il codice. Il client effettuerà richieste HTTP utilizzando la libreria jQuery [https://jquery.com/], che fornisce molte funzioni che semplificano lo sviluppo in JavaScript. Creiamo un file HTML statico [jQuery.html] e lo inseriamo nella cartella [static]:
![]() |
Questo file avrà il seguente contenuto:
<!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>
- riga 6: importazione di jQuery;
- Righe 10–12: un elemento della pagina con l'ID [element1]. Lavoreremo con questo elemento.
Dobbiamo scaricare il file [jquery-2.1.3.min.js]. L'ultima versione di jQuery è disponibile all'URL [http://jquery.com/download/]:

Inserisci il file scaricato nella cartella [static/js] e aggiorna la riga 6 del file HTML in modo che corrisponda alla versione installata.
Una volta fatto ciò, apri la vista statica [jQuery.html] in Chrome [1-2]:
![]() |
In Google Chrome, premi [Ctrl-Shift-I] per aprire gli strumenti di sviluppo [3]. La scheda [Console] [4] ti permette di eseguire codice JavaScript. Di seguito, ti forniamo i comandi JavaScript da digitare e ti spieghiamo a cosa servono.
|
: restituisce l'insieme di tutti gli elementi con l'ID [element1], quindi normalmente una raccolta di 0 o 1 elemento , poiché non è possibile avere due ID identici in una pagina HTML. | ![]() |
|
: imposta il testo [blabla] per tutti gli elementi nella raccolta. Questo modifica il contenuto visualizzato dalla pagina | ![]() |
|
nasconde gli elementi nella collezione. Il testo [blabla] non viene più visualizzato. | ![]() |
|
: visualizza nuovamente la raccolta. Questo ci permette di vedere che l'elemento con l'ID [element1] ha l'attributo CSS style='display: none;', che fa sì che l'elemento sia nascosto. | |
|
: visualizza gli elementi della collezione. Il testo [blabla] ricompare. È l' style='display: block;' che garantisce questa visualizzazione. | ![]() |
|
: imposta un attributo su tutti gli elementi della collezione. L'attributo in questo caso è [style] e il suo valore [color: red]. Il testo [blabla] diventa rosso. | ![]() |
![]() | |
![]() |
Si noti che l'URL del browser non è cambiato durante tutte queste operazioni. Non c'è stata alcuna comunicazione con il server web. Tutto avviene all'interno del browser. Ora, visualizziamo il codice sorgente della pagina:
![]() | ![]() |
Questo è il testo iniziale. Non riflette le modifiche che abbiamo apportato all'elemento nelle righe 10-12. È importante tenerlo presente durante il debug di JavaScript. Spesso, quindi, non è necessario visualizzare il codice sorgente della pagina visualizzata.
18.6. Il codice JavaScript dell'applicazione
Torniamo alla pagina dell'applicazione client che interrogherà il servizio web / jSON:
![]() |
![]() |
Il codice HTML di questa pagina è il seguente:
<!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">
<!-- identifier -->
Identifiant :
<!-- -->
<input type="text" id="identifiant" name="identifiant" value="" />
<!-- password -->
<br /> <br /> Mot de passe :
<!-- -->
<input type="text" id="password" name="password" value="" />
<!-- method 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 />
<!-- posted value -->
<br /> Chaîne jSON à poster : <input type="text" id="posted"
size="50" />
<!-- validation button -->
<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>
- riga 6: importiamo la libreria jQuery;
- riga 7: importiamo il codice che scriveremo;
- Righe 15, 19, 26, 29, 31: Notate gli identificatori [id] dei componenti della pagina. Il JavaScript fa riferimento a questi componenti tramite tali identificatori;
Il codice [client.js] è il seguente:
// global data
var url;
var posted;
var response;
var method;
var baseUrl = 'http://localhost:8080';
var identifiant;
var password;
var authorizationHeader;
function requestServer() {
// information retrieval
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);
// delete the previous answer
response.text("");
// 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 '+authorizationCode
},
url : baseUrl + url,
type : 'GET',
dataType : 'text',
beforeSend : function() {
},
success : function(data) {
// text result
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// system error
response.text(JSON.stringify(jqXHR.statusCode()));
}
})
}
function doPost(url, posted) {
// make a manual Ajax call
$.ajax({
headers : {
'Authorization':'Basic '+authorizationCode
},
url : baseUrl + url,
type : 'POST',
contentType : 'application/json; charset=UTF-8',
data : posted,
dataType : 'text',
beforeSend : function() {
},
success : function(data) {
// text result
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// system error
response.text(JSON.stringify(jqXHR.statusCode()));
}
})
}
// document loading
$(document).ready(function() {
// retrieve page component references
identifiant = $("#identifiant");
password = $("#password");
url = $("#url");
posted = $("#posted");
response = $("#response");
});
- righe 80–87: codice JavaScript eseguito dopo che il documento ha terminato il caricamento nel browser;
- righe 81-86: recupera i riferimenti dei vari elementi nel documento HTML tramite i loro identificatori [id];
- righe 2-9: variabili globali accessibili in tutte le funzioni definite nel file JavaScript;
- riga 13: recupera l'URL inserito dall'utente;
- riga 14: recupera il valore che l'utente desidera inviare (vuoto se si tratta di un'operazione GET);
- riga 15: recupera il nome utente inserito dall'utente;
- riga 16: recupera la password;
- riga 17: recupera il metodo [get] o [post] da utilizzare quando si richiede l'URL della riga 9:
- [document] si riferisce al documento caricato dal browser, noto come DOM (Document Object Model),
- [document.forms[0]] si riferisce al primo modulo nel documento; un documento può contenere più moduli. Qui ce n'è solo uno,
- [document.forms[0].elements['method']] si riferisce all'elemento del modulo con l'attributo [name='method']. Ce ne sono due:
<input type="radio" id="get" name="method" value="get" checked="checked" />GET
<input type="radio" id="post" name="method" value="post" />POST
- (continua)
- [document.forms[0].elements['method'].value] è il valore che verrà inviato per il componente con l'attributo [name='method']. Sappiamo che il valore inviato è il valore dell'attributo [value] del pulsante di opzione selezionato. Qui, quindi, sarà una delle stringhe ['get', 'post'];
- riga 18: costruiamo la codifica Base74 della stringa `username:password`. Questa stringa codificata verrà utilizzata nell'intestazione HTTP [Authorization] che invieremo al server per autenticare la richiesta;
- righe 22–26: a seconda del metodo HTTP da utilizzare, eseguiamo il metodo [doGet] o [doPost];
- Il metodo jQuery [$.ajax] effettua una richiesta HTTP;
- righe 32–34: comunichiamo con un server che richiede un'intestazione HTTP [Authorization: Basic code];
- riga 35: l'utente inserirà URL del tipo [/cors-getAllCategories,/cors-addProduits, ...]. Questi URL devono quindi essere integrati con l'URL del server della riga 6;
- riga 36: metodo HTTP da utilizzare;
- riga 37: il server restituisce JSON. Specifichiamo il tipo [text] come tipo di risultato per visualizzarlo esattamente come ricevuto;
- riga 42: visualizza la risposta di testo del server;
- righe 48-49: visualizza eventuali messaggi di errore;
- riga 53: il metodo [doPost] riceve un secondo parametro, che è il valore da inviare;
- riga 61: per indicare che il valore inviato sarà sotto forma di stringa JSON;
18.7. Esecuzione client
L'applicazione client è un'applicazione Spring Boot avviata dalla seguente classe eseguibile [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);
}
}
- Riga 10: Il metodo [SpringApplication.run] utilizza il file di configurazione [WebConfig]. La pagina [client.html] verrà distribuita sul server Tomcat presente nel classpath del progetto;
18.8. L'URL [/getAllCategories]
Avviamo:
- il server web/JSON sulla porta 8080;
- il client per questo server sulla porta 8081;
quindi richiediamo l'URL [http://localhost:8081/client.html] [1]:
![]() |
- in [2], eseguiamo una richiesta GET sull'URL [http://localhost:8080/getAllCategories];
Non riceviamo alcuna risposta dal server. Quando controlliamo la console degli sviluppatori di Chrome (Ctrl-Shift-I), vediamo un errore:
![]() |
- in [1], ci troviamo nella scheda [Rete];
- In [2], vediamo che la richiesta HTTP effettuata non è [GET] ma [OPTIONS]. Nel caso di una richiesta cross-domain, il browser verifica con il server che determinate condizioni siano soddisfatte inviando una richiesta HTTP [OPTIONS]. In questo caso, le richieste sono quelle indicate dagli indicatori [5-6];
- in [5], il browser chiede se l'URL di destinazione sia raggiungibile con un GET. L'intestazione della richiesta [Access-Control-Request-Method] richiede una risposta con un'intestazione HTTP [Access-Control-Allow-Methods] che indichi che il metodo richiesto è accettato;
- in [6], il browser invia l'intestazione HTTP [Origin: http://localhost:8081]. Questa intestazione richiede una risposta in un'intestazione HTTP [Access-Control-Allow-Origin] che indichi che l'origine specificata è accettata;
- In [7], il browser chiede se le intestazioni HTTP [Accept] e [Authorization] sono accettate. L'intestazione di richiesta [Access-Control-Request-Headers] si aspetta una risposta con un'intestazione HTTP [Access-Control-Allow-Headers] che indichi che le intestazioni richieste sono accettate;
- si verifica un errore in [3]. Facendo clic sull'icona si ottiene l'errore [4];
- in [4], il messaggio indica che il server non ha inviato l'intestazione HTTP [Access-Control-Allow-Origin], che specifica se l'origine della richiesta è accettata;
- In [8], possiamo vedere che il server effettivamente non ha inviato questa intestazione. Di conseguenza, il browser ha rifiutato di effettuare la richiesta HTTP GET inizialmente richiesta;
Dobbiamo modificare il server web / JSON.
18.9. Il nuovo servizio web / json
Creiamo un nuovo progetto Maven [intro-spring-cors-server-jpa]:
![]() | ![]() |
18.9.1. Configurazione Maven
La configurazione Maven per il nuovo servizio web è la seguente:
<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>
- Righe 23–27: recuperiamo tutti i dati del lavoro svolto finora accedendo all'archivio /json del server web sicuro;
18.9.2. Configurazione Spring
La classe di configurazione [AppConfig] è la seguente:
![]() |
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 {
// cross-domain queries
@Bean
public boolean isCorsEnabled() {
return true;
}
}
- riga 10: la classe è una classe di configurazione Spring;
- riga 11: altri componenti Spring si trovano nel pacchetto [spring.cors.server.service];
- righe 16–19: creiamo un componente Spring denominato [isCorsEnabled] che indica se i client al di fuori del dominio del server sono accettati o meno;
18.9.3. La classe [AbstractCorsController]
La classe [AbstractCorsController], che sarà la classe padre di tutti i controller in questa applicazione:
![]() |
Il suo codice è il seguente:
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;
// sending options to the customer
public void setHeaders(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");
}
}
- riga 7: la classe [CorsController] è astratta perché è progettata per essere estesa, non istanziata;
- righe 13–24: il metodo [setHeaders] aggiunge le intestazioni HTTP richieste dalle richieste cross-domain alla [HttpServletResponse response] (riga 13) inviata al client;
- riga 33: il metodo [setHeaders] accetta come parametri:
- la stringa [origin] presente nell'intestazione HTTP [Origin] delle richieste cross-domain:
In questo caso, il parametro [origin] alla riga 13 avrebbe il valore [http://localhost:8081]. Se la richiesta non contiene l'intestazione HTTP [Origin], ci assicureremo che [origin==null];
- (continua)
- l'oggetto [HttpServletResponse response] che verrà restituito al client che ha effettuato la richiesta;
Questi due parametri vengono iniettati da Spring;
- righe 15–175: se l'applicazione è configurata per accettare richieste cross-domain, e se il mittente ha inviato l'intestazione HTTP [Origin], e se questa origine inizia con [http://localhost], allora la richiesta cross-domain viene accettata; altrimenti, viene rifiutata;
- Riga 19: se il client si trova nel dominio [http://localhost:port], inviamo l'intestazione HTTP:
Access-Control-Allow-Origin: http://localhost:port
il che significa che il server accetta l'origine del client;
- riga 21: abbiamo specificato due header HTTP specifici nella richiesta HTTP [OPTIONS]:
In risposta all'intestazione HTTP [Access-Control-Request-X], il server risponde con un'intestazione HTTP [Access-Control-Allow-X] che specifica ciò che è consentito. Le righe 20–23 ripetono semplicemente la richiesta del client per indicare che è stata accettata;
18.9.4. Il controller [MyControllerWithHttpOptions]
Per evitare di dover modificare il server web/JSON non sicuro [intro-server-webjson-01] discusso nella Sezione 13.5.3, creeremo un nuovo controller. Mentre il server non sicuro gestisce l'URL [/url], il nuovo controller gestirà l'URL [/cors-url], e questo URL accetterà le richieste cross-origin.
La classe [MyControllerWithHttpOptions] è il controller che gestirà le richieste HTTP di 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){
// headers CORS
setHeaders(origin, httpServletResponse);
}
...
- riga 14: la classe è un controller Spring MVC;
- riga 15: la classe [MyControllerWithHttpOptions] estende la classe [AbstractCorsController] appena descritta;
- righe 17–18: il metodo [getAllCategories] (riga 18) gestisce l'URL ["/cors-getAllCategories"] quando richiesto con il metodo HTTP [OPTIONS];
- riga 18: il metodo [getAllCategories] accetta due parametri:
- [@RequestHeader(value = "Origin", required = false) String origin] per recuperare il valore dell'intestazione HTTP [Origin:http://localhost:8081] quando è presente. In questo esempio, il parametro [String origin] riceverà il valore [http://localhost:8081]. Questa intestazione non è obbligatoria [required = false]. Quando non è presente, il parametro [String origin] avrà il valore null;
- [HttpServletResponse httpServletResponse]: la risposta che verrà inviata al client;
- riga 21: inviamo le intestazioni HTTP che abilitano le richieste cross-origin. Il metodo [setHeaders] è definito nella classe padre [AbstractCorsController];
Questo viene fatto per tutti gli URL esposti dal server web/JSON non protetto [intro-server-webjson-01] discusso nella Sezione 13.5.3. Quando questo servizio espone l'URL [/url], la classe [MyControllerWithHttpOptions] sopra espone l'URL [/cors-url].
18.9.5. Il controller [MyControllerWithCors]
![]() |
La classe [MyControllerWithCors] è il controller che gestirà le richieste HTTP [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 {
// spring dependencies
@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 {
// answer
return myController.getAllCategories();
}
...
- riga 17: la classe [MyControllerWithCors] è un controller Spring MVC
- riga 18: estende la classe [AbstractCorsController];
- righe 21–22: iniezione del controller [MyController] dal server web non protetto / JSON [intro-server-webjson-01] discusso nella Sezione 13.5.3;
- righe 25–27: il metodo [getAllCategories] gestisce l'URL [/cors-getAllCategories] (riga 28) quando richiesto utilizzando il metodo HTTP [GET];
- riga 26: il risultato del metodo [getAllCategories] verrà inviato al client. Questo risultato è un flusso JSON (l'attributo [produces] alla riga 27 e il tipo [String] del risultato alla riga 25);
- riga 27: il metodo riceve gli stessi parametri del metodo [getAllCategories] del controller [MyControllerWithHttpOptions] che abbiamo appena esaminato;
- riga 30: il metodo [myController.getAllCategories()] viene chiamato per inviare la risposta;
In definitiva, è il metodo [myController.getAllCategories()] del server non protetto che invia la risposta. Abbiamo semplicemente arricchito la sua risposta con le intestazioni richieste per le richieste cross-domain.
Questo viene fatto per tutti gli URL esposti dal server web/JSON non protetto [intro-server-webjson-01] discusso nella Sezione 13.5.3. Quando questo servizio espone l'URL [/url], la classe [MyControllerWithCors] sopra espone l'URL [/cors-url].
Una richiesta cross-domain procederà come segue:
- il codice JavaScript del client richiede l'URL [/cors-url] utilizzando una richiesta HTTP GET o POST;
- il browser che esegue questo codice intercetta la richiesta e richiede prima l'URL [/cors-url] utilizzando una richiesta HTTP OPTIONS per verificare che il servizio web di destinazione accetti richieste cross-origin;
- uno dei metodi nel controller [MyControllerWithHttpOptions] invia le intestazioni cross-domain previste dal browser;
- il browser richiede quindi l'URL iniziale [/cors-url] utilizzando una richiesta HTTP GET o POST;
- uno dei metodi nel controller [MyControllerWithCors] risponde quindi;
18.9.6. Test
La classe di avvio per il progetto [intro-spring-cors-server-jpa] è la seguente:
![]() |
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);
}
}
- Riga 10: Il metodo statico [SpringApplication.run] viene eseguito con la configurazione Spring [AppConfig]. Come risultato di questa configurazione, viene avviato il server Tomcat incorporato negli archivi del progetto e l'applicazione web [intro-spring-cors-server-jpa] viene distribuita su di esso. Su di esso viene distribuita anche l'applicazione web del server non protetto [intro-server-webjson-01], che fa parte degli archivi del progetto. Poiché anche il progetto [intro-spring-security-server-01] fa parte degli archivi, alla fine vengono esposti due tipi di URL:
- quelli del servizio web protetto: /url;
- quelli del servizio web che accetta richieste cross-domain: /cors-url;
Ora siamo pronti per ulteriori test. Lanciamo la nuova versione del servizio web e scopriamo che il problema persiste. Non è cambiato nulla. Se aggiungiamo un'istruzione di output della console alla riga 7 qui sotto, questa non viene mai visualizzata, indicando che il metodo [getAllCategories] della classe [MyControllerWithHttpOptions] non viene mai chiamato;
@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) ;
// headers CORS
setHeaders(origin, httpServletResponse);
}
Dopo alcune ricerche, scopriamo che, per impostazione predefinita, Spring MVC gestisce autonomamente le richieste HTTP [OPTIONS]. Pertanto, è sempre Spring a rispondere, e mai il metodo [getAllCategories] alla riga 5 sopra. Questo comportamento predefinito di Spring MVC può essere modificato. Modifichiamo la classe [AppConfig] esistente:
![]() |
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 {
// 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);
}
}
- righe 25-26: iniezione del bean [dispatcherServlet], che gestisce le richieste dei client. Questo bean è stato definito nella configurazione del server web/JSON non protetto [intro-server-webjson-01] discusso nella sezione 13.5.3;
- righe 28-29: il metodo [init] (riga 29) verrà eseguito non appena la classe [AppConfig] sarà stata istanziata e saranno state eseguite le iniezioni Spring. Pertanto, quando viene eseguito, il campo alla riga 26 è già stato inizializzato;
- riga 31: configuriamo il bean [dispatcherServlet] in modo che consenta all'applicazione web di gestire autonomamente le richieste HTTP [OPTIONS];
Eseguiamo nuovamente i test con questa nuova configurazione. Otteniamo il seguente risultato:
![]() |
- in [1], vediamo che ci sono due richieste HTTP all'URL [http://localhost:8080/cors-getAllCategories];
- in [2], la richiesta [OPTIONS];
- in [3], le tre intestazioni HTTP che abbiamo appena configurato nella risposta del server;
Ora esaminiamo la seconda richiesta:
![]() |
- in [1], la richiesta in esame;
- in [2], questa è la richiesta GET. Grazie alla prima richiesta [OPTIONS], il browser ha ricevuto le informazioni richieste. Ora sta eseguendo la richiesta [GET] inizialmente richiesta;
- in [3], la risposta del server;
- in [4], il server invia JSON;
- in [5], si è verificato un errore;
- in [6], il messaggio di errore;
È più difficile spiegare cosa sia successo in questo caso. La risposta del server [3] è normale [HTTP/1.1 200 OK]. Dovremmo quindi avere il documento richiesto. È possibile che il server abbia effettivamente inviato il documento ma che il browser ne impedisca l'uso perché richiede che, anche per la richiesta GET, la risposta includa l'intestazione HTTP [Access-Control-Allow-Origin:http://localhost:8081].
Modificheremo quindi il controller [MyControllerWithCors] in modo che invii anche le intestazioni richieste per le richieste cross-origin:
@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 {
// headers CORS
setHeaders(origin, httpServletResponse);
// answer
return myController.getAllCategories();
}
- riga 6: le intestazioni richieste per le richieste cross-domain sono incluse nella risposta;
Dopo questa modifica, i risultati sono i seguenti:
![]() |
Abbiamo ottenuto con successo l'elenco delle categorie.
18.10. Gli altri URL [GET]
Nei controller [MyControllerWithCors, MyControllerWithHttpOptions], il codice delle azioni che gestiscono gli URL [GET] richiesti segue lo schema delle azioni che in precedenza gestivano l'URL [/cors-getAllCategories]. Il lettore può verificare il codice negli esempi forniti con questo documento. Ecco un esempio per l'URL [/cors-getAllProducts]:
in [MyControllerWithHttpOptions]
@RequestMapping(value = "/cors-getAllProduits", method = RequestMethod.OPTIONS)
public void getAllProduits(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse) {
// headers CORS
setHeaders(origin, httpServletResponse);
}
in [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 {
// headers CORS
setHeaders(origin, httpServletResponse);
// answer
return myController.getAllProduits();
}
Il risultato ottenuto è il seguente:
![]() |
18.11. URL [POST]
Esaminiamo il seguente scenario:
![]() |
- Effettuiamo un POST [1] all'URL [2];
- in [3], il valore inviato. Si tratta di una stringa JSON;
- In generale, stiamo cercando di creare una categoria chiamata [category2];
Al momento non stiamo modificando alcun codice. Il risultato ottenuto è il seguente:
![]() |
- in [1], come per le richieste [GET], il browser effettua una richiesta [OPTIONS];
- in [2], richiede l'autorizzazione all'accesso per una richiesta [POST]. In precedenza era [GET];
- in [3], richiede l'autorizzazione per inviare le intestazioni HTTP [accept, authorization, content-type]. In precedenza, avevamo solo le prime due intestazioni;
- in [4], il servizio web non concede tutte le autorizzazioni richieste, il che causa l'errore [5];
Modifichiamo il metodo [AbstractController.sendHeaders] come segue:
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;
// sending options to the customer
public void setHeaders(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");
}
}
- riga 21: abbiamo aggiunto l'intestazione HTTP [Content-Type] (non fa differenza tra maiuscole e minuscole);
- riga 23: abbiamo aggiunto il metodo HTTP [POST];
Ciò significa che i metodi [POST] vengono gestiti allo stesso modo delle richieste [GET]. Ecco un esempio dell'URL [/cors-addArticles]:
in [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 {
// headers CORS
setHeaders(origin, httpServletResponse);
// answer
return myController.addCategories(request);
}
in [MyControllerWithHttpOptions]
@RequestMapping(value = "/cors-addCategories", method = RequestMethod.OPTIONS)
public void addCategories(HttpServletRequest request,
@RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse httpServletResponse)
throws JsonProcessingException {
// headers CORS
setHeaders(origin, httpServletResponse);
}
Il risultato è il seguente:
![]() |
La categoria [categorie2] è stata aggiunta con successo al database. Il DBMS le ha assegnato la chiave primaria 1729.
18.12. Il [AuthenticateCorsController]
![]() |
Il controller [AuthenticateCorsController] serve a fornire l'URL [/cors-authenticate], che consente di richiamare l'URL esistente [/authenticate] con una richiesta cross-domain. Il suo codice è il seguente:
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 {
// headers CORS
setHeaders(origin, response);
// original method
return authenticateController.authenticate();
}
@RequestMapping(value = "/cors-authenticate", method = RequestMethod.OPTIONS)
public void corsAuthenticate(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse response) {
// headers CORS
setHeaders(origin, response);
}
}
Ecco due esempi:
![]() |
- Le risposte vengono visualizzate utilizzando il seguente codice JavaScript:
function doGet(url) {
// make a manual Ajax call
$.ajax({
headers : {
'Authorization':'Basic '+authorizationCode
},
url : baseUrl + url,
type : 'GET',
dataType : 'text',
beforeSend : function() {
},
success : function(data) {
// text result
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// system error
response.text(JSON.stringify(jqXHR.statusCode()));
}
})
}
- La risposta [1] viene visualizzata dalla riga 14 della funzione [success];
- La risposta [2] viene visualizzata dalla riga 20 della funzione [error]. La funzione [JSON.stringify] crea la stringa JSON dell'oggetto [jqXHR.statusCode()], che è l'oggetto che incapsula l'errore verificatosi. Questo oggetto fornisce poche informazioni. È possibile utilizzare altri metodi dell'oggetto [jqXHR] per ottenere, ad esempio, le intestazioni HTTP restituite dal server;
18.13. Conclusione
La nostra applicazione ora supporta le richieste cross-domain. Queste possono essere abilitate o disabilitate tramite la configurazione nella classe [AppConfig]:
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ SecurityConfig.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);
}
}







































