Skip to content

21. Gestione degli accessi tra domini

21.1. Architettura

Esamineremo ora la questione delle 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]. Ebbene, 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]. Il browser, infatti, interroga il server [2] per determinare se questo consenta a un client che non proviene dal proprio dominio di interrogarlo. Questa tecnica di condivisione è chiamata CORS (Cross-Origin Resource Sharing). Il server [2] concede l'autorizzazione inviando specifici header HTTP.

Per dimostrare i problemi che possono sorgere, creeremo un'applicazione client/server in cui:

  • il server sarà il nostro server web/JSON sicuro;
  • il client sarà una semplice pagina HTML dotata di codice JavaScript che effettuerà richieste al server web/JSON;

Implementeremo 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];

21.2. Il progetto [spring-cors-server-jdbc-generic]

21.2.1. Configurazione dell'ambiente di sviluppo

  
  • Scaricare i progetti sopra elencati. I progetti [spring-cors-*] si trovano nella cartella [<examples>\spring-database-generic\spring-cors];
  • Premere Alt-F5 e ricompilare tutti i progetti Maven;

Quindi esegui la configurazione di esecuzione denominata [spring-cors-server-jdbc-generic] (il database MySQL deve essere in esecuzione), che avvia un servizio web sulla porta 8081:

 

Popolare il database [dbproduitscategories] utilizzando la configurazione di runtime denominata [spring-jdbc-generic-04-fillDataBase]:

 

Esegui la configurazione di runtime denominata [spring-cors-client-generic], che avvia una seconda applicazione web (su un'altra istanza di Tomcat) sulla porta 8082:

 

Utilizzando un browser, richiedere l'URL [http://localhost:8082/client.html]:

  • In [1] richiediamo la versione breve di tutte le categorie;
  • in [2], la risposta JSON del server;

21.2.2. Il progetto client [spring-cors-client-generic]

  

Il file [application.properties] ci permette di impostare la porta per l'applicazione web client. Il suo contenuto è il seguente:


server.port=8082

Quindi:

  • il client è un'applicazione web disponibile all'URL [http://localhost:8082];
  • il server è un'applicazione web disponibile all'URL [http://localhost:8081];

Poiché l'accesso al client non avviene tramite la stessa porta del server, si pone il problema delle richieste cross-domain. Infatti, [http://localhost:8081] e [http://localhost:8082] sono due domini diversi.

21.2.3. Configurazione di Maven

Il progetto è un progetto Maven con il seguente file [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>
  • righe 14–19: questo è un progetto Spring Boot;
  • righe 27–30: utilizziamo la dipendenza [spring-boot-starter-web], che include un server Tomcat e Spring MVC;

21.2.4. Nozioni di base su jQuery e JavaScript

L'applicazione web restituisce la seguente pagina singola:

 

Include codice JavaScript (JS) che viene 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 numerose 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="/js/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]. Puoi trovare l'ultima versione di jQuery all'URL [http://jquery.com/download/]:

Image

Inseriremo il file scaricato nella cartella [static/js]:

  

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.

JS
risultato
$("#element1")
: restituisce la raccolta 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.
$("#element1").text("blah")
: assegna il testo [blabla] a tutti gli elementi della
collezione. Questo modifica il
contenuto visualizzato dalla pagina
$("#element1").hide()
nasconde gli elementi della collezione. Il testo
[blabla] non viene più visualizzato.
$("#element1")
: visualizza nuovamente la raccolta. Questo
ci permette di vedere che l'elemento con l'ID [element1] ha
l'attributo CSS style='display: none;', il che
fa sì che l'elemento sia nascosto.
$("#element1").show()
: visualizza gli elementi della collezione. Il testo
[blabla] ricompare. È l'
style='display: block;' che garantisce questa
visualizzazione.
$("#element1").attr('style','color: red')
: 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.
Array
Dizionario

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:


<!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>

Questo è il testo originale. Non riflette le modifiche apportate all'elemento nelle righe 10–12. È importante tenerlo presente durante il debug di JavaScript. In questi casi, spesso non è necessario visualizzare il codice sorgente della pagina visualizzata.

21.2.5. Il codice JavaScript dell'applicazione

Torniamo al codice HTML della pagina dell'applicazione client che interrogherà il servizio 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>
  • riga 6: importiamo la libreria jQuery;
  • riga 7: importiamo il codice che scriveremo;
  • Righe 11, 15, 17, 21: 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;
 
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");
});
  • righe 71–75: codice JavaScript eseguito dopo che il documento ha terminato il caricamento nel browser;
  • righe 73-75: recupera i riferimenti di tre elementi nel documento HTML;
  • righe 2-5: variabili globali note in tutte le funzioni definite nel file JavaScript;
  • riga 9: recupera l'URL inserito dall'utente;
  • riga 10: recupera il valore che l'utente desidera inviare;
  • riga 11: 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'];
  • Righe 13–18: A seconda del metodo HTTP da utilizzare, viene eseguito il metodo [doGet] o [doPost];
  • Il metodo jQuery [$.ajax] effettua una richiesta HTTP;
  • righe 23–25: stiamo comunicando con un server che richiede un'intestazione HTTP [Authorization: Basic code]. Creiamo questa intestazione per l'utente [admin / admin], che è l'unico autorizzato a interrogare il server;
  • riga 26: l'utente inserirà URL del tipo [/getAllLongCategories, /saveCategories, ...]. Questi URL devono quindi essere completati;
  • riga 27: metodo HTTP da utilizzare;
  • riga 28: il server restituisce JSON. Specifichiamo il tipo [text/plain] come tipo di risposta in modo che venga visualizzato esattamente come ricevuto;
  • riga 33: visualizza la risposta testuale del server;
  • riga 39: visualizza eventuali messaggi di errore in formato testo;
  • riga 44: il metodo [doPost] riceve un secondo parametro, che è il valore da inviare;
  • riga 52: per indicare che il valore inviato sarà sotto forma di stringa JSON;

21.2.6. Esecuzione client

L'applicazione client è un'applicazione console avviata dalla seguente classe eseguibile [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);
    }
}
  • Riga 6: L'annotazione [@EnableAutoConfiguration] è un'annotazione del progetto [Spring Boot] (riga 4). Spring Boot esaminerà gli archivi presenti nel classpath del progetto. In questo caso, si tratterà di tutte le dipendenze Maven fornite dall'unica dipendenza nel file [pom.xml]:

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

Questa dipendenza include un gran numero di librerie, in particolare Spring MVC e un server Tomcat. A causa di queste dipendenze, Spring Boot configurerà un progetto Spring MVC in esecuzione su Tomcat utilizzando i valori predefiniti. Il server Tomcat viene quindi configurato per l'esecuzione sulla porta 8080. Se si desidera sovrascrivere i valori predefiniti scelti da Spring Boot, è possibile utilizzare il file [application.properties] nella radice del classpath (tutto ciò che si trova in [src/main/resources] si trova nella radice del classpath):

  

Specifichiamo che il server Tomcat debba essere in esecuzione sulla porta 8082 come segue:


server.port=8082

Un elenco dei parametri che possono essere utilizzati in [application.properties] è disponibile all'URL (giugno 2015) [http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html];

Torniamo al codice in [Client.java]:

  • riga 10: il metodo [SpringApplication.run] distribuirà la pagina [client.html] sul server Tomcat presente nel classpath del progetto;

21.2.7. L'URL [/getAllShortCategories]

Avviamo:

  • il server web/JSON sicuro sulla porta 8081 (configurazione [spring-security-server-jdbc-generic]);
  • il client per questo server sulla porta 8082 (configurazione [spring-cors-client-generic]);

quindi richiediamo l'URL [http://localhost:8082/client.html] [1]:

  • in [2], eseguiamo una richiesta GET sull'URL [http://localhost:8081/getAllShortCategories];

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 dai puntini [5-6];
  • In [5], il browser chiede se l'URL di destinazione sia raggiungibile con un GET. La 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. La 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.

21.2.8. Un nuovo servizio web / JSON

Stiamo creando un nuovo progetto Maven [spring-cors-server-jdbc-generic]:

 

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>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>
  • Righe 30–32: recuperiamo tutti i dati relativi al lavoro svolto finora accedendo all'archivio JSON protetto sul server web;

Alla fine, le dipendenze sono le seguenti:

  

La classe di configurazione [AppConfig] è la seguente:

  

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;
    }
...
}
  • riga 12: la classe è una classe di configurazione Spring;
  • riga 9: altri componenti Spring si trovano nel pacchetto [spring.cors.server.service];
  • riga 14: importiamo i bean dal progetto [spring-security-server-jdbc-generic];
  • righe 18–21: creiamo un componente Spring denominato [isCorsEnabled] che indica se i client al di fuori del dominio del server sono accettati o meno;

21.2.9. I controller

Il nuovo servizio web dispone di quattro controller:

  
  • [CorsCategorieController] gestisce gli URL relativi alle categorie. Si occupa esclusivamente delle intestazioni CORS provenienti dai client web. Per il resto, delega il lavoro al [CategorieController] presente nella dipendenza [spring-webjson-server-jdbc-generic];
  • [CorsProductController] e [CorsAuthenticateController] fanno lo stesso delegando il lavoro al [ProductController] nella dipendenza [spring-webjson-server-jdbc-generic] e all'[AuthenticateController] nella dipendenza [spring-security-server-jdbc-generic];
  • [CorsController] viene utilizzato per estrarre ciò che è comune ai tre controller precedenti;

21.2.9.1. Il [CorsController]

La classe [CorsController] è la seguente:


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");
    }
}
  • riga 8: la classe [CorsController] è un controller Spring;
  • righe 11-12: iniezione del bean [isCorsEnabled], che indica se gestire o meno le intestazioni CORS;
  • righe 15–26: il metodo [sendOptions] gestisce le risposte ai client che inviano intestazioni CORS;
  • righe 17-19: se l'applicazione è configurata per accettare richieste cross-domain, se il mittente ha inviato l'intestazione HTTP [Origin] e se tale origine inizia con [http://localhost], allora la richiesta cross-domain viene accettata; altrimenti, viene rifiutata;
  • riga 21: 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;

  • righe 22–25: abbiamo specificato due header HTTP specifici nella richiesta HTTP [OPTIONS]:
Access-Control-Request-Method: GET
Access-Control-Request-Headers: accept, authorization

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 22–25 ripetono semplicemente la richiesta del client per indicare che è stata accettata;

21.2.9.2. Il controller [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();
    }
 
...
}
  • riga 19: l'annotazione [@RestController] rende la classe sia un componente Spring sia un controller MVC che invia le proprie risposte al client;
  • Riga 20: la classe [CorsCategorieController] estende la classe [CorsController] che abbiamo appena visto;
  • righe 22–23: iniezione del controller [CategorieController] dalla dipendenza [spring-webjson-server-jdbc-generic];
  • righe 25–29: gestiscono l'URL [/cors-getAllShortCategories] quando viene richiesto con il metodo HTTP [OPTIONS]. Per convenzione, stabiliamo che i client web che desiderano chiamare l'URL [/U] del servizio web protetto debbano in realtà chiamare l'URL [/cors-U]. Il servizio web distribuito avrà quindi due tipi di URL:
    • [/U]: per i client non web;
    • [/cors-U]: per i client web;
  • riga 25: il metodo [/cors-getAllShortCategories] accetta i seguenti parametri:
    • l'oggetto [@RequestHeader(value = "Origin", required = false)], che recupera l'intestazione HTTP [Origin] dalla richiesta. Questa intestazione è stata inviata dall'origine della richiesta:
Origin:http://localhost:8082

Specifichiamo che l'intestazione HTTP [Origin] è facoltativa [required = false]. In questo caso, se l'intestazione manca, il parametro [String origin] avrà un valore nullo. Con [required = true], che è il valore predefinito, viene generata un'eccezione se l'intestazione manca. Volevamo evitare questo scenario;

  • (continua)
    • l'oggetto [HttpServletResponse response] che verrà restituito al client che ha effettuato la richiesta;

Questi due parametri vengono iniettati da Spring;

  • riga 28: deleghiamo la gestione della richiesta al metodo [sendOptions] della classe padre [CorsController];
  • righe 31–36: il metodo [getAllShortCategories] gestisce l'URL [/cors-getAllShortCategories] quando viene richiesto con un GET;
  • Riga 35: il lavoro viene delegato al metodo [CategorieController.getAllShortCategories] della dipendenza [spring-webjson-server-jdbc-generic];

Ora siamo pronti per ulteriori test. Lanciamo la nuova versione del servizio web e constatiamo che il problema persiste. Non è cambiato nulla. Se aggiungiamo un output della console alla riga 28 sopra, questo non viene mai visualizzato, il che indica che il metodo [corsGetAllShortCategories] alla riga 25 non viene mai chiamato.

Dopo alcune ricerche, scopriamo che Spring MVC gestisce autonomamente le richieste HTTP [OPTIONS] utilizzando la gestione predefinita. Pertanto, è sempre Spring a rispondere, e mai il metodo [corsGetAllShortCategories] alla riga 25. 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;
 
@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);
    }
}
 
  • righe 23-24: iniettiamo il componente [DispatcherServlet dispatcherServlet], definito nella dipendenza [spring-webjson-server-jdbc-generic];
  • righe 26-30: l'annotazione [@PostConstruct] garantisce che il metodo [init] venga eseguito dopo l'istanziazione della classe [AppConfig] e dopo che Spring ha eseguito le sue iniezioni;
  • riga 29: configuriamo il servlet per inoltrare le richieste HTTP [OPTIONS] all'applicazione;

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/getAllCategories];
  • in [2], la richiesta [OPTIONS];
  • in [3], le tre intestazioni HTTP che abbiamo appena configurato nella risposta del server;

Esaminiamo ora la seconda affermazione:

  • in [1], la richiesta in esame;
  • in [2], si tratta della richiesta GET. Grazie alla prima richiesta [OPTIONS], il browser ha ricevuto le informazioni richieste. Ora esegue 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'utilizzo perché richiede che, anche per la richiesta GET, la risposta includa l'intestazione HTTP [Access-Control-Allow-Origin:http://localhost:8081].

Modifichiamo il metodo che gestisce la richiesta GET per l'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();
}
  • riga 5: come per la richiesta HTTP [OPTIONS], il server invierà le intestazioni HTTP CORS per una richiesta HTTP [GET];

Dopo questa modifica, i risultati sono i seguenti:

 

Abbiamo ottenuto con successo la versione abbreviata di tutte le categorie.

21.2.9.3. Gli URL [GET]

Nei controller [CorsCategorieController, CorsProduitController, CorsAuthenticateController], il codice delle azioni che gestiscono gli URL [GET] richiesti segue lo schema delle azioni che in precedenza gestivano l'URL [/cors-getAllShortArticles]. Il lettore può verificare il codice negli esempi forniti con questo documento. Ecco un esempio per l'URL [/cors-getAllLongProduits] nel controller [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. URL [POST]

Consideriamo il seguente scenario:

  • Effettuiamo un POST [1] all'URL [2];
  • in [3], il valore inviato. Questa è la stringa JSON per una categoria senza prodotti;
  • In definitiva, vogliamo creare una categoria denominata [category[2]];

A questo punto 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 di 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 [CorsController.sendOptions] come segue:


    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");
    }
}
  • riga 9: abbiamo aggiunto l'intestazione HTTP [Content-Type] (non fa differenza maiuscolo/minuscolo);
  • riga 11: 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-saveCategories] nel controller [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);
}

Una volta apportate queste modifiche, il risultato è il seguente:

 

La categoria [categorie[2]] è stata aggiunta con successo al database. Il DBMS le ha assegnato la chiave primaria 226. È possibile verificarlo utilizzando il metodo GET [/cors-getAllShortCategories]:

 

21.2.10. Conclusione

La nostra applicazione ora supporta le richieste cross-domain. Queste possono essere abilitate o disabilitate tramite la configurazione nella 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. Il progetto Eclipse [spring-cors-server-jpa-generic]

Il servizio web CORS sarà ora implementato dal progetto [spring-cors-server-jpa-generic], basato sul progetto [spring-security-server-jpa-generic] che gestisce l'accesso al database utilizzando Spring Data JPA:

Il progetto [spring-cors-server-jpa-generic] viene creato clonando il progetto [spring-cors-server-jdbc-generic] precedentemente studiato.

  

Ora bisogna apportare due modifiche. La prima riguarda il 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>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>
  • righe 30–32: la dipendenza dal servizio web sicuro [spring-security-server-jpa-generic];

Alla fine, le dipendenze del progetto sono le seguenti:

  

Nota: premere Alt-F5 e quindi rigenerare tutti i progetti

La seconda modifica consiste nell'aggiornare le importazioni nelle classi che segnalano errori [Alt-Shift-O].

Questo è tutto. Avviamo il servizio web CORS con la configurazione di runtime [spring-cors-server-jpa-generic-hibernate-eclipselink]:

Quindi avviamo il client generico:

e, utilizzando un browser, richiedi l'URL [1] con un GET. In [2], vediamo che la versione abbreviata delle categorie restituite presenta il campo [entityType], che mancava nella precedente versione JDBC.

Ora tratteremo altre due architetture CORS:

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

Implementeremo la seguente architettura:

Caricare i seguenti progetti:

  

Nota: premi Alt-F5 e rigenera tutti i progetti Maven.

Avvia il database DB2 e verifica che il database [dbproduitscategories] esista. In caso contrario, crealo (sezione 12.1.2).

Creiamo gli utenti nel database [dbproduitscategories] utilizzando la configurazione di runtime [spring-security-create-users-hibernate-eclipselink]:

Image

Quindi avvia il servizio web CORS utilizzando la configurazione di runtime denominata [spring-cors-server-jpa-generic-hibernate-eclipselink] e il relativo client denominato [spring-cors-client-generic]:

Piena il database [dbproduitscategories] con i valori utilizzando la configurazione di runtime [spring-jdbc-generic-04-fillDataBase]:

 

Infine, richiedere il seguente URL in un browser:

 

21.5. CORS / JPA OpenJPA / Architettura Firebird

Ora implementeremo la seguente architettura:

Caricare i seguenti progetti:

  

Nota: premi Alt-F5 e rigenera tutti i progetti Maven.

Avvia il DBMS Firebird e verifica che il database [dbproduitscategories] esista. In caso contrario, crealo (sezione 14.1.2).

Creiamo gli utenti nel database [dbproduitscategories] utilizzando la configurazione di runtime [spring-security-create-users-openjpa]:

Image

Quindi avviare il servizio web CORS con la configurazione di runtime denominata [spring-cors-server-jpa-generic-openjpa]:

Avvia il client CORS utilizzando la configurazione [spring-cors-client-generic]:

 

Popolare il database [dbproduitscategories] con i valori utilizzando la configurazione di runtime [spring-jdbc-generic-04-fillDataBase]:

 

Infine, richiedere il seguente URL in un browser: