Skip to content

21. Gestione degli accessi tra domini

21.1. Architecture

Esamineremo ora il problema delle richieste tra domini. Nel documento [Tutoriel AngularJS / Spring 4] viene sviluppata 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 perché costituisce una falla di sicurezza. L’applicazione può interrogare solo il server da cui proviene, ovvero il server [1];

In realtà, non è esatto affermare che il browser impedisca all’applicazione Angular di interrogare il server [2]. Il browser, infatti, interroga il server [2] per sapere se autorizza un client che non proviene dalla sua stessa origine a interrogarlo. Questa tecnica di condivisione è denominata CORS (Cross-Origin Resource Sharing). Il server [2] dà il proprio consenso inviando specifici header HTTP.

Per illustrare i problemi che si possono incontrare, creeremo un'applicazione client/server in cui:

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

Implementeremo la seguente architettura:

  • in [1], un'applicazione web fornisce le pagine HTML / jS;
  • in [2], il browser esegue il JavaScript incorporato nelle pagine HTML per interrogare il servizio web protetto [3];

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

21.2.1. Configurazione dell’ambiente di lavoro

  
  • caricare i progetti sopra indicati. I progetti [spring-cors-*] si trovano nella cartella [<exemples>\spring-database-generic\spring-cors];
  • premere Alt-F5 e rigenerare tutti i progetti Maven;

Eseguire quindi la configurazione di esecuzione denominata [spring-cors-server-jdbc-generic] (devono essere avviati anche SGBD e MySQL), che avvia un servizio web sulla porta 8081:

 

Compilare la base [dbproduitscategories] con la configurazione di esecuzione denominata [spring-jdbc-generic-04-fillDataBase]:

 

Eseguire la configurazione di esecuzione denominata [spring-cors-client-generic] che avvia una seconda applicazione web (su un altro Tomcat) sulla porta 8082:

 

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

  • in [1], si richiede la versione breve di tutte le categorie;
  • con [2], si ottiene la risposta jSON dal server;

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

  

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


server.port=8082

Quindi:

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

Poiché il client non viene raggiunto dalla stessa porta del server, si presenta il problema delle richieste interdominio. Infatti, [http://localhost:8081] e [http://localhost:8082] sono due domini diversi.

21.2.3. Configurazione 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 /> <!-- ricerca del genitore dal 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>

    <!-- plugin -->
    <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: si tratta di un progetto Spring Boot;
  • righe 27-30: si utilizza 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 unica:

 

Include codice JavaScript (jS) eseguito nel browser. Presenteremo alcune nozioni di base di JavaScript che ci permetteranno di comprendere il codice. Il client effettuerà chiamate HTTP utilizzando la libreria jQuery [https://jquery.com/], che offre numerose funzioni che facilitano lo sviluppo in JavaScript. Creiamo un file statico HTML [jQuery.html] che 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 id [element1]. Ora sperimenteremo con questo elemento.

Dobbiamo scaricare il file [jquery-2.1.3.min.js]. Troveremo l’ultima versione di jQuery in URL [http://jquery.com/download/]:

Image

Il file scaricato va inserito nella cartella [static / js]:

  

Fatto ciò, si richiederà la visualizzazione statica [jQuery.html] con Chrome [1-2]:

Con Google Chrome, digitare [Ctrl-Maj-I] per visualizzare gli strumenti di sviluppo [3]. La scheda [Console] [4] consente di eseguire codice JavaScript. Di seguito forniamo alcuni comandi JavaScript da digitare e ne forniamo una spiegazione.

JS
résultat
$("#element1")
: restituisce la collezione di tutti gli elementi con id
[element1], quindi normalmente una collezione
di 0 o 1 elemento, poiché non è possibile avere
due ID identici in una pagina HTML.
$("#element1").text("blabla")
: assegna il testo [blabla] a tutti gli elementi della
collezione. Ciò ha l’effetto di modificare il
contenuto visualizzato dalla pagina
$("#element1").hide()
nasconde gli elementi della collezione. Il testo
[blabla] non viene più visualizzato.
$("#element1")
: visualizza nuovamente la collezione. Questo ci
permette di vedere che l'elemento con id [element1] ha
l'attributo CSS style='display: none;', che fa
che l'elemento sia nascosto.
$("#element1").show()
: visualizza gli elementi della collezione. Il testo
[blabla] riappare. È l'attributo
CSS style='display: block;' che garantisce questa
visualizzazione.
$("#element1").attr('style','color: red')
: assegna un attributo a tutti gli elementi della
collezione. L'attributo in questo caso è [style] e il suo valore
[color: red]. Il testo [blabla] diventa rosso.
Tableau
Dictionnaire

Si noti che il codice URL del browser non è cambiato durante tutte queste operazioni. Non ci sono stati scambi 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 iniziale. Non riflette in alcun modo le modifiche apportate all’elemento nelle righe 10-12. È importante tenerlo presente quando si esegue il debug di JavaScript. Spesso, quindi, è inutile visualizzare il codice sorgente della pagina visualizzata.

21.2.5. Il codice jS 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">
        <!--  metodo 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 />
        <!-- valore inserito -->
        <br /> Chaîne jSON à poster : <input type="text" id="posted" size="50" />
        <!-- pulsante di conferma -->
        <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: si importa la libreria jQuery;
  • riga 7: si importa un codice che scriveremo noi;
  • righe 11, 15, 17, 21: si annotano gli identificatori [id] dei componenti della pagina. Il codice JavaScript fa riferimento a questi componenti tramite tali identificatori;

Il codice [client.js] è il seguente:


// dati generali
var url;
var posted;
var response;
var method;

function requestServer() {
    // si recuperano le informazioni dal modulo
    var urlValue = url.val();
    var postedValue = posted.val();
    method = document.forms[0].elements['method'].value;
    // si effettua manualmente una richiesta Ajax
    if (method === "get") {
        doGet(urlValue);
    } else {
        doPost(urlValue, postedValue);
    }
}

function doGet(url) {
    // si effettua una chiamata Ajax manualmente
    $.ajax({
        headers : {
            'Authorization' : 'Basic YWRtaW46YWRtaW4='
        },
        url : 'http://localhost:8081' + url,
        type : 'GET',
        dataType : 'tex/plain',
        beforeSend : function() {
        },
        success : function(data) {
            // risultato testo
            response.text(data);
        },
        complete : function() {
        },
        error : function(jqXHR) {
            // errore di sistema
            response.text(jqXHR.responseText);
        }
    })
}

function doPost(url, posted) {
    // si effettua manualmente una chiamata Ajax
    $.ajax({
        headers : {
            'Autorizzazione: 'Basic YWRtaW46YWRtaW4='
        },
        url : 'http://localhost:8081    ' + url,
        type : 'POST',
        contentType : 'application/json',
        data : posted,
        dataType : 'tex/plain',
        beforeSend : function() {
        },
        success : function(data) {
            // risultato testo
            response.text(data);
        },
        complete : function() {
        },
        error : function(jqXHR) {
            // errore di sistema
            response.text(jqXHR.responseText);
        }
    })
}

// durante il caricamento del documento
$(document).ready(function() {
    // si recuperano i riferimenti dei componenti della pagina
    url = $("#url");
    posted = $("#posted");
    response = $("#response");
});
  • righe 71-75: codice jS eseguito al termine del caricamento del documento nel browser;
  • righe 73-75: si recuperano i riferimenti di tre delle aree del documento HTML;
  • righe 2-5: variabili globali note in tutte le funzioni definite nel file jS;
  • riga 9: si recupera il valore URL digitato dall'utente;
  • riga 10: si recupera il valore che l'utente desidera inviare;
  • riga 11: si recupera il metodo [get] o [post] da utilizzare per richiedere il valore URL della riga 9:
    • "document" indica il documento caricato dal browser, ovvero il DOM (Document Object Model),
    • document.forms[0] indica il primo modulo del documento; un documento può contenerne diversi. In questo caso ce n’è solo uno,
    • document.forms[0].elements['method'] indica l’elemento del modulo che possiede 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 che possiede l’attributo [name='method']. Si sa che il valore inviato è il valore dell’attributo [value] del pulsante di opzione selezionato. In questo caso, sarà quindi una delle stringhe ['get', 'post'];
  • righe 13-18: a seconda del metodo HTTP da utilizzare, si esegue il metodo [doGet] o [doPost];
  • il metodo jQuery [$.ajax] effettua una chiamata HTTP;
  • righe 23-25: ci si rivolge a un server che richiede un'intestazione HTTP [Authorization: Basic code]. Creiamo questa intestazione per l'utente [admin / admin], l'unico autorizzato a interrogare il server;
  • riga 26: l'utente inserirà dei URL del tipo [/getAllLongCategories, /saveCategories, ...]. È quindi necessario completare questi URL;
  • riga 27: metodo HTTP da utilizzare;
  • riga 28: il server restituisce jSON. Si indica il tipo [text/plain] come tipo di risultato per visualizzarlo così come è stato ricevuto;
  • riga 33: visualizzazione della risposta testuale del server;
  • riga 39: visualizzazione di un eventuale messaggio 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 del client

L'applicazione client è un'applicazione da 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à i file presenti nel Classpath del progetto. In questo caso si tratterà di tutte le dipendenze Maven fornite dall'unica dipendenza del file [pom.xml]:

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

Questa dipendenza introduce numerosi file, in particolare Spring MVC e un server Tomcat. A causa della presenza di queste dipendenze, Spring Boot configurerà, con i valori predefiniti, un progetto Spring MVC in esecuzione su Tomcat. Il server Tomcat viene quindi configurato per funzionare sulla porta 8080. Se si desidera ignorare 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):

  

Indichiamo che il server Tomcat deve funzionare sulla porta 8082 nel modo seguente:


server.port=8082

L'elenco dei parametri utilizzabili è disponibile in [application.properties], URL (giugno 2015) e [http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html];

Torniamo al codice di [Client.java]:

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

21.2.7. URL [/getAllShortCategories]

Avviamo:

  • il server web/json protetto sulla porta 8081 (configurazione [spring-security-server-jdbc-generic]);
  • il client di questo server sulla porta 8082 (configurazione [spring-cors-client-generic]);

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

  • in [2], eseguiamo un GET su URL e [http://localhost:8081/getAllShortCategories];

Non riceviamo alcuna risposta dal server. Osservando la console di sviluppo di Chrome (Ctrl-Maiusc-I) si rileva un errore:

  • in [1], ci troviamo nella scheda [Network];
  • in [2], si nota che la richiesta HTTP effettuata non è [GET] ma [OPTIONS]. Nel caso di una richiesta interdominio, il browser verifica con il server che siano soddisfatte una serie di condizioni inviandogli una richiesta HTTP [OPTIONS]. In questo caso, le richieste sono quelle indicate dai simboli [5-6];
  • In [5], il browser chiede se la destinazione URL 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 con 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] attende una risposta con un'intestazione HTTP [Access-Control-Allow-Headers] che indichi che le intestazioni richieste sono accettate;
  • si verifica un errore in [3]. Cliccando 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], si può constatare che il server non ha effettivamente inviato tale intestazione. Di conseguenza, il browser ha rifiutato di eseguire la richiesta HTTP GET inizialmente richiesta;

Dobbiamo modificare il server web / jSON.

21.2.8. Un nuovo servizio web / json

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

 

La configurazione Maven del 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>

    <!-- plugin -->
    <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 tutto il lavoro svolto finora attingendo all'archivio del server web / json protetto;

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 {

    // richieste interdominio
    @Bean
    public boolean isCorsEnabled() {
        return true;
    }
...
}
  • riga 12: la classe è una classe di configurazione Spring;
  • riga 9: altri componenti Spring sono da ricercare nel pacchetto [spring.cors.server.service];
  • riga 14: si importano i bean dal progetto [spring-security-server-jdbc-generic];
  • righe 18-21: si crea un componente Spring denominato [isCorsEnabled] che indica se accettare o meno i client esterni al dominio del server;

21.2.9. I controller

Il nuovo servizio web dispone di quattro controller:

  
  • [CorsCategorieController] gestisce il URL relativo all’elaborazione delle categorie. Gestisce solo le intestazioni CORS dei client web. In caso contrario, delega il lavoro al controller [CategorieController] della dipendenza [spring-webjson-server-jdbc-generic];
  • [CorsProduitController] e [CorsAuthenticateController] fanno lo stesso, delegando il lavoro ai controller [ProduitController] della dipendenza [spring-webjson-server-jdbc-generic] e [AuthenticateController] della dipendenza[spring-security-server-jdbc-generic];
  • [CorsController] serve a estrarre ciò che è comune ai tre controllori precedenti;

21.2.9.1. Il controller [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;

    // invio delle opzioni al client
    public void sendOptions(String origin, HttpServletResponse response) {
        // CORS consentito?
        if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
            return;
        }
        // si imposta l'header CORS
        response.addHeader("Access-Control-Allow-Origin", origin);
        // si autorizzano determinati header
        response.addHeader("Access-Control-Allow-Headers", "accept, authorization");
        // si autorizza il 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] si occupa di rispondere ai client che inviano le intestazioni CORS;
  • righe 17-19: se l'applicazione è configurata per accettare le richieste interdominio e se il mittente ha inviato l'intestazione HTTP [Origin] e se tale origine inizia con [http://localhost], allora si accetta la richiesta inter-dominio, altrimenti la si rifiuta;
  • riga 21: se il client si trova nel dominio [http://localhost:port], si invia l'intestazione HTTP:
Access-Control-Allow-Origin:  http://localhost:porta

il che significa che il server accetta l’origine del client;

  • righe 22-25: abbiamo segnalato due intestazioni HTTP particolari nella richiesta HTTP [OPTIONS]:
Access-Control-Request-Method: GET
Access-Control-Request-Headers: accept, authorization

All'intestazione HTTP [Access-Control-Request-X], il server risponde con un'intestazione HTTP [Access-Control-Allow-X] in cui indica ciò che è consentito. Le righe 22-25 si limitano a riprendere 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) {
        // metodo di origine
        return categorieController.getAllShortCategories();
    }

...
}
  • riga 19: l’annotazione [@RestController] rende la classe sia un componente Spring sia un controller MVC che invia a sua volta le risposte al cliente;
  • riga 20: la classe [CorsCategorieController] estende la classe [CorsController] che abbiamo appena visto;
  • righe 22-23: iniezione del controller [CategorieController categorieController] dalla dipendenza [spring-webjson-server-jdbc-generic];
  • righe 25-29: gestiscono URL [/cors-getAllShortCategories] quando viene richiesto con il comando HTTP [OPTIONS]. Per convenzione, stabiliamo che i client web che desiderano richiamare l'URL [/U] del servizio web protetto, dovranno in realtà richiamare l'URL [/cors-U]. Il servizio web implementato 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 come parametri:
    • l’oggetto [@RequestHeader(value = "Origin", required = false)] che recupererà l’intestazione HTTP [Origin] della richiesta. Tale intestazione è stata inviata dal mittente della richiesta:
Origin:http://localhost:8082

Si precisa che l'intestazione HTTP [Origin] è facoltativa [required = false]. In questo caso, se l'intestazione è assente, il parametro [String origin] assumerà il valore null. Con [required = true], che è il valore predefinito, viene generata un'eccezione se l'intestazione è assente. Si è voluto evitare questo caso;

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

Questi due parametri vengono inseriti da Spring;

  • riga 28: si delega l’elaborazione della richiesta al metodo [sendOptions] della classe padre [CorsController];
  • righe 31-36: il metodo [getAllShortCategories] elabora URL e [/cors-getAllShortCategories] quando viene richiamato con un GET;
  • riga 35: l'operazione viene delegata al metodo [CategorieController.getAllShortCategories] della dipendenza [spring-webjson-server-jdbc-generic];

Ora siamo pronti per nuovi test. Lanciamo la nuova versione del servizio web e scopriamo che il problema persiste. Non è cambiato nulla. Se alla riga 28 sopra inseriamo un output di console, questo non viene mai visualizzato, dimostrando così che il metodo [corsGetAllShortCategories] della riga 25 non viene mai chiamato.

Dopo alcune ricerche, scopriamo che Spring MVC gestisce autonomamente i comandi HTTP e [OPTIONS] con un'elaborazione 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 {

    // richieste tra domini
    @Bean
    public boolean isCorsEnabled() {
        return true;
    }
    
    @Autowired
    private DispatcherServlet dispatcherServlet;
    
    @PostConstruct
    public void init(){
        // l'applicazione gestisce autonomamente le richieste HTTP [OPTIONS]
        dispatcherServlet.setDispatchOptionsRequest(true);
    }
}

  • righe 23-24: si inietta il componente [DispatcherServlet dispatcherServlet] che è stato definito nella dipendenza [spring-webjson-server-jdbc-generic];
  • righe 26-30: l'annotazione [@PostConstruct] fa sì che il metodo [init] venga eseguito dopo l'istanziazione della classe [AppConfig] e dopo le iniezioni effettuate da Spring;
  • riga 29: si richiede che il servlet inoltri all'applicazione i comandi HTTP e [OPTIONS];

Ripetiamo i test con questa nuova configurazione. Si ottiene il seguente risultato:

  • in [1], si nota che ci sono due richieste HTTP verso URL e [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 richiesta:

  • in [1], la richiesta esaminata;
  • in [2], ovvero la richiesta GET. Grazie alla prima richiesta [OPTIONS], il browser ha ricevuto le informazioni richieste. Ora esegue la richiesta [GET] richiesta inizialmente;
  • 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 [3] del server è normale, [HTTP/1.1 200 OK]. Dovremmo quindi avere il documento richiesto. È possibile che il server abbia effettivamente inviato il documento, ma che sia il browser a impedirne l’utilizzo perché richiede che anche per la richiesta GET la risposta contenga l’intestazione HTTP [Access-Control-Allow-Origin:http://localhost:8081].

Modifichiamo il metodo che gestisce GET da URL a [/cors-getAllShortCategories]:


    @RequestMapping(value = "/cors-getAllShortCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllShortCategories(
            @RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse response) {
        // intestazioni CORS
        sendOptions(origin, response);
        // metodo di origine
        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 correttamente la versione abbreviata di tutte le categorie.

21.2.9.3. I URL [GET]

Nei controller [CorsCategorieController, CorsProduitController, CorsAuthenticateController], il codice delle azioni che gestiscono le richieste URL con un [GET] segue il modello delle azioni che in precedenza hanno gestito URL e [/cors-getAllShortArticles]. Il lettore può verificare il codice negli esempi forniti con il presente documento. Di seguito è riportato un esempio per i codici URL e [/cors-getAllLongProduits] del 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) {
        // intestazioni CORS
        sendOptions(origin, response);
        // metodo di origine
        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. I URL [POST]

Esaminiamo il seguente caso:

  • si effettua un trasferimento da POST [1] verso URL [2];
  • in [3], il valore pubblicato. Si tratta della stringa jSON di una categoria senza prodotti;
  • in definitiva, si cerca di creare una categoria denominata [categorie[2]];

Per il momento non modifichiamo alcun codice. Il risultato ottenuto è quindi il seguente:

  • in [1], come per le richieste [GET], il browser effettua una richiesta [OPTIONS];
  • da [2], richiede un'autorizzazione di accesso per una richiesta [POST]. In precedenza era [GET];
  • in [3], richiede l’autorizzazione a inviare le intestazioni HTTP e [accept, authorization, content-type]. In precedenza erano presenti solo le prime due intestazioni;
  • in [4], il servizio web non concede tutte le autorizzazioni richieste, causando l'errore [5];

Modifichiamo il metodo [CorsController.sendOptions] nel modo seguente:


    public void sendOptions(String origin, HttpServletResponse response) {
        // Cors consentito?
        if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
            return;
        }
        // si imposta l'intestazione CORS
        response.addHeader("Access-Control-Allow-Origin", origin);
        // si autorizzano alcune intestazioni
        response.addHeader("Access-Control-Allow-Headers", "accept, authorization, content-type");
        // si autorizzano GET e POST
        response.addHeader("Access-Control-Allow-Methods", "GET, POST");
    }
}
  • riga 9: è stata aggiunta l'intestazione HTTP [Content-Type] (le maiuscole e le minuscole non fanno differenza);
  • riga 11: è stato aggiunto il metodo HTTP [POST];

In questo modo i metodi [POST] vengono trattati allo stesso modo delle richieste [GET]. Ecco un esempio dei metodi URL e [/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) {
        // intestazioni CORS
        sendOptions(origin, response);
        // metodo di origine
        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 ottenuto è il seguente:

 

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

 

21.2.10. Conclusione

La nostra applicazione ora supporta le richieste interdominio. Queste possono essere autorizzate o meno tramite 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 {

    // richieste interdominio
    @Bean
    public boolean isCorsEnabled() {
        return true;
    }
...
}

21.3. Il progetto Eclipse [spring-cors-server-jpa-generic]

Il servizio web CORS verrà ora implementato dal progetto [spring-cors-server-jpa-generic], che si basa sul progetto [spring-security-server-jpa-generic], il quale gestisce gli accessi al database con Spring Data JPA:

Il progetto [spring-cors-server-jpa-generic] è stato ottenuto tramite clonazione del progetto precedentemente esaminato, [spring-cors-server-jdbc-generic].

  

Successivamente, occorre 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>

    <!-- plugin -->
    <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 protetto [spring-security-server-jpa-generic];

Alla fine, le dipendenze del progetto sono le seguenti:

  

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

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

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

Quindi si avvia il client generico:

e, tramite un browser, si richiedono i dati URL, [1] e GET. In [2], si nota che la versione abbreviata delle categorie restituite contiene il campo [entityType], che non era presente nella versione precedente JDBC.

Esamineremo altre due architetture CORS:

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

Implementeremo la seguente architettura:

Carichiamo i seguenti progetti:

  

Nota: premere Alt-F5 e rigenerare tutti i progetti Maven.

Avviare SGBD e DB2 e verificare che il database [dbproduitscategories] esista effettivamente. In caso contrario, crearlo (paragrafo 12.1.2).

Gli utenti vengono creati nel database [dbproduitscategories] con la configurazione di esecuzione [spring-security-create-users-hibernate-eclipselink]:

Image

Avviare quindi il servizio web CORS con la configurazione di esecuzione denominata [spring-cors-server-jpa-generic-hibernate-eclipselink] e il relativo client denominato [spring-cors-client-generic]:

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

 

Infine, richiamare in un browser la seguente URL:

 

21.5. Architettura CORS / JPA OpenJPA / Firebird

Ora implementeremo la seguente architettura:

Carichiamo i seguenti progetti:

  

Nota: premere Alt-F5 e rigenerare tutti i progetti Maven.

Avviare SGBD Firebird e verificare che il database [dbproduitscategories] esista effettivamente. In caso contrario, crearlo (paragrafo 14.1.2).

Si creano gli utenti nel database [dbproduitscategories] con la configurazione di esecuzione [spring-security-create-users-openjpa]:

Image

Avviare quindi il servizio web CORS con la configurazione di esecuzione denominata [spring-cors-server-jpa-generic-openjpa]:

Avviare il client CORS con la configurazione [spring-cors-client-generic]:

 

Compilare la base [dbproduitscategories] con i valori utilizzando la configurazione di esecuzione [spring-jdbc-generic-04-fillDataBase]:

 

Infine, richiamare in un browser la seguente URL: