18. [Cours]: Verwaltung des domänenübergreifenden Zugriffs
Stichworte: CORS (Cross-Origin Resource Sharing).
Dieses Kapitel weicht etwas vom TD ab. Es wurde beibehalten, da es eine Einführung in die Webprogrammierung und die JavaScript-Programmierung bietet. Man sollte hier bedenken, dass eines der Ziele dieses TD darin besteht, Konzepte vorzustellen, die bei der JEE-Entwicklung häufig zum Einsatz kommen, d. h. bei der Webentwicklung auf Basis von Java-Frameworks. Hier wird der in der Studie zur Produkt- und Kategoriendatenbank verwendete Webserver ergänzt, damit er domänenübergreifende Anfragen akzeptieren kann.
Im Dokument [Tutoriel AngularJS / Spring 4] wird eine Client-Server-Anwendung entwickelt, bei der der Client eine AngularJS-Anwendung ist:
![]() |
- Die Seiten HTML / CSS / JS der Angular-Anwendung stammen vom Server [1];
- In [2] sendet der Dienst [dao] eine Anfrage an einen anderen Server, nämlich den Server [2]. Das wird jedoch vom Browser, der die Angular-Anwendung ausführt, unterbunden, da es sich um eine Sicherheitslücke handelt. Die Anwendung darf nur den Server abfragen, von dem sie stammt, d. h. den Server [1];
Tatsächlich ist es nicht richtig zu sagen, dass der Browser der Angular-Anwendung verbietet, den Server [2] abzufragen. Sie fragt ihn vielmehr ab, um zu erfahren, ob er einem Client, der nicht von ihm selbst stammt, erlaubt, ihn abzufragen. Diese Technik der gemeinsamen Nutzung wird als CORS (Cross-Origin Resource Sharing) bezeichnet. Der Server [2] erteilt seine Zustimmung, indem er bestimmte HTTP-Header sendet.
Wir werden die folgende Architektur erstellen:
![]() |
- In [1] liefert eine Webanwendung die Seiten HTML / jS aus;
- in [2] führt der Browser das in den Seiten HTML eingebettete JavaScript aus, um den gesicherten Webdienst [3] abzufragen;
18.1. Support
![]() |
Die Projekte dieses Kapitels befinden sich im Ordner [support / chap-18].
18.2. Das Kundenprojekt
Wir erstellen das folgende Eclipse-Projekt:
![]() |
18.3. Maven-Konfiguration
Das Projekt ist ein Maven-Projekt mit der folgenden Datei: [pom.xml]
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.webjson</groupId>
<artifactId>intro-server-webjson-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>intro-server-webjson-01</name>
<description>démo spring mvc</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>istia.st.springdata</groupId>
<artifactId>intro-spring-data-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- Zeilen 11–15: Es handelt sich um ein Spring-Boot-Projekt;
- Zeilen 23–26: Es wird die Abhängigkeit [spring-boot-starter-web] verwendet, die einen Tomcat-Server und Spring MVC mitbringt;
18.4. Spring-Konfiguration
![]() |
Die Klasse [WebConfig], die das Spring-Projekt konfiguriert, sieht wie folgt aus:
package spring.cors.client.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// -------------------------------- Konfiguration der Ebene [web]
@Autowired
private ApplicationContext context;
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet servlet = new DispatcherServlet((WebApplicationContext) context);
return servlet;
}
@Bean
public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new ServletRegistrationBean(dispatcherServlet, "/*");
}
@Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory("", 8081);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/*.html").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/*.js").addResourceLocations("classpath:/static/js/");
}
}
- Zeile 15: Die Klasse konfiguriert ein Spring-Projekt MVC;
- Zeile 16: Die Klasse erweitert die Klasse [WebMvcConfigurerAdapter], um einige ihrer Methoden neu zu definieren;
- Zeilen 18–36: Diese Beans sind uns bereits begegnet, beispielsweise in Abschnitt 13.5.3.1. In Zeile 35 ist zu beachten, dass der Webdienst auf Port 8081 laufen wird;
- Zeilen 38–42: Mit der Methode [addResourceHandlers] lassen sich statische Ressourcen definieren, d. h. Ressourcen, die nicht von der Methode [DispatcherServlet] in Zeile 23 verarbeitet werden;
- Zeile 40: Jede Anfrage nach einer Ressource mit der Endung .html erhält als Antwort die in der Anfrage angeforderte Datei, die im Ordner „[static]“ im Classpath des Projekts gefunden wurde;
- Zeile 41: Jede Anfrage nach einer Ressource mit der Endung .js wird mit der in der Anfrage angeforderten JavaScript-Datei beantwortet, die im Ordner „[static/js]“ im Classpath des Projekts gefunden wird;
![]() |
18.5. Grundlagen von jQuery und JavaScript
Die Seite HTML des Clients sieht wie folgt aus:
![]() |
Sie enthält JavaScript-Code (jS), der im Browser ausgeführt wird. Wir werden einige Grundlagen von JavaScript vorstellen, die uns helfen werden, den Code zu verstehen. Der Client wird HTTP-Aufrufe mithilfe der Bibliothek jQuery [https://jquery.com/] durchführen, die zahlreiche Funktionen zur Erleichterung der JavaScript-Entwicklung bereitstellt. Wir erstellen eine statische Datei HTML [jQuery.html], die wir im Ordner [static] ablegen:
![]() |
Diese Datei hat folgenden Inhalt:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>JQuery-01</title>
<script type="text/javascript" src="/jquery-2.1.3.min.js"></script>
</head>
<body>
<h3>Rudiments de JQuery</h3>
<div id="element1">Elément 1</div>
</body>
</html>
- Zeile 6: Import von jQuery;
- Zeilen 10–12: ein Element der Seite mit der ID [element1]. Wir werden mit diesem Element experimentieren.
Wir müssen die Datei [jquery-2.1.3.min.js] herunterladen. Die neueste Version von jQuery finden wir unter URL [http://jquery.com/download/]:

Die heruntergeladene Datei wird in den Ordner „[static / js]“ verschoben, und Zeile 6 der Datei „HTML“ wird entsprechend der installierten Version angepasst.
Anschließend rufen wir die statische Ansicht [jQuery.html] mit Chrome [1-2] auf:
![]() |
Rufen Sie in Google Chrome die Datei [Ctrl-Maj-I] auf, um die Entwicklertools [3] anzuzeigen. Über die Registerkarte [Console] [4] können Sie JavaScript-Code ausführen. Im Folgenden geben wir JavaScript-Befehle an, die eingegeben werden müssen, und erläutern diese.
|
: Erzeugt die Sammlung aller Elemente mit der ID [element1], also normalerweise eine Sammlung von 0 oder 1 Element , da auf einer Seite keine zwei identischen IDs vorkommen können: HTML. | ![]() |
|
: Weist allen Elementen der Sammlung zu. Dies führt dazu, dass der angezeigten Inhalt der Seite | ![]() |
|
blendet die Elemente der Sammlung aus. Der Text „[blabla]“ wird nicht mehr angezeigt. | ![]() |
|
: Zeigt die Sammlung wieder an. Dadurch können wir sehen, dass das Element mit der ID [element1] das Attribut CSS style='display: none;', das bewirkt, dazu führt, dass das Element ausgeblendet wird. | |
|
: Zeigt die Elemente der Sammlung an. Der Text [blabla] erscheint wieder. Es ist das Attribut CSS style='display: block;', das diese Anzeige. | ![]() |
|
: Weist allen Elementen der Sammlung zu. Das Attribut lautet hier [style] und sein Wert [color: red]. Der Text [blabla] wird rot dargestellt. | ![]() |
![]() | |
![]() |
Es ist zu beachten, dass sich der Wert „URL“ des Browsers während all dieser Vorgänge nicht verändert hat. Es fand kein Datenaustausch mit dem Webserver statt. Alles geschieht innerhalb des Browsers. Sehen wir uns nun den Quellcode der Seite an:
![]() | ![]() |
Das ist der ursprüngliche Text. Er spiegelt in keiner Weise die Änderungen wider, die wir an dem Element in den Zeilen 10–12 vorgenommen haben. Das sollte man beim Debuggen von JavaScript unbedingt beachten. Oft ist es daher sinnlos, den Quellcode der angezeigten Seite einzusehen.
18.6. Der JavaScript-Code der Anwendung
Kehren wir zur Seite der Client-Anwendung zurück, die den Webservice /jSON abfragen wird:
![]() |
![]() |
Der Code HTML dieser Seite lautet wie folgt:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Spring MVC</title>
<script type="text/javascript" src="/jquery-2.1.3.min.js"></script>
<script type="text/javascript" src="/client.js"></script>
</head>
<body>
<h2>Client du service web / jSON</h2>
<form id="formulaire">
<!-- Benutzername -->
Identifiant :
<!-- -->
<input type="text" id="identifiant" name="identifiant" value="" />
<!-- Passwort -->
<br /> <br /> Mot de passe :
<!-- -->
<input type="text" id="password" name="password" value="" />
<!-- Methode HTTP -->
<br /> <br /> Méthode HTTP :
<!-- -->
<input type="radio" id="get" name="method" value="get"
checked="checked" />GET
<!-- -->
<input type="radio" id="post" name="method" value="post" />POST
<!-- URL -->
<br /> <br />URL cible (commençant par /): <input type="text"
id="url" size="30"><br />
<!-- Eingebener Wert -->
<br /> Chaîne jSON à poster : <input type="text" id="posted"
size="50" />
<!-- Bestätigungsschaltfläche -->
<br /> <br /> <input type="button" value="Valider"
onclick="javascript:requestServer()"></input>
</form>
<hr />
<h2>Réponse du serveur</h2>
<div id="response"></div>
</body>
</html>
- Zeile 6: Wir importieren die Bibliothek jQuery;
- Zeile 7: Es wird ein Code importiert, den wir noch schreiben werden;
- Zeilen 15, 19, 26, 29, 31: Notieren Sie sich die Bezeichner [id] der Komponenten der Seite. Das JavaScript verweist über diese Bezeichner auf diese Komponenten;
Der Code [client.js] lautet wie folgt:
// globale Daten
var url;
var posted;
var response;
var method;
var baseUrl = 'http://localhost:8080';
var identifiant;
var password;
var authorizationHeader;
function requestServer() {
// die Informationen werden abgerufen
var urlValue = url.val();
var postedValue = posted.val();
var identifiantValue = identifiant.val();
var passwordValue = password.val();
var method = document.forms[0].elements['method'].value;
authorizationCode = btoa(identifiantValue + ':' + passwordValue);
// die vorherige Antwort wird gelöscht
response.text("");
// Man führt manuell einen Ajax-Aufruf durch
if (method === "get") {
doGet(urlValue);
} else {
doPost(urlValue, postedValue);
}
}
function doGet(url) {
// Man führt einen manuellen Ajax-Aufruf durch
$.ajax({
headers : {
'Authorization':'Basic '+authorizationCode
},
url : baseUrl + url,
type : 'GET',
dataType : 'text',
beforeSend : function() {
},
success : function(data) {
// Textergebnis
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// Systemfehler
response.text(JSON.stringify(jqXHR.statusCode()));
}
})
}
function doPost(url, posted) {
// Es wird manuell ein Ajax-Aufruf durchgeführt
$.ajax({
headers : {
'Autorisierung':'Basic '+authorizationCode
},
url : baseUrl + url,
type : 'POST',
contentType : 'application/json; charset=UTF-8',
data : posted,
dataType : 'text',
beforeSend : function() {
},
success : function(data) {
// Textergebnis
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// Systemfehler
response.text(JSON.stringify(jqXHR.statusCode()));
}
})
}
// beim Laden des Dokuments
$(document).ready(function() {
// Hier werden die Referenzen der Komponenten der Seite abgerufen
identifiant = $("#identifiant");
password = $("#password");
url = $("#url");
posted = $("#posted");
response = $("#response");
});
- Zeilen 80–87: Code jS, der nach dem Laden des Dokuments im Browser ausgeführt wird;
- Zeilen 81–86: Die Referenzen der verschiedenen Elemente des Dokuments HTML werden über ihre Kennung [id] abgerufen;
- Zeilen 2–9: Globale Variablen, die in allen in der Datei jS definierten Funktionen bekannt sind;
- Zeile 13: Die vom Benutzer eingegebene URL wird abgerufen;
- Zeile 14: Der Wert, den der Benutzer senden möchte, wird abgerufen (leer bei der Operation GET);
- Zeile 15: Die vom Benutzer eingegebene Benutzer-ID wird abgerufen;
- Zeile 16: Das Passwort wird abgerufen;
- Zeile 17: Es wird ermittelt, ob [get] oder [post] verwendet werden soll, um den Wert aus Zeile 9 abzufragen:
- [document] bezeichnet das vom Browser geladene Dokument, das sogenannte DOM (Document Object Model),
- [document.forms[0]] bezeichnet das erste Formular des Dokuments; ein Dokument kann mehrere Formulare enthalten. Hier gibt es nur eines,
- [document.forms[0].elements['method']] bezeichnet das Formularelement, das das Attribut [name='method'] hat. Es gibt zwei davon:
<input type="radio" id="get" name="method" value="get" checked="checked" />GET
<input type="radio" id="post" name="method" value="post" />POST
- (Fortsetzung)
- [document.forms[0].elements['method'].value] ist der Wert, der für die Komponente mit dem Attribut [name='method'] übermittelt wird. Es ist bekannt, dass der übermittelte Wert dem Wert des Attributs [value] des markierten Optionsfelds entspricht. In diesem Fall handelt es sich also um eine der Zeichenfolgen ['get', 'post'];
- Zeile 18: Wir erstellen die Base74-Kodierung der Zeichenkette „identifiant:password“. Diese kodierte Zeichenkette wird im Header HTTP [Authorization] verwendet, den wir an den Server senden, um die Anfrage zu authentifizieren;
- Zeilen 22–26: Je nachdem, welche Methode HTTP verwendet werden soll, wird die Methode [doGet] oder [doPost] ausgeführt;
- die Methode jQuery [$.ajax] führt einen Aufruf von HTTP durch;
- Zeilen 32–34: Es wird eine Verbindung zu einem Server hergestellt, der einen Header vom Typ HTTP oder [Authorization: Basic code] verlangt;
- Zeile 35: Der Benutzer gibt URL vom Typ [/cors-getAllCategories,/cors-addProduits, ...] ein. Diese URL müssen daher mit dem URL des Servers aus Zeile 6 ergänzt werden;
- Zeile 36: Zu verwendende Methode HTTP;
- Zeile 37: Der Server sendet jSON zurück. Als Ergebnistyp wird der Typ [text] angegeben, um ihn so anzuzeigen, wie er empfangen wurde;
- Zeile 42: Anzeige der Textantwort des Servers;
- Zeilen 48–49: Anzeige einer eventuellen Fehlermeldung;
- Zeile 53: Die Methode [doPost] erhält einen zweiten Parameter, nämlich den zu übermittelnden Wert;
- Zeile 61: Angabe, dass der gesendete Wert in Form einer Zeichenkette „jSON“ übermittelt wird;
18.7. Ausführung des Clients
Die Client-Anwendung ist eine Spring-Boot-Anwendung, die von der folgenden ausführbaren Klasse [Boot] gestartet wird:
![]() |
package spring.cors.client.boot;
import org.springframework.boot.SpringApplication;
import spring.cors.client.config.WebConfig;
public class Boot {
public static void main(String[] args) {
SpringApplication.run(WebConfig.class, args);
}
}
- Zeile 10: Die Methode [SpringApplication.run] verwendet die Konfigurationsdatei [WebConfig]. Die Seite [client.html] wird auf dem Tomcat-Server bereitgestellt, der sich im Classpath des Projekts befindet;
18.8. L'URL [/getAllCategories]
Wir starten:
- den Web-/JSON-Server auf Port 8080;
- den Client dieses Servers auf Port 8081;
anschließend fragen wir die URL, [http://localhost:8081/client.html] und [1] ab:
![]() |
- in [2], wir führen ein GET auf dem URL [http://localhost:8080/getAllCategories] durch;
Wir erhalten keine Antwort vom Server. Wenn man sich die Entwicklerkonsole von Chrome (Strg-Umschalt-I) ansieht, wird ein Fehler angezeigt:
![]() |
- in [1], wir befinden uns auf der Registerkarte [Network];
- bei [2] sieht man, dass die gestellte Anfrage HTTP nicht [GET], sondern [OPTIONS] ist. Bei einer domänenübergreifenden Anfrage überprüft der Browser beim Server, ob bestimmte Bedingungen erfüllt sind, indem er eine Anfrage HTTP [OPTIONS] sendet. In diesem Fall handelt es sich bei den Anfragen um diejenigen, auf die die Markierungen [5-6] verweisen;
- In [5] fragt der Browser ab, ob das Ziel URL mit einem GET erreicht werden kann. Der Header der Anfrage [Access-Control-Request-Method] fordert eine Antwort mit einem Header HTTP [Access-Control-Allow-Methods] an, der angibt, dass die angeforderte Methode akzeptiert wird;
- In [6] sendet der Browser den Header HTTP [Origin: http://localhost:8081]. Dieser Header fordert eine Antwort in Form eines Headers HTTP [Access-Control-Allow-Origin] an, der angibt, dass die angegebene Herkunft akzeptiert wird;
- In [7] fragt der Browser, ob die Header HTTP, [accept] und [authorization] akzeptiert werden. Der Header der Anfrage [Access-Control-Request-Headers] erwartet eine Antwort mit einem Header HTTP [Access-Control-Allow-Headers], der angibt, dass die angeforderten Header akzeptiert werden;
- bei [3] tritt ein Fehler auf. Beim Klicken auf das Symbol wird der Fehler [4] angezeigt;
- in [4] besagt die Meldung, dass der Server den Header HTTP [Access-Control-Allow-Origin] nicht gesendet hat, der angibt, ob die Herkunft der Anfrage akzeptiert wird;
- In [8] lässt sich feststellen, dass der Server diesen Header tatsächlich nicht gesendet hat. Daher hat der Browser die ursprünglich angeforderte Anfrage HTTP GET abgelehnt;
Wir müssen den Webserver anpassen / jSON.
18.9. Der neue Webdienst /json
Wir erstellen ein neues Maven-Projekt [intro-spring-cors-server-jpa]:
![]() | ![]() |
18.9.1. Maven-Konfiguration
Die Maven-Konfiguration des neuen Webdienstes lautet wie folgt:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.cors</groupId>
<artifactId>spring-cors-server-jpa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cors-server-jpa</name>
<description>démo spring cors</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>istia.st.spring.security</groupId>
<artifactId>intro-spring-security-server-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- Plugins -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- Zeilen 23–27: Wir nutzen alle bisherigen Arbeitsergebnisse, indem wir auf das Archiv des gesicherten Webserver-/JSON-Servers zurückgreifen;
18.9.2. Spring-Konfiguration
Die Konfigurationsklasse [AppConfig] lautet wie folgt:
![]() |
package spring.cors.server.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import spring.security.config.SecurityConfig;
@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ SecurityConfig.class })
public class AppConfig {
// domänenübergreifende Anfragen
@Bean
public boolean isCorsEnabled() {
return true;
}
}
- Zeile 10: Die Klasse ist eine Spring-Konfigurationsklasse;
- Zeile 11: Weitere Spring-Komponenten sind im Paket [spring.cors.server.service] zu finden;
- Zeilen 16–19: Wir erstellen eine Spring-Komponente namens [isCorsEnabled], die angibt, ob Clients außerhalb der Serverdomäne akzeptiert werden oder nicht;
18.9.3. Die Klasse [AbstractCorsController]
Die Klasse [AbstractCorsController], die als übergeordnete Klasse für alle Controller dieser Anwendung dient:
![]() |
Ihr Code lautet wie folgt:
package spring.cors.server.service;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class AbstractCorsController {
@Autowired
private boolean isCorsEnabled;
// Senden der Optionen an den Client
public void setHeaders(String origin, HttpServletResponse response) {
// CORS erlaubt?
if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
return;
}
// Der Header CORS wird festgelegt
response.addHeader("Access-Control-Allow-Origin", origin);
// Bestimmte Header werden zugelassen
response.addHeader("Access-Control-Allow-Headers", "accept, authorization");
// GET wird zugelassen
response.addHeader("Access-Control-Allow-Methods", "GET");
}
}
- Zeile 7: Die Klasse [CorsController] ist abstrakt, da sie dazu gedacht ist, erweitert und nicht instanziiert zu werden;
- Zeilen 13–24: Die Methode [setHeaders] fügt in die an den Client gerichtete Antwort [HttpServletResponse response] (Zeile 13) die von domänenübergreifenden Anfragen geforderten Header HTTP ein;
- Zeile 33: Die Methode [/setHeaders] akzeptiert als Parameter:
- die Zeichenfolge [origin], die im Header HTTP [Origin] der domänenübergreifenden Anfragen enthalten ist:
Hier hätte der Parameter [origin] in Zeile 13 den Wert [http://localhost:8081]. Falls die Anfrage den Header HTTP [Origin] nicht enthält, wird dafür gesorgt, dass [origin==null] vorhanden ist;
- (Fortsetzung)
- das Objekt [HttpServletResponse response], das an den Kunden zurückgesendet wird, der die Anfrage gestellt hat;
Diese beiden Parameter werden von Spring eingefügt;
- Zeilen 15–175: Wenn die Anwendung so konfiguriert ist, dass sie domänenübergreifende Anfragen akzeptiert, und wenn der Absender den Header HTTP [Origin] gesendet hat und diese Herkunft mit [http://localhost] beginnt, dann wird die domänenübergreifende Anfrage akzeptiert, andernfalls wird sie abgelehnt;
- Zeile 19: Befindet sich der Client in der Domäne [http://localhost:port], wird der Header HTTP gesendet:
was bedeutet, dass der Server die Herkunft des Clients akzeptiert;
- Zeile 21: Wir haben zwei spezielle Header HTTP in der Anfrage HTTP [OPTIONS] angegeben:
Auf die Header HTTP und [Access-Control-Request-X] antwortet der Server mit den Headern HTTP und [Access-Control-Allow-X], in denen er angibt, was zulässig ist. Die Zeilen 20–23 geben lediglich die Anfrage des Clients wieder, um anzuzeigen, dass sie akzeptiert wurde;
18.9.4. Der Controller [MyControllerWithHttpOptions]
Um den in Abschnitt 13.5.3 behandelten ungesicherten Webserver / jSON [intro-server-webjson-01] nicht ändern zu müssen, werden wir einen neuen Controller erstellen, der anstelle des ungesicherten Servers die URL und [/url] verarbeitet; wird der neue Controller die URL und [/cors-url] verarbeiten, und dieser URL wird domänenübergreifende Anfragen akzeptieren.
Die Klasse [MyControllerWithHttpOptions] ist der Controller, der die Anfragen vom Typ [OPTIONS] mit der ID HTTP verarbeitet:
![]() |
package spring.cors.server.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.fasterxml.jackson.core.JsonProcessingException;
@Controller
public class MyControllerWithHttpOptions extends AbstractCorsController {
@RequestMapping(value = "/cors-getAllCategories", method = RequestMethod.OPTIONS)
public void getAllCategories(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse){
// Header CORS
setHeaders(origin, httpServletResponse);
}
...
- Zeile 14: Die Klasse ist ein Spring-Controller vom Typ MVC;
- Zeile 15: Die Klasse [MyControllerWithHttpOptions] erweitert die soeben beschriebene Klasse [AbstractCorsController];
- Zeilen 17–18: Die Methode [getAllCategories] (Zeile 18) verarbeitet die Methode URL ["/cors-getAllCategories"], wenn sie mit der Methode HTTP [OPTIONS] aufgerufen wird;
- Zeile 18: Die Methode [getAllCategories] akzeptiert zwei Parameter:
- [@RequestHeader(value = "Origin", required = false) String origin], um den Wert des Headers HTTP [Origin:http://localhost:8081] abzurufen, sofern dieser vorhanden ist. In diesem Beispiel erhält der Parameter [String origin] den Wert [http://localhost:8081]. Dieser Header ist nicht obligatorisch ([required = false]). Ist er nicht vorhanden, erhält der Parameter [String origin] den Wert null;
- [HttpServletResponse httpServletResponse]: die Antwort, die an den Client gesendet wird;
- Zeile 21: Es werden die Header HTTP gesendet, die domänenübergreifende Anfragen ermöglichen. Die Methode [setHeaders] ist in der übergeordneten Klasse [AbstractCorsController] definiert;
Dies gilt für alle URL, die vom Webserver bereitgestellt werden, sowie für die ungesicherte jSON und die in Abschnitt 13.5.3 behandelte [intro-server-webjson-01]. Wenn dieser Dienst die URL und [/url] bereitstellt, stellt die oben genannte Klasse [MyControllerWithHttpOptions] die URL und [/cors-url] bereit.
18.9.5. Der Controller [MyControllerWithCors]
![]() |
Die Klasse [MyControllerWithCors] ist der Controller, der die Anfragen HTTP vom Typ [GET] und [POST] verarbeitet:
package spring.cors.server.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import spring.webjson.service.MyController;
@Controller
public class MyControllerWithCors extends AbstractCorsController {
// Spring-Abhängigkeiten
@Autowired
private MyController myController;
...
@RequestMapping(value = "/cors-getAllCategories", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getAllCategories(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse) throws JsonProcessingException {
// Antwort
return myController.getAllCategories();
}
...
- Zeile 17: Die Klasse [MyControllerWithCors] ist ein Spring-Controller MVC
- Zeile 18: Sie erweitert die Klasse [AbstractCorsController];
- Zeilen 21–22: Injektion des Controllers [MyController] vom Webserver / des ungesicherten Controllers jSON, der in Abschnitt 13.5.3 behandelt wurde;
- Zeilen 25–27: Die Methode [getAllCategories] verarbeitet die URL und [/cors-getAllCategories] (Zeile 28), wenn sie mit der Methode HTTP [GET] angefordert wird;
- Zeile 26: Das Ergebnis der Methode [getAllCategories] wird an den Client gesendet. Dieses Ergebnis ist ein Datenstrom jSON (Attribut [produces] aus Zeile 27 und Typ [String] des Ergebnisses aus Zeile 25);
- Zeile 27: Die Methode erhält dieselben Parameter wie die Methode [getAllCategories] des Controllers [MyControllerWithHttpOptions], den wir gerade betrachtet haben;
- Zeile 30: Die Methode [myController.getAllCategories()] wird aufgefordert, die Antwort zu senden;
Letztendlich sendet die Methode [myController.getAllCategories()] des ungesicherten Servers die Antwort. Wir haben ihre Antwort lediglich um die für domänenübergreifende Anfragen erforderlichen Header ergänzt.
Dies gilt für alle URL, die vom Webserver / dem ungesicherten jSON [intro-server-webjson-01] bereitgestellt werden, der in Abschnitt 13.5.3 behandelt wurde. Wenn dieser Dienst die URL und [/url] bereitstellt, stellt die oben genannte Klasse [MyControllerWithCors] die URL und [/cors-url] bereit.
Eine domänenübergreifende Anfrage läuft wie folgt ab:
- Der Code JS des Clients fordert denURL und [/cors-url] mit einer Anfrage nach HTTP, GET oder POST an;
- der Browser, der diesen Code ausführt, fängt diese Anfrage ab und fordert zunächst dieURL [/cors-url] mit einer Anfrage HTTP OPTIONS ab, um zu überprüfen, ob der Ziel-Webdienst domänenübergreifende Anfragen akzeptiert;
- eine der Methoden des Controllers [MyControllerWithHttpOptions] sendet die vom Browser erwarteten domänenübergreifenden Header;
- der Browser fordert daraufhin die ursprüngliche Anfrage URL ([/cors-url]) mit einer Anfrage HTTP, GET oder POST an;
- eine der Methoden des Controllers [MyControllerWithCors] antwortet daraufhin;
18.9.6. Tests
Die Boot-Klasse des Projekts [intro-spring-cors-server-jpa] lautet wie folgt:
![]() |
package spring.cors.server.boot;
import org.springframework.boot.SpringApplication;
import spring.cors.server.config.AppConfig;
public class Boot {
public static void main(String[] args) {
SpringApplication.run(AppConfig.class, args);
}
}
- Zeile 10: Die statische Methode [SpringApplication.run] wird mit der Spring-Konfiguration [AppConfig] ausgeführt. Aufgrund dieser Konfiguration wird der in den Projektarchiven enthaltene eingebettete Tomcat-Server gestartet und die Webanwendung [intro-spring-cors-server-jpa] darauf bereitgestellt. Die Webanwendung des ungesicherten Servers [intro-server-webjson-01], die Teil des Projektarchivs ist, wird ebenfalls darauf bereitgestellt. Da das Projekt [intro-spring-security-server-01] ebenfalls Teil des Archivs ist, werden letztendlich zwei Arten von URL bereitgestellt:
- die des gesicherten Webdienstes: /url;
- die des Webdienstes, der domänenübergreifende Anfragen akzeptiert: /cors-url;
Wir sind nun bereit für weitere Tests. Wir starten die neue Version des Webdienstes und stellen fest, dass das Problem weiterhin besteht. Es hat sich nichts geändert. Wenn wir in Zeile 7 unten eine Konsolenausgabe einfügen, wird diese nie angezeigt, was zeigt, dass die Methode [getAllCategories] der Klasse [MyControllerWithHttpOptions] nie aufgerufen wird;
@Controller
public class MyControllerWithHttpOptions extends AbstractCorsController {
@RequestMapping(value = "/cors-getAllCategories", method = RequestMethod.OPTIONS)
public void getAllCategories(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse){
System.out.println(un_texte) ;
// Header CORS
setHeaders(origin, httpServletResponse);
}
Nach einigen Recherchen stellt man fest, dass Spring MVC standardmäßig die Befehle HTTP und [OPTIONS] selbst verarbeitet. Daher antwortet immer Spring und niemals die Methode [getAllCategories] aus Zeile 5 oben. Dieses Standardverhalten von Spring MVC lässt sich ändern. Wir ändern die vorhandene Klasse [AppConfig]:
![]() |
package spring.cors.server.config;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.DispatcherServlet;
import spring.security.config.SecurityConfig;
@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ SecurityConfig.class })
public class AppConfig {
// domänenübergreifende Anfragen
@Bean
public boolean isCorsEnabled() {
return true;
}
@Autowired
private DispatcherServlet dispatcherServlet;
@PostConstruct
public void init() {
// Die Anwendung verarbeitet die Anfragen selbst HTTP [OPTIONS]
dispatcherServlet.setDispatchOptionsRequest(true);
}
}
- Zeilen 25–26: Einbindung des Beans [dispatcherServlet], der die Client-Anfragen verarbeitet. Dieser Bean wurde in der Konfiguration des ungesicherten Webservers /jSON definiert, der in Abschnitt 13.5.3 behandelt wurde;
- Zeilen 28–29: Die Methode [init] (Zeile 29) wird ausgeführt, sobald die Klasse [AppConfig] instanziiert und die Spring-Injektionen vorgenommen wurden. Wenn sie also ausgeführt wird, ist das Feld in Zeile 26 bereits initialisiert;
- Zeile 31: Wir konfigurieren die Bean [dispatcherServlet] so, dass die Webanwendung die Befehle HTTP und [OPTIONS] selbst verarbeitet;
Wir führen die Tests mit dieser neuen Konfiguration erneut durch. Dabei erhalten wir folgendes Ergebnis:
![]() |
- Bei [1] sehen wir, dass zwei Anfragen HTTP an URL und [http://localhost:8080/cors-getAllCategories] gerichtet sind;
- in [2] die Anfrage [OPTIONS];
- in [3] die drei Header HTTP, die wir soeben in der Serverantwort konfiguriert haben;
Betrachten wir nun die zweite Anfrage:
![]() |
- in [1], die untersuchte Anfrage;
- in [2], das ist die Anfrage GET. Dank der ersten Anfrage [OPTIONS] hat der Browser die angeforderten Informationen erhalten. Er führt nun die ursprünglich angeforderte Anfrage [GET] aus;
- in [3] die Antwort des Servers;
- in [4] sendet der Server jSON;
- in [5] ist ein Fehler aufgetreten;
- in [6] die Fehlermeldung;
Es ist schwieriger zu erklären, was hier passiert ist. Die Antwort [3] des Servers ist normal, [HTTP/1.1 200 OK]. Wir sollten also das angeforderte Dokument erhalten haben. Es ist möglich, dass der Server das Dokument zwar gesendet hat, der Browser jedoch dessen Nutzung verhindert, weil er verlangt, dass die Antwort auch für die Anfrage GET den Header HTTP [Access-Control-Allow-Origin:http://localhost:8081] enthält.
Wir ändern daher den Controller [MyControllerWithCors] so, dass auch er die für domänenübergreifende Anfragen erforderlichen Header sendet:
@RequestMapping(value = "/cors-getAllCategories", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getAllCategories(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse) throws JsonProcessingException {
// Header CORS
setHeaders(origin, httpServletResponse);
// Antwort
return myController.getAllCategories();
}
- Zeile 6: Die für domänenübergreifende Anfragen erforderlichen Header werden in die Antwort aufgenommen;
Nach dieser Änderung ergeben sich folgende Ergebnisse:
![]() |
Wir haben die Liste der Kategorien erfolgreich erhalten.
18.10. Die anderen: URL, [GET]
In den Controllern [MyControllerWithCors, MyControllerWithHttpOptions] folgt der Code der Aktionen, die die mit einem [GET] angeforderten URL verarbeiten, dem Muster der Aktionen, die zuvor die URL und [/cors-getAllCategories] verarbeitet haben. Der Leser kann den Code in den mit diesem Dokument gelieferten Beispielen überprüfen. Hier ist ein Beispiel für die URL und [/cors-getAllProduits]:
in [MyControllerWithHttpOptions]
@RequestMapping(value = "/cors-getAllProduits", method = RequestMethod.OPTIONS)
public void getAllProduits(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse) {
// Kopfzeilen CORS
setHeaders(origin, httpServletResponse);
}
in [MyControllerWithCors]
@RequestMapping(value = "/cors-getAllProduits", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getAllProduits(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse httpServletResponse) throws JsonProcessingException {
// Header CORS
setHeaders(origin, httpServletResponse);
// Antwort
return myController.getAllProduits();
}
Das Ergebnis lautet wie folgt:
![]() |
18.11. Die URL [POST]
Betrachten wir den folgenden Fall:
![]() |
- Es wird ein POST [1] in Richtung des URL [2] gebildet;
- in [3] ist der übermittelte Wert. Es handelt sich um eine Zeichenfolge jSON;
- insgesamt soll eine Kategorie mit dem Namen [categorie2] angelegt werden;
Wir ändern vorerst keinen Code. Das Ergebnis sieht wie folgt aus:
![]() |
- in [1]; wie bei den Anfragen [GET] wird vom Browser eine Anfrage [OPTIONS] gestellt;
- bei [2] fordert er eine Zugriffsberechtigung für eine Anfrage [POST] an. Zuvor war es [GET];
- in [3] fordert er die Berechtigung zum Senden der Header HTTP und [accept, authorization, content-type] an. Zuvor gab es nur die ersten beiden Header;
- in [4] erteilt der Webdienst nicht alle angeforderten Berechtigungen, was den Fehler [5] verursacht;
Wir ändern die Methode [AbstractController.sendHeaders] wie folgt:
package spring.cors.server.service;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class AbstractCorsController {
@Autowired
private boolean isCorsEnabled;
// Senden der Optionen an den Client
public void setHeaders(String origin, HttpServletResponse response) {
// Cors erlaubt?
if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
return;
}
// Der Header CORS wird festgelegt
response.addHeader("Access-Control-Allow-Origin", origin);
// Bestimmte Header werden zugelassen
response.addHeader("Access-Control-Allow-Headers", "accept, authorization, content-type");
// GET und POST werden zugelassen
response.addHeader("Access-Control-Allow-Methods", "GET, POST");
}
}
- Zeile 21: Der Header „HTTP [Content-Type]“ (Groß-/Kleinschreibung spielt keine Rolle) wurde hinzugefügt;
- Zeile 23: Die Methode HTTP [POST] wurde hinzugefügt;
Dadurch werden die Methoden [POST] genauso behandelt wie die Abfragen [GET]. Hier ist das Beispiel für URL [/cors-addArticles]:
in [MyControllerWithCors]
@RequestMapping(value = "/cors-addCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
@ResponseBody
public String addCategories(HttpServletRequest request,
@RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse httpServletResponse)
throws JsonProcessingException {
// Header CORS
setHeaders(origin, httpServletResponse);
// Antwort
return myController.addCategories(request);
}
in [MyControllerWithHttpOptions]
@RequestMapping(value = "/cors-addCategories", method = RequestMethod.OPTIONS)
public void addCategories(HttpServletRequest request,
@RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse httpServletResponse)
throws JsonProcessingException {
// Kopfzeilen CORS
setHeaders(origin, httpServletResponse);
}
Das Ergebnis lautet wie folgt:
![]() |
Die Kategorie [categorie2] wurde erfolgreich in die Datenbank aufgenommen. Der Eintrag SGBD hat ihr den Primärschlüssel 1729 zugewiesen.
18.12. Der Controller [AuthenticateCorsController]
![]() |
Der Controller [AuthenticateCorsController] dient dazu, denURL [/cors-authenticate] bereitzustellen, der es ermöglicht, die bereits vorhandene URL [/authenticate] mit einer domänenübergreifenden Abfrage aufzurufen. Sein Code lautet wie folgt:
package spring.cors.server.service;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import spring.security.service.AuthenticateController;
@Controller
public class AuthenticateCorsController extends AbstractCorsController {
@Autowired
private AuthenticateController authenticateController;
@RequestMapping(value = "/cors-authenticate", method = RequestMethod.GET)
@ResponseBody
public String authenticate(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse response) throws JsonProcessingException {
// Header CORS
setHeaders(origin, response);
// Ursprungsmethode
return authenticateController.authenticate();
}
@RequestMapping(value = "/cors-authenticate", method = RequestMethod.OPTIONS)
public void corsAuthenticate(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse response) {
// Header CORS
setHeaders(origin, response);
}
}
Hier sind zwei Beispiele:
![]() |
- Die angezeigten Antworten werden durch den folgenden Code jS dargestellt:
function doGet(url) {
// Man führt einen Ajax-Aufruf manuell durch
$.ajax({
headers : {
'„Authorization“: „Basic “ + authorizationCode
},
url : baseUrl + url,
type : 'GET',
dataType : 'text',
beforeSend : function() {
},
success : function(data) {
// Textergebnis
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// Systemfehler
response.text(JSON.stringify(jqXHR.statusCode()));
}
})
}
- Die Antwort [1] wird durch Zeile 14 der Funktion [success] angezeigt;
- Die Antwort [2] wird in Zeile 20 der Funktion [error] ausgegeben. Die Funktion [JSON.stringify] erzeugt die Zeichenkette jSON aus dem Objekt [jqXHR.statusCode()], das den aufgetretenen Fehler kapselt. Dieses Objekt liefert nur wenige Informationen. Es ist möglich, weitere Methoden des Objekts [jqXHR] zu nutzen, um beispielsweise die vom Server zurückgegebenen Header HTTP abzurufen;
18.13. Conclusion
Unsere Anwendung unterstützt nun domänenübergreifende Anfragen. Diese können über die Konfiguration in der Klasse [AppConfig] zugelassen oder abgelehnt werden:
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ SecurityConfig.class })
public class AppConfig {
// domänenübergreifende Anfragen
@Bean
public boolean isCorsEnabled() {
return true;
}
@Autowired
private DispatcherServlet dispatcherServlet;
@PostConstruct
public void init() {
// Die Anwendung verarbeitet die Anfragen selbst HTTP [OPTIONS]
dispatcherServlet.setDispatchOptionsRequest(true);
}
}







































