21. Beheer van domeinoverstijgende toegang
21.1. Architecture
We gaan nu het probleem van verzoeken tussen domeinen bekijken. In het document [Tutoriel AngularJS / Spring 4] wordt een client/server-toepassing ontwikkeld waarbij de client een toepassing is AngularJS:
![]() |
- de pagina’s HTML / CSS / JS van de Angular-toepassing zijn afkomstig van de server [1];
- in [2] doet de service [dao] een verzoek aan een andere server, namelijk de server [2]. Welnu, dat wordt verboden door de browser die de Angular-applicatie uitvoert, omdat het een beveiligingslek is. De applicatie mag alleen de server opvragen waarvan ze afkomstig is, d.w.z. de server [1];
Eigenlijk is het niet helemaal juist om te zeggen dat de browser de Angular-applicatie verbiedt om de server [2] te benaderen. De browser vraagt in feite aan de server [2] of deze toestaat dat een client die niet van zijn eigen domein afkomstig is, hem benadert. Deze techniek wordt CORS (Cross-Origin Resource Sharing) genoemd. De server [2] geeft toestemming door specifieke HTTP-headers te verzenden.
Om de problemen te illustreren die zich kunnen voordoen, gaan we een client-server-toepassing maken waarbij:
- de server onze beveiligde web/jSON-server is;
- de client een eenvoudige pagina HTML is, voorzien van JavaScript-code die verzoeken naar de webserver / jSON verstuurt;
We gaan de volgende architectuur opzetten:
![]() |
- op [1] levert een webapplicatie de pagina’s HTML / jS;
- in [2] voert de browser het in de pagina's HTML ingebedde JavaScript uit om de beveiligde webservice [3] te raadplegen;
21.2. Het project [spring-cors-server-jdbc-generic]
21.2.1. Het opzetten van de werkomgeving
![]() |
- Laad de bovenstaande projecten. De projecten [spring-cors-*] bevinden zich in de map [<exemples>\spring-database-generic\spring-cors];
- druk op Alt-F5 en genereer alle Maven-projecten opnieuw;
Voer vervolgens de uitvoerconfiguratie met de naam [spring-cors-server-jdbc-generic] uit (SGBD en MySQL moeten worden gestart), die een webservice op poort 8081 start:
![]() |
Vul de basis [dbproduitscategories] in met de uitvoeringsconfiguratie met de naam [spring-jdbc-generic-04-fillDataBase]:
![]() |
Voer de uitvoeringsconfiguratie met de naam [spring-cors-client-generic] uit, die een tweede webapplicatie (op een andere Tomcat) op poort 8082 start:
![]() |
Vraag met een browser de URL [http://localhost:8082/client.html] op:
![]() |
- bij [1] wordt de korte versie van alle categorieën opgevraagd;
- met [2] wordt het antwoord jSON van de server opgevraagd;
21.2.2. Het project van de klant [spring-cors-client-generic]
![]() |
![]() |
Met het bestand [application.properties] kunnen we de poort van de webclient instellen. De inhoud ervan is als volgt:
server.port=8082
Dus:
- de client is een webapplicatie die beschikbaar is op URL [http://localhost:8082];
- de server is een webapplicatie die beschikbaar is op URL [http://localhost:8081];
Omdat de client niet via dezelfde poort wordt opgehaald als de server, doet zich het probleem van cross-domain-verzoeken voor. [http://localhost:8081] en [http://localhost:8082] zijn namelijk twee verschillende domeinen.
21.2.3. Maven-configuratie
Het project is een Maven-project met het volgende bestand [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 /> <!-- opzoeken van bovenliggende rol in de 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>
- regels 14-19: dit is een Spring Boot-project;
- regels 27-30: er wordt gebruikgemaakt van de afhankelijkheid [spring-boot-starter-web], die een Tomcat-server en Spring MVC meebrengt;
21.2.4. Basisbegrippen van jQuery en JavaScript
![]() |
De webapplicatie levert de volgende enige pagina op:
![]() |
De applicatie bevat JavaScript-code (jS) die in de browser wordt uitgevoerd. We zullen enkele basisbegrippen van JavaScript bespreken die ons helpen de code te begrijpen. De client voert HTTP-aanroepen uit met behulp van de bibliotheek jQuery [https://jquery.com/], die talrijke functies biedt die de ontwikkeling van JavaScript vergemakkelijken. We maken een statisch bestand HTML [jQuery.html] aan dat we in de map [static] plaatsen:
![]() |
Dit bestand heeft de volgende inhoud:
<!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>
- regel 6: import van jQuery;
- regels 10-12: een element van de pagina met id [element1]. We gaan met dit element aan de slag.
We moeten het bestand [jquery-2.1.3.min.js] downloaden. We vinden de nieuwste versie van jQuery bij URL en [http://jquery.com/download/]:

We plaatsen het gedownloade bestand in de map [static / js]:
![]() |
Zodra dit is gebeurd, roepen we de statische weergave [jQuery.html] op met Chrome [1-2]:
![]() |
Gebruik in Google Chrome de opdracht [Ctrl-Maj-I] om de ontwikkeltools [3] weer te geven. Via het tabblad [Console] [4] kunt u JavaScript-code uitvoeren. Hieronder geven we enkele JavaScript-opdrachten die u kunt invoeren, samen met een uitleg.
|
: genereert de verzameling van alle elementen met id [element1], dus normaal gesproken een verzameling van 0 of 1 element, omdat er twee identieke id's op één pagina HTML. | ![]() |
|
: wijst de tekst [blabla] toe aan alle elementen van de verzameling. Dit heeft tot gevolg dat de weergave van de pagina | ![]() |
|
verbergt de elementen van de collectie. De tekst [blabla] wordt niet meer weergegeven. | ![]() |
|
: toont de collectie weer. Hierdoor kunnen we zien dat het element met id [element1] het attribuut CSS style='display: none;' heeft, waardoor ervoor zorgt dat het element verborgen is. | |
|
: toont de elementen van de verzameling. De tekst [blabla] verschijnt weer. Het is het attribuut CSS style='display: block;' dat zorgt voor deze weergave. | ![]() |
|
: stelt een attribuut in voor alle elementen van de verzameling. Het attribuut is hier [style] en de waarde ervan [color: red]. De tekst [blabla] wordt rood weergegeven. | ![]() |
![]() | |
![]() |
Opgemerkt moet worden dat de URL van de browser tijdens al deze handelingen niet is veranderd. Er heeft geen communicatie met de webserver plaatsgevonden. Alles gebeurt binnen de browser. Laten we nu de broncode van de pagina bekijken:
<!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>
Dit is de oorspronkelijke tekst. Deze weerspiegelt op geen enkele manier de bewerkingen die we op het element in de regels 10-12 hebben uitgevoerd. Het is belangrijk om dit in gedachten te houden bij het debuggen van JavaScript. Het is dan vaak zinloos om de broncode van de weergegeven pagina te bekijken.
21.2.5. De code jS van de applicatie
Laten we teruggaan naar de code HTML van de pagina van de clienttoepassing die de webservice /jSON gaat opvragen:
![]() |
<!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">
<!-- methode 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 />
<!-- geplaatste waarde -->
<br /> Chaîne jSON à poster : <input type="text" id="posted" size="50" />
<!-- bevestigingsknop -->
<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>
- regel 6: we importeren de bibliotheek jQuery;
- regel 7: we importeren een stuk code dat we zelf gaan schrijven;
- regels 11, 15, 17, 21: noteer de [id]-identificaties van de componenten van de pagina. Het JavaScript verwijst naar deze componenten via deze identificaties;
De code [client.js] is als volgt:
// algemene gegevens
var url;
var posted;
var response;
var method;
function requestServer() {
// we halen de gegevens uit het formulier op
var urlValue = url.val();
var postedValue = posted.val();
method = document.forms[0].elements['method'].value;
// er wordt handmatig een Ajax-verzoek gedaan
if (method === "get") {
doGet(urlValue);
} else {
doPost(urlValue, postedValue);
}
}
function doGet(url) {
// handmatig een Ajax-verzoek uitvoeren
$.ajax({
headers : {
'Authorization' : 'Basic YWRtaW46YWRtaW4='
},
url : 'http://localhost:8081' + url,
type : 'GET',
dataType : 'tex/plain',
beforeSend : function() {
},
success : function(data) {
// tekstresultaat
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// systeemfout
response.text(jqXHR.responseText);
}
})
}
function doPost(url, posted) {
// we voeren handmatig een Ajax-aanroep uit
$.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) {
// tekstresultaat
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// systeemfout
response.text(jqXHR.responseText);
}
})
}
// bij het laden van het document
$(document).ready(function() {
// de referenties van de componenten op de pagina worden opgehaald
url = $("#url");
posted = $("#posted");
response = $("#response");
});
- regels 71-75: de code jS die wordt uitgevoerd zodra het document volledig in de browser is geladen;
- regels 73-75: de verwijzingen naar drie velden van het document HTML worden opgehaald;
- regels 2-5: globale variabelen die bekend zijn in alle functies die zijn gedefinieerd in het bestand jS;
- regel 9: de door de gebruiker ingevoerde URL wordt opgehaald;
- regel 10: de waarde die de gebruiker wil verzenden wordt opgehaald;
- regel 11: we halen de methode [get] of [post] op die moet worden gebruikt om de URL van regel 9 op te vragen:
- document verwijst naar het document dat door de browser is geladen, ook wel het DOM (Document Object Model) genoemd,
- document.forms[0] verwijst naar het eerste formulier van het document; een document kan er meerdere bevatten. Hier is er slechts één,
- document.forms[0].elements['method'] verwijst naar het element van het formulier dat het attribuut [name='method'] heeft. Er zijn er twee:
<input type="radio" id="get" name="method" value="get" checked="checked" />GET
<input type="radio" id="post" name="method" value="post" />POST
- (vervolg)
- document.forms[0].elements['method'].value is de waarde die wordt verzonden voor de component met het attribuut [name='method']. We weten dat de verzonden waarde de waarde is van het attribuut [value] van de aangevinkte keuzeknop. In dit geval zal dat dus een van de tekenreeksen ['get', 'post'] zijn;
- regels 13-18: afhankelijk van de te gebruiken methode HTTP wordt de methode [doGet] of [doPost] uitgevoerd;
- de methode jQuery [$.ajax] voert een aanroep HTTP uit;
- regels 23-25: er wordt verbinding gemaakt met een server die een header HTTP [Authorization: Basic code] vereist. We maken deze header aan voor de gebruiker [admin / admin], die als enige de server mag benaderen;
- regel 26: de gebruiker voert URL in van het type [/getAllLongCategories, /saveCategories, ...]. Deze URL moeten dus worden aangevuld;
- regel 27: de te gebruiken methode HTTP;
- regel 28: de server stuurt jSON terug. We geven het type [text/plain] op als resultaattype om het weer te geven zoals het is ontvangen;
- regel 33: weergave van het tekstantwoord van de server;
- regel 39: weergave van een eventuele foutmelding in tekstformaat;
- regel 44: de methode [doPost] ontvangt een tweede parameter, namelijk de waarde die moet worden verzonden;
- regel 52: om aan te geven dat de verzonden waarde de vorm van een tekenreeks jSON zal hebben;
21.2.6. Uitvoering van de client
De clienttoepassing is een console-applicatie die wordt gestart door de volgende uitvoerbare klasse [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);
}
}
- regel 6: de annotatie [@EnableAutoConfiguration] is een annotatie van het project [Spring Boot] (regel 4). Spring Boot controleert de archieven die in het classpath van het project aanwezig zijn. Dit zijn in dit geval alle Maven-afhankelijkheden die worden meegebracht door de enige afhankelijkheid van het bestand [pom.xml]:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Deze afhankelijkheid brengt een groot aantal archieven met zich mee, waaronder Spring MVC en een Tomcat-server. Vanwege de aanwezigheid van deze afhankelijkheden zal Spring Boot, met standaardwaarden, een Spring-project MVC configureren dat op Tomcat draait. De Tomcat-server wordt dan geconfigureerd om op poort 8080 te werken. Als je af wilt wijken van de standaardwaarden die Spring Boot heeft gekozen, kun je het bestand [application.properties] in de root van de Classpath gebruiken (alles wat zich in [src / main / resources] bevindt, staat in de root van de Classpath):
![]() |
We geven op dat de Tomcat-server op poort 8082 moet draaien, en wel als volgt:
server.port=8082
De lijst met bruikbare parameters is te vinden in [application.properties] tot en met URL (juni 2015) [http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html];
Terug naar de code van [Client.java]:
- regel 10: de methode [SpringApplication.run] zal de pagina [client.html] implementeren op de Tomcat-server die zich in het classpath van het project bevindt;
21.2.7. De URL [/getAllShortCategories]
![]() |
We starten:
- de beveiligde web-/json-server op poort 8081 (configuratie [spring-security-server-jdbc-generic]);
- de client van deze server op poort 8082 (configuratie [spring-cors-client-generic]);
vervolgens vragen we de URL [http://localhost:8082/client.html] [1] op:
![]() |
- in [2] voeren we een GET uit op de URL en [http://localhost:8081/getAllShortCategories];
We krijgen geen antwoord van de server. Als we naar de ontwikkelingsconsole van Chrome kijken (Ctrl-Shift-I), zien we een foutmelding:
![]() |
- in [1], we bevinden ons in het tabblad [Network];
- in [2] zien we dat het verzoek HTTP dat is gedaan niet [GET] is, maar [OPTIONS]. Bij een verzoek tussen domeinen controleert de browser bij de server of aan een aantal voorwaarden is voldaan door een verzoek HTTP [OPTIONS] te verzenden. In dit geval zijn de verzoeken die worden aangeduid door de stippen [5-6];
- In [5] vraagt de browser of het doel URL bereikbaar is via een GET. Het verzoek [Access-Control-Request-Method] vraagt om een antwoord met een header HTTP [Access-Control-Allow-Methods] waarin wordt aangegeven dat de gevraagde methode wordt geaccepteerd;
- in [6] stuurt de browser de header HTTP [Origin: http://localhost:8081]. Deze header vraagt om een antwoord in de vorm van een header HTTP [Access-Control-Allow-Origin] waarin wordt aangegeven dat de opgegeven bron wordt geaccepteerd;
- in [7] vraagt de browser of de headers HTTP, [accept] en [authorization] worden geaccepteerd. Het verzoek [Access-Control-Request-Headers] wacht op een antwoord met een header HTTP [Access-Control-Allow-Headers] waarin wordt aangegeven dat de gevraagde headers worden geaccepteerd;
- er treedt een fout op in [3]. Als je op het pictogram klikt, krijg je de foutmelding [4];
- in [4] geeft het bericht aan dat de server de header HTTP [Access-Control-Allow-Origin] niet heeft verzonden, die aangeeft of de herkomst van het verzoek wordt geaccepteerd;
- in [8] is te zien dat de server deze header inderdaad niet heeft verzonden. Daardoor heeft de browser geweigerd het aanvankelijk gevraagde verzoek HTTP GET uit te voeren;
We moeten de webserver aanpassen / jSON.
21.2.8. Een nieuwe webservice / json
We maken een nieuw Maven-project [spring-cors-server-jdbc-generic] aan:
![]() |
De Maven-configuratie van de nieuwe webservice is als volgt:
<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>
<!-- plug-ins -->
<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>
- regels 30-32: we gebruiken alles wat we tot nu toe hebben gedaan, op basis van het archief van de beveiligde web-/JSON-server;
Uiteindelijk zijn de afhankelijkheden als volgt:
![]() |
De configuratieklasse [AppConfig] is als volgt:
![]() |
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 {
// verzoeken tussen domeinen
@Bean
public boolean isCorsEnabled() {
return true;
}
...
}
- regel 12: de klasse is een Spring-configuratieklasse;
- regel 9: andere Spring-componenten zijn te vinden in het pakket [spring.cors.server.service];
- regel 14: de beans van het project [spring-security-server-jdbc-generic] worden geïmporteerd;
- regels 18-21: we maken een Spring-component met de naam [isCorsEnabled] die aangeeft of clients van buiten het domein van de server worden geaccepteerd of niet;
21.2.9. De controllers
De nieuwe webservice heeft vier controllers:
![]() |
- [CorsCategorieController] beheert de URL voor de verwerking van categorieën. Deze verwerkt uitsluitend de CORS-headers van webclients. In alle andere gevallen delegeert hij het werk aan de controller [CategorieController] van de afhankelijkheid [spring-webjson-server-jdbc-generic];
- [CorsProduitController] en [CorsAuthenticateController] doen hetzelfde door het werk te delegeren aan de controllers [ProduitController] van de afhankelijkheid [spring-webjson-server-jdbc-generic] en [AuthenticateController] van de afhankelijkheid[spring-security-server-jdbc-generic];
- [CorsController] dient om de gemeenschappelijke elementen van de drie voorgaande controllers te factoriseren;
21.2.9.1. De controller [CorsController]
De klasse [CorsController] is als volgt:
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;
// opties naar de client verzenden
public void sendOptions(String origin, HttpServletResponse response) {
// Cors toegestaan?
if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
return;
}
// we stellen de header CORS in
response.addHeader("Access-Control-Allow-Origin", origin);
// bepaalde headers toestaan
response.addHeader("Access-Control-Allow-Headers", "accept, authorization");
// GET wordt toegestaan
response.addHeader("Access-Control-Allow-Methods", "GET");
}
}
- regel 8: de klasse [CorsController] is een Spring-controller;
- regels 11-12: injectie van de bean [isCorsEnabled], die aangeeft of de headers CORS al dan niet moeten worden verwerkt;
- regels 15-26: de methode [sendOptions] zorgt voor het beantwoorden van verzoeken van clients die de headers CORS verzenden;
- regels 17-19: als de applicatie is geconfigureerd om verzoeken van andere domeinen te accepteren en als de afzender de header HTTP [Origin] heeft verzonden en als deze herkomst begint met [http://localhost], dan wordt het verzoek van een ander domein geaccepteerd, anders wordt het afgewezen;
- regel 21: als de client zich in het domein [http://localhost:port] bevindt, sturen we de header HTTP:
wat betekent dat de server de herkomst van de client accepteert;
- regels 22-25: we hebben twee specifieke HTTP-headers gemarkeerd in het verzoek HTTP [OPTIONS]:
Op de headers HTTP en [Access-Control-Request-X] reageert de server met een header HTTP en [Access-Control-Allow-X], waarin wordt aangegeven wat is toegestaan. De regels 22-25 herhalen slechts het verzoek van de client om aan te geven dat dit is geaccepteerd;
21.2.9.2. De 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) {
// oorspronkelijke methode
return categorieController.getAllShortCategories();
}
...
}
- regel 19: de annotatie [@RestController] maakt van de klasse zowel een Spring-component als een controller MVC die zelf zijn antwoorden naar de klant stuurt;
- regel 20: de klasse [CorsCategorieController] is een uitbreiding van de klasse [CorsController] die we zojuist hebben bekeken;
- regels 22-23: injectie van de controller [CategorieController categorieController] vanuit de afhankelijkheid [spring-webjson-server-jdbc-generic];
- regels 25-29: verwerken de URL en [/cors-getAllShortCategories] wanneer deze worden aangevraagd met de opdracht HTTP en [OPTIONS]. Volgens afspraak hebben we besloten dat webclients die de URL [/U] van de beveiligde webservice willen aanroepen, in feite de URL [/cors-U] moeten aanroepen. De geïmplementeerde webservice zal dus twee soorten URL hebben:
- [/U]: voor niet-webclients;
- [/cors-U]: voor webclients;
- regel 25: de methode [/cors-getAllShortCategories] accepteert als parameters:
- het object [@RequestHeader(value = "Origin", required = false)], dat de header HTTP [Origin] van het verzoek ophaalt. Deze header is verzonden door de afzender van het verzoek:
Er wordt aangegeven dat de header HTTP [Origin] optioneel is [required = false]. In dat geval, als de header ontbreekt, krijgt de parameter [String origin] de waarde null. Bij [required = true], de standaardwaarde, wordt er een uitzondering gegenereerd als de header ontbreekt. We wilden dit scenario vermijden;
- (vervolg)
- het object [HttpServletResponse response] dat wordt teruggestuurd naar de klant die het verzoek heeft ingediend;
Deze twee parameters worden door Spring ingevuld;
- regel 28: de verwerking van het verzoek wordt gedelegeerd aan de methode [sendOptions] van de bovenliggende klasse [CorsController];
- regels 31-36: de methode [getAllShortCategories] verwerkt de URL en [/cors-getAllShortCategories] wanneer deze wordt aangevraagd met een GET;
- regel 35: de taak wordt gedelegeerd aan de methode [CategorieController.getAllShortCategories] van de afhankelijkheid [spring-webjson-server-jdbc-generic];
We zijn nu klaar voor nieuwe tests. We lanceren de nieuwe versie van de webservice en ontdekken dat het probleem nog steeds bestaat. Er is niets veranderd. Als we in regel 28 hierboven een console-uitvoer plaatsen, wordt deze nooit weergegeven, wat aantoont dat de methode [corsGetAllShortCategories] uit regel 25 nooit wordt aangeroepen.
Na wat onderzoek ontdekken we dat Spring MVC de opdrachten HTTP en [OPTIONS] zelf verwerkt met een standaardverwerking. Het is dus altijd Spring die reageert en nooit de methode [corsGetAllShortCategories] op regel 25. Dit standaardgedrag van Spring MVC kan worden gewijzigd. We passen de bestaande klasse [AppConfig] aan:
![]() |
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 {
// verzoeken tussen domeinen
@Bean
public boolean isCorsEnabled() {
return true;
}
@Autowired
private DispatcherServlet dispatcherServlet;
@PostConstruct
public void init(){
// de applicatie verwerkt de verzoeken zelf HTTP [OPTIONS]
dispatcherServlet.setDispatchOptionsRequest(true);
}
}
- regels 23-24: we injecteren de component [DispatcherServlet dispatcherServlet] die is gedefinieerd in de afhankelijkheid [spring-webjson-server-jdbc-generic];
- regels 26-30: de annotatie [@PostConstruct] zorgt ervoor dat de methode [init] wordt uitgevoerd na het instantiëren van de klasse [AppConfig] en na de injecties door Spring;
- regel 29: er wordt gevraagd dat de servlet de commando's HTTP en [OPTIONS] doorstuurt naar de applicatie;
We voeren de tests opnieuw uit met deze nieuwe configuratie. We krijgen het volgende resultaat:
![]() |
- in [1] zien we dat er twee verzoeken HTTP zijn naar URL en [http://localhost:8080/getAllCategories];
- in [2], de verzoek [OPTIONS];
- in [3] de drie headers HTTP die we zojuist in het antwoord van de server hebben geconfigureerd;
Laten we nu het tweede verzoek bekijken:
![]() |
- in [1], het onderzochte verzoek;
- in [2], dit is het verzoek GET. Dankzij het eerste verzoek [OPTIONS] heeft de browser de gevraagde informatie ontvangen. Hij voert nu het aanvankelijk gevraagde verzoek [GET] uit;
- in [3], het antwoord van de server;
- in [4] stuurt de server jSON;
- in [5] is er een fout opgetreden;
- in [6], de foutmelding;
Het is moeilijker uit te leggen wat hier is gebeurd. Het antwoord [3] van de server is normaal [HTTP/1.1 200 OK]. We zouden dus het gevraagde document moeten hebben. Het is mogelijk dat de server het document wel heeft verzonden, maar dat de browser het gebruik ervan verhindert omdat hij wil dat het antwoord voor het verzoek GET ook de header HTTP [Access-Control-Allow-Origin:http://localhost:8081] bevat.
We passen de methode aan die de GET verwerkt vanuit de URL en [/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);
// oorspronkelijke methode
return categorieController.getAllShortCategories();
}
- regel 5: net als bij de aanvraag HTTP [OPTIONS], zal de server de headers HTTP CORS verzenden voor een verzoek HTTP [GET];
Na deze wijziging zijn de resultaten als volgt:
![]() |
We hebben inderdaad de verkorte versie van alle categorieën verkregen.
21.2.9.3. De URL [GET]
In de controllers [CorsCategorieController, CorsProduitController, CorsAuthenticateController], volgt de code van de acties die de aangevraagde URL met een [GET] verwerken, het patroon van de acties die eerder de URL en [/cors-getAllShortArticles] hebben verwerkt. De lezer kan de code controleren in de voorbeelden die bij dit document zijn geleverd. Hier volgt een voorbeeld voor de URL en [/cors-getAllLongProduits] van de 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) {
// kopteksten CORS
sendOptions(origin, response);
// oorspronkelijke methode
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. De URL en [POST]
Laten we het volgende geval eens bekijken:
![]() |
- er wordt een POST [1] naar de URL [2] gestuurd;
- in [3], de geplaatste waarde. Dit is de reeks jSON van een categorie zonder producten;
- uiteindelijk willen we een categorie aanmaken met de naam [categorie[2]];
We wijzigen voorlopig geen code. Het verkregen resultaat is dan als volgt:
![]() |
- in [1]; net als bij de verzoeken [GET] wordt er door de browser een verzoek [OPTIONS] gedaan;
- bij [2] vraagt de browser om toestemming voor een verzoek naar [POST]. Voorheen was dit [GET];
- in [3] vraagt hij toestemming om de headers HTTP en [accept, authorization, content-type] te verzenden. Voorheen waren er alleen de eerste twee headers;
- in [4] verleent de webservice niet alle gevraagde machtigingen, wat de fout [5] veroorzaakt;
We passen de methode [CorsController.sendOptions] als volgt aan:
public void sendOptions(String origin, HttpServletResponse response) {
// Cors toegestaan?
if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
return;
}
// de header wordt ingesteld CORS
response.addHeader("Access-Control-Allow-Origin", origin);
// bepaalde headers worden toegestaan
response.addHeader("Access-Control-Allow-Headers", "accept, authorization, content-type");
// GET en POST worden toegestaan
response.addHeader("Access-Control-Allow-Methods", "GET, POST");
}
}
- regel 9: we hebben de header HTTP [Content-Type] toegevoegd (hoofdletters en kleine letters maken niet uit);
- regel 11: we hebben de methode HTTP [POST] toegevoegd;
Hierdoor worden de methoden [POST] op dezelfde manier verwerkt als de verzoeken [GET]. Hier volgt een voorbeeld van de URL en [/cors-saveCategories] in de 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);
// oorspronkelijke methode
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);
}
Na deze wijzigingen is het resultaat als volgt:
![]() |
De categorie [categorie[2]] is inderdaad aan de database toegevoegd. De SGBD heeft er de primaire sleutel 226 aan toegewezen. Dit kan worden gecontroleerd met de methode GET [/cors-getAllShortCategories]:
![]() |
21.2.10. Conclusie
Onze applicatie ondersteunt nu domeinoverschrijdende verzoeken. Deze kunnen al dan niet worden toegestaan via de configuratie in de klasse [AppConfig]:
package spring.cors.server.config;
...
@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ spring.security.config.AppConfig.class })
public class AppConfig {
// verzoeken tussen domeinen
@Bean
public boolean isCorsEnabled() {
return true;
}
...
}
21.3. Het Eclipse-project [spring-cors-server-jpa-generic]
De webservice CORS wordt nu geïmplementeerd door het project [spring-cors-server-jpa-generic], dat voortbouwt op het project [spring-security-server-jpa-generic], dat de toegang tot de database beheert met Spring Data JPA:
![]() |
Het project [spring-cors-server-jpa-generic] is een kopie van het eerder besproken project [spring-cors-server-jdbc-generic].
![]() |
Vervolgens moeten er twee wijzigingen worden aangebracht. De eerste bevindt zich in het bestand [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>
<!-- plug-ins -->
<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>
- regels 30-32: de afhankelijkheid van de beveiligde webservice [spring-security-server-jpa-generic];
Uiteindelijk zijn de afhankelijkheden van het project als volgt:
![]() |
Opmerking: druk op Alt-F5 en genereer vervolgens alle projecten opnieuw
De tweede wijziging is het bijwerken van de imports in de klassen die fouten melden: [Alt-Maj-O].
Dat is alles. We starten de webservice CORS met de uitvoeringsconfiguratie [spring-cors-server-jpa-generic-hibernate-eclipselink]:
![]() | ![]() |
Vervolgens starten we de generieke client:
![]() |
en met een browser vragen we de URL, [1] en GET op. In [2] zien we dat de verkorte versie van de geretourneerde categorieën het veld [entityType] bevat, dat in de vorige versie JDBC ontbrak.
We zullen twee andere architecturen CORS behandelen:
- architectuur CORS / JPA EclipseLink / DB2;
- architectuur CORS / JPA OpenJpa / Firebird;
21.4. Architectuur CORS / JPA EclipseLink / DB2
We gaan de volgende architectuur implementeren:
![]() |
We laden de volgende projecten:
![]() |
Opmerking: druk op Alt-F5 en genereer alle Maven-projecten opnieuw.
Start SGBD en DB2 en controleer of de database [dbproduitscategories] daadwerkelijk bestaat. Zo niet, maak deze dan aan (paragraaf 12.1.2).
We maken de gebruikers aan in de database [dbproduitscategories] met de uitvoeringsconfiguratie [spring-security-create-users-hibernate-eclipselink]:
![]() | ![]() |

Start vervolgens de webservice CORS met de uitvoeringsconfiguratie [spring-cors-server-jpa-generic-hibernate-eclipselink] en de bijbehorende client [spring-cors-client-generic]:
![]() | ![]() |
Vul de database [dbproduitscategories] met waarden met behulp van de uitvoeringsconfiguratie [spring-jdbc-generic-04-fillDataBase]:
![]() |
Vraag ten slotte in een browser de volgende URL op:
![]() |
21.5. Architectuur CORS / JPA OpenJPA / Firebird
We gaan nu de volgende architectuur implementeren:
![]() |
We laden de volgende projecten:
![]() |
Opmerking: druk op Alt-F5 en genereer alle Maven-projecten opnieuw.
Start de Firebird-database SGBD en controleer of de database [dbproduitscategories] daadwerkelijk bestaat. Zo niet, maak deze dan aan (paragraaf 14.1.2).
We maken de gebruikers aan in de database [dbproduitscategories] met de uitvoeringsconfiguratie [spring-security-create-users-openjpa]:
![]() | ![]() |

Start vervolgens de webservice CORS met de uitvoeringsconfiguratie met de naam [spring-cors-server-jpa-generic-openjpa]:
![]() | ![]() |
Start de client CORS met de configuratie [spring-cors-client-generic]:
![]() |
Vul de database [dbproduitscategories] met waarden met behulp van de uitvoerconfiguratie [spring-jdbc-generic-04-fillDataBase]:
![]() |
Vraag ten slotte in een browser de volgende URL op:
![]() |


























































