21. Zarządzanie dostępem międzydomenowym
21.1. Architecture
Przyjrzyjmy się teraz problemowi żądań międzydomenowych. W dokumencie [Tutoriel AngularJS / Spring 4] opracowuje się aplikację klient-serwer, w której klientem jest aplikacja AngularJS:
![]() |
- strony HTML / CSS / JS aplikacji Angular pochodzą z serwera [1];
- w [2] usługa [dao] wysyła żądanie do innego serwera, serwera [2]. Cóż, jest to zabronione przez przeglądarkę, w której uruchomiona jest aplikacja Angular, ponieważ stanowi to lukę w zabezpieczeniach. Aplikacja może wysyłać zapytania wyłącznie do serwera, z którego pochodzi, tj. do serwera [1];
W rzeczywistości nieprawdziwe jest stwierdzenie, że przeglądarka zabrania aplikacji Angular wysyłania zapytania do serwera [2]. Przeglądarka w rzeczywistości wysyła zapytanie do serwera [2], aby dowiedzieć się, czy zezwala on na wysłanie zapytania przez klienta, który nie pochodzi z jego własnej sieci. Technikę tę nazywa się CORS (Cross-Origin Resource Sharing). Serwer [2] wyraża zgodę, wysyłając odpowiednie nagłówki HTTP.
Aby zilustrować potencjalne problemy, stworzymy aplikację klient-serwer, w której:
- serwerem będzie nasz zabezpieczony serwer WWW / jSON;
- klientem będzie prosta strona HTML wyposażona w kod JavaScript, który będzie wysyłał żądania do serwera WWW / jSON;
Wdrożymy następującą architekturę:
![]() |
- w [1] aplikacja internetowa dostarcza strony HTML / jS;
- w [2] przeglądarka uruchamia kod JavaScript osadzony na stronach HTML w celu wysłania zapytania do zabezpieczonej usługi internetowej [3];
21.2. Projekt [spring-cors-server-jdbc-generic]
21.2.1. Konfiguracja środowiska pracy
![]() |
- załaduj powyższe projekty. Projekty [spring-cors-*] znajdują się w folderze [<exemples>\spring-database-generic\spring-cors];
- naciśnij klawisze Alt-F5 i zregeneruj wszystkie projekty Maven;
Następnie uruchom konfigurację uruchomieniową o nazwie [spring-cors-server-jdbc-generic] (należy uruchomić SGBD i MySQL), która uruchamia serwis internetowy na porcie 8081:
![]() |
Wprowadź do bazy [dbproduitscategories] konfigurację uruchomienia o nazwie [spring-jdbc-generic-04-fillDataBase]:
![]() |
Uruchom konfigurację wykonania o nazwie [spring-cors-client-generic], która uruchamia drugą aplikację internetową (na innym serwerze Tomcat) na porcie 8082:
![]() |
W przeglądarce wywołaj URL [http://localhost:8082/client.html]:
![]() |
- w [1] żądamy skróconej wersji wszystkich kategorii;
- w [2] – odpowiedź serwera jSON;
21.2.2. Projekt klienta [spring-cors-client-generic]
![]() |
![]() |
Plik [application.properties] pozwala nam ustawić port aplikacji internetowej klienta. Jego zawartość jest następująca:
server.port=8082
A więc:
- klient to aplikacja internetowa dostępna pod adresem URL [http://localhost:8082];
- serwer to aplikacja internetowa dostępna pod adresem URL [http://localhost:8081];
Ponieważ klient nie korzysta z tego samego portu co serwer, pojawia się problem z żądaniami międzydomenowymi. [http://localhost:8081] i [http://localhost:8082] to bowiem dwie różne domeny.
21.2.3. Konfiguracja Maven
Projekt jest projektem Maven z następującym plikiem [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 /> <!-- wyszukiwanie elementu nadrzędnego w repozytorium -->
</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>
<!-- wtyczki -->
<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>
- wiersze 14–19: jest to projekt Spring Boot;
- wiersze 27–30: wykorzystuje się zależność [spring-boot-starter-web], która zawiera serwer Tomcat oraz Spring MVC;
21.2.4. Podstawy jQuery i JavaScript
![]() |
Aplikacja internetowa wyświetla następującą, jedyną stronę:
![]() |
Zawiera ona kod JavaScript (jS) wykonywany w przeglądarce. Przedstawimy kilka podstaw JavaScriptu, które pozwolą nam zrozumieć ten kod. Klient będzie wysyłał wywołania HTTP przy użyciu biblioteki jQuery [https://jquery.com/], która udostępnia wiele funkcji ułatwiających tworzenie kodu JavaScript. Tworzymy plik statyczny HTML [jQuery.html], który umieszczamy w folderze [static]:
![]() |
Plik ten będzie miał następującą zawartość:
<!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>
- wiersz 6: import pliku jQuery;
- wiersze 10–12: element strony o identyfikatorze [element1]. Będziemy modyfikować ten element.
Musimy pobrać plik [jquery-2.1.3.min.js]. Znajdziemy go w najnowszej wersji jQuery w URL [http://jquery.com/download/]:

Pobrany plik umieścimy w folderze [static / js]:
![]() |
Po wykonaniu tej czynności wyświetlimy widok statyczny [jQuery.html] w przeglądarce Chrome [1-2]:
![]() |
W przeglądarce Google Chrome należy wpisać [Ctrl-Maj-I], aby wyświetlić narzędzia programistyczne [3]. Zakładka [Console] [4] umożliwia uruchamianie kodu JavaScript. Poniżej podajemy polecenia JavaScript, które należy wpisać, wraz z ich objaśnieniami.
|
: tworzy kolekcję wszystkich elementów o identyfikatorze [element1], a więc zazwyczaj zbiór 0 lub 1 elementu, ponieważ na jednej stronie nie mogą występować dwa identyczne identyfikatory na jednej stronie HTML. | ![]() |
|
: przypisuje tekst [blabla] do wszystkich elementów kolekcji. Powoduje to zmianę treści wyświetlanej na stronie | ![]() |
|
ukrywa elementy kolekcji. Tekst [blabla] nie jest już wyświetlany. | ![]() |
|
: ponownie wyświetla kolekcję. Dzięki temu widzieć, że element o identyfikatorze [element1] ma atrybut CSS style='display: none;', co powoduje, , że element jest ukryty. | |
|
: wyświetla elementy kolekcji. Tekst [blabla] pojawia się ponownie. To właśnie atrybut CSS style='display: block;' zapewnia taki wyświetlanie. | ![]() |
|
: ustawia atrybut dla wszystkich elementów kolekcji. Atrybutem tym jest tutaj [style], a jego wartość [color: red]. Tekst [blabla] zmienia kolor na czerwony. | ![]() |
![]() | |
![]() |
Warto zauważyć, że identyfikator URL przeglądarki nie uległ zmianie podczas wszystkich tych operacji. Nie doszło do żadnej komunikacji z serwerem WWW. Wszystko odbywa się wewnątrz przeglądarki. Teraz przyjrzyjmy się kodowi źródłowemu strony:
<!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>
To jest tekst początkowy. Nie odzwierciedla on w żaden sposób zmian, które wprowadziliśmy w elemencie w wierszach 10–12. Należy o tym pamiętać podczas debugowania kodu JavaScript. W takich przypadkach przeglądanie kodu źródłowego wyświetlanej strony jest często zbędne.
21.2.5. Kod jS aplikacji
Wróćmy do kodu HTML ze strony aplikacji klienckiej, która będzie wysyłać zapytanie do serwisu internetowego / 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">
<!-- metoda 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 />
<!-- wartość wprowadzona -->
<br /> Chaîne jSON à poster : <input type="text" id="posted" size="50" />
<!-- przycisk zatwierdzenia -->
<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>
- wiersz 6: importujemy bibliotekę jQuery;
- wiersz 7: importujemy kod, który zamierzamy napisać;
- wiersze 11, 15, 17, 21: należy zanotować identyfikatory [id] elementów strony. Kod JavaScript odwołuje się do tych elementów za pomocą tych identyfikatorów;
Kod [client.js] wygląda następująco:
// dane ogólne
var url;
var posted;
var response;
var method;
function requestServer() {
// pobieramy informacje z formularza
var urlValue = url.val();
var postedValue = posted.val();
method = document.forms[0].elements['method'].value;
// ręczne wywołanie Ajax
if (method === "get") {
doGet(urlValue);
} else {
doPost(urlValue, postedValue);
}
}
function doGet(url) {
// ręczne wywołanie Ajax
$.ajax({
headers : {
'„Authorization”: „Basic YWRtaW46YWRtaW4=”
},
url : 'http://localhost:8081' + url,
type : 'GET',
dataType : 'tex/plain',
beforeSend : function() {
},
success : function(data) {
// wynik tekstowy
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// błąd systemowy
response.text(jqXHR.responseText);
}
})
}
function doPost(url, posted) {
// ręczne wywołanie Ajax
$.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) {
// wynik tekstowy
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// błąd systemowy
response.text(jqXHR.responseText);
}
})
}
// podczas ładowania dokumentu
$(document).ready(function() {
// pobierane są odniesienia do elementów strony
url = $("#url");
posted = $("#posted");
response = $("#response");
});
- wiersze 71–75: kod jS wykonywany po zakończeniu ładowania dokumentu w przeglądarce;
- wiersze 73–75: pobierane są odniesienia do trzech pól dokumentu HTML;
- wiersze 2–5: zmienne globalne znane we wszystkich funkcjach zdefiniowanych w pliku jS;
- wiersz 9: pobierana jest wartość URL wprowadzona przez użytkownika;
- wiersz 10: pobierana jest wartość, którą użytkownik chce wysłać;
- wiersz 11: pobieramy sposób [get] lub [post], który należy zastosować, aby zażądać wartości URL z wiersza 9:
- document oznacza dokument załadowany przez przeglądarkę, tzw. DOM (Document Object Model),
- document.forms[0] oznacza pierwszy formularz w dokumencie; dokument może zawierać kilka formularzy. W tym przypadku jest tylko jeden,
- document.forms[0].elements['method'] oznacza element formularza, który posiada atrybut [name='method']. Są dwa takie elementy:
<input type="radio" id="get" name="method" value="get" checked="checked" />GET
<input type="radio" id="post" name="method" value="post" />POST
- (ciąg dalszy)
- document.forms[0].elements['method'].value to wartość, która zostanie przesłana dla komponentu posiadającego atrybut [name='method']. Wiadomo, że zapisywana wartość to wartość atrybutu [value] zaznaczonego przycisku opcji. W tym przypadku będzie to więc jeden z ciągów znaków ['get', 'post'];
- wiersze 13–18: w zależności od metody HTTP, która ma zostać użyta, wywoływana jest metoda [doGet] lub [doPost];
- metoda jQuery [$.ajax] wykonuje wywołanie HTTP;
- wiersze 23–25: nawiązujemy połączenie z serwerem, który wymaga nagłówka HTTP lub [Authorization: Basic code]. Tworzymy ten nagłówek dla użytkownika [admin / admin], który jako jedyny może wysyłać zapytania do serwera;
- wiersz 26: użytkownik wprowadzi wartości typu [/getAllLongCategories, /saveCategories, ...]. Należy zatem uzupełnić te wartości;
- wiersz 27: należy zastosować metodę o typie HTTP;
- wiersz 28: serwer zwraca jSON. Jako typ wyniku podaje się [text/plain], aby wyświetlić go w takiej postaci, w jakiej został odebrany;
- wiersz 33: wyświetlenie tekstowej odpowiedzi serwera;
- wiersz 39: wyświetlenie ewentualnego komunikatu o błędzie w formacie tekstowym;
- wiersz 44: metoda [doPost] otrzymuje drugi parametr, którym jest wartość do wysłania;
- wiersz 52: w celu wskazania, że wysyłana wartość będzie miała postać ciągu znaków jSON;
21.2.6. Działanie klienta
Aplikacja kliencka to aplikacja konsolowa uruchamiana przez następującą klasę wykonywalną [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);
}
}
- wiersz 6: adnotacja [@EnableAutoConfiguration] jest adnotacją projektu [Spring Boot] (wiersz 4). Spring Boot sprawdzi archiwa znajdujące się w ścieżce Classpath projektu. Będą to w tym przypadku wszystkie zależności Maven dostarczone przez pojedynczą zależność pliku [pom.xml]:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Ta zależność wprowadza bardzo wiele archiwów, w szczególności Spring MVC oraz serwer Tomcat. Ze względu na obecność tych zależności Spring Boot skonfiguruje, przy użyciu wartości domyślnych, projekt Spring MVC działający na serwerze Tomcat. Serwer Tomcat jest wówczas skonfigurowany do pracy na porcie 8080. Jeśli chcemy odejść od wartości domyślnych wybranych przez Spring Boot, możemy użyć pliku [application.properties] znajdującego się w katalogu głównym ścieżki Classpath (wszystko, co znajduje się w katalogu [src / main / resources], znajduje się w katalogu głównym ścieżki Classpath):
![]() |
W ten sposób określamy, że serwer Tomcat ma działać na porcie 8082:
server.port=8082
Listę dostępnych parametrów można znaleźć w pliku [application.properties], URL (czerwiec 2015 r.) oraz [http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html];
Powrót do kodu [Client.java]:
- wiersz 10: metoda [SpringApplication.run] wdroży stronę [client.html] na serwerze Tomcat znajdującym się w ścieżce Classpath projektu;
21.2.7. URL [/getAllShortCategories]
![]() |
Uruchamiamy:
- zabezpieczony serwer WWW/JSON na porcie 8081 (konfiguracja [spring-security-server-jdbc-generic]);
- klienta tego serwera na porcie 8082 (konfiguracja [spring-cors-client-generic]);
następnie wysyłamy żądania do URL, [http://localhost:8082/client.html] i [1]:
![]() |
- w przypadku [2] wykonujemy operację GET na URL i [http://localhost:8081/getAllShortCategories];
Nie otrzymujemy odpowiedzi z serwera. Po sprawdzeniu konsoli programistycznej przeglądarki Chrome (Ctrl-Shift-I) widzimy błąd:
![]() |
- w [1], znajdujemy się w zakładce [Network];
- w zakładce [2] widać, że wysłane żądanie HTTP nie jest [GET], lecz [OPTIONS]. W przypadku żądania międzydomenowego przeglądarka sprawdza u serwera, czy spełnionych jest pewna liczba warunków, wysyłając do niego żądanie o numerze HTTP [OPTIONS]. W tym przypadku żądania te są oznaczone symbolami [5-6];
- W przypadku [5] przeglądarka pyta, czy do celu URL można dotrzeć za pomocą GET. Żądanie o numerze [Access-Control-Request-Method] wymaga odpowiedzi z nagłówkiem HTTP [Access-Control-Allow-Methods] wskazującym, że żądana metoda jest akceptowana;
- w odpowiedzi [6] przeglądarka wysyła nagłówek HTTP [Origin: http://localhost:8081]. Nagłówek ten zawiera prośbę o odpowiedź w postaci nagłówka HTTP [Access-Control-Allow-Origin], wskazującego, że wskazane źródło zostało zaakceptowane;
- w nagłówku [7] przeglądarka pyta, czy nagłówki HTTP, [accept] i [authorization] są akceptowane. Żądanie [Access-Control-Request-Headers] oczekuje na odpowiedź zawierającą nagłówki HTTP i [Access-Control-Allow-Headers], wskazujące, że żądane nagłówki są akceptowane;
- w przypadku [3] występuje błąd. Po kliknięciu ikony pojawia się błąd [4];
- w [4] komunikat wskazuje, że serwer nie wysłał nagłówka HTTP [Access-Control-Allow-Origin], który określa, czy źródło żądania jest akceptowane;
- w [8] widać, że serwer rzeczywiście nie wysłał tego nagłówka. W rezultacie przeglądarka odmówiła wykonania żądania HTTP GET, o które pierwotnie proszono;
Musimy zmodyfikować serwer WWW / jSON.
21.2.8. Nowa usługa internetowa / json
Tworzymy nowy projekt Maven o nazwie [spring-cors-server-jdbc-generic]:
![]() |
Konfiguracja Maven nowej usługi internetowej wygląda następująco:
<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>
<!-- wtyczki -->
<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>
- wiersze 30–32: wykorzystujemy dotychczasowe osiągnięcia, opierając się na archiwum bezpiecznego serwera internetowego / json;
Ostatecznie zależności są następujące:
![]() |
Klasa konfiguracyjna [AppConfig] wygląda następująco:
![]() |
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 {
// żądania międzydomenowe
@Bean
public boolean isCorsEnabled() {
return true;
}
...
}
- wiersz 12: klasa jest klasą konfiguracyjną Spring;
- wiersz 9: pozostałe komponenty Springa należy odszukać w pakiecie [spring.cors.server.service];
- wiersz 14: importujemy bean’y z projektu [spring-security-server-jdbc-generic];
- wiersze 18–21: tworzymy komponent Spring o nazwie [isCorsEnabled], który określa, czy akceptujemy klientów spoza domeny serwera, czy nie;
21.2.9. Kontrolery
Nowa usługa internetowa posiada cztery kontrolery:
![]() |
- [CorsCategorieController] zarządza URL odpowiedzialnym za przetwarzanie kategorii. Obsługuje wyłącznie nagłówki CORS od klientów internetowych. W pozostałych przypadkach przekazuje zadanie kontrolerowi [CategorieController] z zależności [spring-webjson-server-jdbc-generic];
- [CorsProduitController] i [CorsAuthenticateController] postępują analogicznie, przekazując zadanie kontrolerom [ProduitController] z zależności [spring-webjson-server-jdbc-generic] oraz [AuthenticateController] z zależności[spring-security-server-jdbc-generic];
- [CorsController] służy do wyodrębnienia elementów wspólnych dla trzech poprzednich kontrolerów;
21.2.9.1. Kontroler [CorsController]
Klasa [CorsController] ma następującą postać:
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;
// wysyłanie opcji do klienta
public void sendOptions(String origin, HttpServletResponse response) {
// Czy CORS jest dozwolone?
if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
return;
}
// ustawiamy nagłówek CORS
response.addHeader("Access-Control-Allow-Origin", origin);
// zezwalamy na niektóre nagłówki
response.addHeader("Access-Control-Allow-Headers", "accept, authorization");
// zezwalamy na GET
response.addHeader("Access-Control-Allow-Methods", "GET");
}
}
- wiersz 8: klasa [CorsController] jest kontrolerem Spring;
- wiersze 11–12: wstrzyknięcie komponentu [isCorsEnabled], który określa, czy należy obsługiwać nagłówki CORS;
- wiersze 15–26: metoda [sendOptions] odpowiada na żądania klientów wysyłających nagłówki CORS;
- wiersze 17–19: jeśli aplikacja jest skonfigurowana tak, aby akceptować żądania międzydomenowe, a nadawca wysłał nagłówek HTTP [Origin] i jeśli źródło to zaczyna się od [http://localhost], wtedy akceptujemy żądanie międzydomenowe, w przeciwnym razie je odrzucamy;
- wiersz 21: jeśli klient znajduje się w domenie [http://localhost:port], wysyłamy nagłówek HTTP:
co oznacza, że serwer akceptuje pochodzenie klienta;
- wiersze 22–25: w żądaniu HTTP [OPTIONS] zaznaczyliśmy dwa konkretne nagłówki HTTP:
Na nagłówki HTTP i [Access-Control-Request-X] serwer odpowiada nagłówkami HTTP i [Access-Control-Allow-X], w których wskazuje, co jest dozwolone. Wiersze 22–25 po prostu powtarzają żądanie klienta, aby wskazać, że zostało ono zaakceptowane;
21.2.9.2. Kontroler [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) {
// metoda źródłowa
return categorieController.getAllShortCategories();
}
...
}
- wiersz 19: adnotacja [@RestController] sprawia, że klasa jest jednocześnie komponentem Springa i kontrolerem MVC, który sam wysyła odpowiedzi do klienta;
- wiersz 20: klasa [CorsCategorieController] rozszerza klasę [CorsController], którą właśnie omówiliśmy;
- wiersze 22–23: wstrzyknięcie kontrolera [CategorieController categorieController] z zależności [spring-webjson-server-jdbc-generic];
- wiersze 25–29: obsługują URL [/cors-getAllShortCategories], gdy jest ona wywoływana za pomocą polecenia HTTP [OPTIONS]. Zgodnie z ustaleniami postanowiliśmy, że klienci internetowi, którzy chcą wywołać URL [/U] z zabezpieczonej usługi internetowej, będą musieli w rzeczywistości wywołać URL [/cors-U]. Wdrożona usługa internetowa będzie zatem posiadać dwa rodzaje URL:
- [/U]: dla klientów niekorzystających z sieci;
- [/cors-U]: dla klientów internetowych;
- wiersz 25: metoda [/cors-getAllShortCategories] przyjmuje jako parametry:
- obiekt [@RequestHeader(value = "Origin", required = false)], który pobiera nagłówek HTTP [Origin] z żądania. Nagłówek ten został wysłany przez nadawcę żądania:
Wskazuje się, że nagłówek HTTP [Origin] jest opcjonalny [required = false]. W takim przypadku, jeśli nagłówek nie występuje, parametr [String origin] przyjmie wartość null. Przy wartości [required = true], która jest wartością domyślną, w przypadku braku nagłówka generowany jest wyjątek. Chcieliśmy uniknąć tej sytuacji;
- (ciąg dalszy)
- obiekt [HttpServletResponse response], który zostanie zwrócony klientowi, który złożył żądanie;
Te dwa parametry są wstrzykiwane przez Spring;
- wiersz 28: przekazujemy przetwarzanie żądania do metody [sendOptions] klasy nadrzędnej [CorsController];
- wiersze 31–36: metoda [getAllShortCategories] przetwarza obiekty URL i [/cors-getAllShortCategories], gdy jest wywoływana z obiektem GET;
- wiersz 35: zadanie jest przekazywane do metody [CategorieController.getAllShortCategories] zależności [spring-webjson-server-jdbc-generic];
Jesteśmy teraz gotowi do przeprowadzenia nowych testów. Uruchamiamy nową wersję serwisu internetowego i okazuje się, że problem nadal występuje. Nic się nie zmieniło. Jeśli w wierszu 28 powyżej umieścimy wyświetlanie konsoli, to nigdy nie zostanie ono wyświetlone, co wskazuje, że metoda [corsGetAllShortCategories] z wiersza 25 nigdy nie jest wywoływana.
Po kilku poszukiwaniach okazuje się, że Spring MVC samodzielnie przetwarza polecenia HTTP i [OPTIONS], stosując domyślną obsługę. W związku z tym to zawsze Spring odpowiada, a nigdy metoda [corsGetAllShortCategories] z linii 25. To domyślne zachowanie Springa MVC można zmienić. Modyfikujemy istniejącą klasę [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;
@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ spring.security.config.AppConfig.class })
public class AppConfig {
// żądania międzydomenowe
@Bean
public boolean isCorsEnabled() {
return true;
}
@Autowired
private DispatcherServlet dispatcherServlet;
@PostConstruct
public void init(){
// aplikacja sama przetwarza żądania HTTP [OPTIONS]
dispatcherServlet.setDispatchOptionsRequest(true);
}
}
- wiersze 23–24: wstrzykujemy komponent [DispatcherServlet dispatcherServlet], który został zdefiniowany w zależności [spring-webjson-server-jdbc-generic];
- wiersze 26–30: adnotacja [@PostConstruct] powoduje, że metoda [init] zostanie wykonana po instancjonowaniu klasy [AppConfig] oraz po wstrzyknięciach dokonanych przez Spring;
- wiersz 29: wymagamy, aby serwlet przekazywał do aplikacji polecenia HTTP i [OPTIONS];
Ponownie przeprowadzamy testy z tą nową konfiguracją. Otrzymujemy następujący wynik:
![]() |
- w [1] widzimy, że istnieją dwa żądania HTTP skierowane do URL i [http://localhost:8080/getAllCategories];
- w [2] – żądanie [OPTIONS];
- w [3] znajdują się trzy nagłówki HTTP, które właśnie skonfigurowaliśmy w odpowiedzi serwera;
Przyjrzyjmy się teraz drugiemu żądaniu:
![]() |
- w [1] – analizowane żądanie;
- w [2], czyli żądanie GET. Dzięki pierwszemu żądaniu [OPTIONS] przeglądarka otrzymała żądane informacje. Teraz wysyła żądanie [GET], o które prosiła początkowo;
- w [3] znajduje się odpowiedź serwera;
- w [4] serwer wysyła jSON;
- w pliku [5] wystąpił błąd;
- w [6] – komunikat o błędzie;
Trudniej jest wyjaśnić, co się tutaj wydarzyło. Odpowiedź serwera [3] jest normalna – [HTTP/1.1 200 OK]. Powinniśmy więc otrzymać żądany dokument. Możliwe, że serwer rzeczywiście wysłał dokument, ale to przeglądarka uniemożliwia jego wykorzystanie, ponieważ wymaga, aby również w przypadku żądania GET odpowiedź zawierała nagłówek HTTP [Access-Control-Allow-Origin:http://localhost:8081].
Modyfikujemy metodę obsługującą żądania GET, URL oraz [/cors-getAllShortCategories]:
@RequestMapping(value = "/cors-getAllShortCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllShortCategories(
@RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse response) {
// nagłówki CORS
sendOptions(origin, response);
// metoda źródłowa
return categorieController.getAllShortCategories();
}
- wiersz 5: podobnie jak w przypadku żądania HTTP [OPTIONS], serwer wyśle nagłówki HTTP CORS dla żądania HTTP [GET];
Po tej zmianie wyniki są następujące:
![]() |
Udało nam się uzyskać skróconą wersję wszystkich kategorii.
21.2.9.3. URL [GET]
W kontrolerach [CorsCategorieController, CorsProduitController, CorsAuthenticateController] kod akcji przetwarzających żądania URL z [GET] jest zgodny ze wzorcem akcji, które wcześniej przetwarzały URL i [/cors-getAllShortArticles]. Czytelnik może sprawdzić kod w przykładach dołączonych do niniejszego dokumentu. Oto przykład dla URL i [/cors-getAllLongProduits] kontrolera [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) {
// nagłówki CORS
sendOptions(origin, response);
// metoda źródłowa
return produitController.getAllLongProduits();
}
@RequestMapping(value = "/cors-getAllLongProduits", method = RequestMethod.OPTIONS)
public void corsGetAllLongProduits(@RequestHeader(value = "Origin", required = false) String origin,
HttpServletResponse response) {
sendOptions(origin, response);
}
...
}
![]() |
21.2.9.4. URL [POST]
Rozważmy następujący przypadek:
![]() |
- tworzymy POST [1] w kierunku URL [2];
- w [3] znajduje się wartość, która została opublikowana. Jest to ciąg znaków jSON z kategorii bez produktów;
- ostatecznie chcemy utworzyć kategorię o nazwie [categorie[2]];
Na razie nie modyfikujemy żadnego kodu. Uzyskany wynik jest następujący:
![]() |
- w [1], podobnie jak w przypadku żądań [GET], przeglądarka wysyła żądanie [OPTIONS];
- w przypadku [2] przeglądarka prosi o zezwolenie na dostęp dla żądania [POST]. Wcześniej było to [GET];
- w [3] przeglądarka prosi o zezwolenie na wysłanie nagłówków HTTP i [accept, authorization, content-type]. Wcześniej były tylko dwa pierwsze nagłówki;
- w [4] serwis internetowy nie udziela wszystkich wymaganych uprawnień, co powoduje błąd [5];
Modyfikujemy metodę [CorsController.sendOptions] w następujący sposób:
public void sendOptions(String origin, HttpServletResponse response) {
// Czy dozwolone są treści?
if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
return;
}
// ustalamy nagłówek CORS
response.addHeader("Access-Control-Allow-Origin", origin);
// zezwalamy na niektóre nagłówki
response.addHeader("Access-Control-Allow-Headers", "accept, authorization, content-type");
// zezwala się na GET i POST
response.addHeader("Access-Control-Allow-Methods", "GET, POST");
}
}
- wiersz 9: dodano nagłówek HTTP [Content-Type] (wielkość liter nie ma znaczenia);
- wiersz 11: dodano metodę HTTP [POST];
Dzięki temu metody [POST] są przetwarzane w taki sam sposób jak zapytania [GET]. Oto przykład metod URL i [/cors-saveCategories] w kontrolerze [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) {
// nagłówki CORS
sendOptions(origin, response);
// metoda pochodzenia
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);
}
Po wprowadzeniu tych zmian uzyskano następujący wynik:
![]() |
Kategoria [categorie[2]] została pomyślnie dodana do bazy danych. SGBD przypisał jej klucz główny 226. Można to sprawdzić za pomocą metody GET [/cors-getAllShortCategories]:
![]() |
21.2.10. Wniosek
Nasza aplikacja obsługuje teraz żądania międzydomenowe. Można je zezwolić lub zablokować poprzez konfigurację w klasie [AppConfig]:
package spring.cors.server.config;
...
@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ spring.security.config.AppConfig.class })
public class AppConfig {
// żądania międzydomenowe
@Bean
public boolean isCorsEnabled() {
return true;
}
...
}
21.3. Projekt Eclipse [spring-cors-server-jpa-generic]
Usługa internetowa CORS zostanie teraz zaimplementowana przez projekt [spring-cors-server-jpa-generic], który opiera się na projekcie [spring-security-server-jpa-generic], zarządzającym dostępem do bazy danych za pomocą Spring Data JPA:
![]() |
Projekt [spring-cors-server-jpa-generic] powstał poprzez skopiowanie wcześniej analizowanego projektu [spring-cors-server-jdbc-generic].
![]() |
Następnie należy wprowadzić dwie zmiany. Pierwsza dotyczy pliku [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>
<!-- wtyczki -->
<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>
- wiersze 30–32: zależność od zabezpieczonej usługi internetowej [spring-security-server-jpa-generic];
Ostatecznie zależności projektu są następujące:
![]() |
Uwaga: należy nacisnąć Alt-F5, a następnie zregenerować wszystkie projekty
Drugą zmianą jest aktualizacja importów w klasach zgłaszających błędy [Alt-Maj-O].
To wszystko. Uruchamiamy serwis internetowy CORS z konfiguracją uruchomienia [spring-cors-server-jpa-generic-hibernate-eclipselink]:
![]() | ![]() |
Następnie uruchamiamy klienta generycznego:
![]() |
a następnie za pomocą przeglądarki wysyłamy żądanie do URL, [1] oraz GET. W [2] widać, że skrócona wersja zwracanych kategorii zawiera pole [entityType], którego nie było w poprzedniej wersji JDBC.
Omówimy jeszcze dwie inne architektury CORS:
- architektura CORS / JPA EclipseLink / DB2;
- architektura CORS / JPA OpenJpa / Firebird;
21.4. Architektura CORS / JPA EclipseLink / DB2
Wdrożymy następującą architekturę:
![]() |
Ładujemy następujące projekty:
![]() |
Uwaga: naciśnij klawisze Alt-F5 i zregeneruj wszystkie projekty Maven.
Uruchom SGBD oraz DB2 i sprawdź, czy baza danych [dbproduitscategories] rzeczywiście istnieje. Jeśli nie, utwórz ją (punkt 12.1.2).
Użytkownicy są tworzone w bazie danych [dbproduitscategories] przy użyciu konfiguracji uruchomieniowej [spring-security-create-users-hibernate-eclipselink]:
![]() | ![]() |

Następnie uruchom usługę internetową CORS z konfiguracją wykonania o nazwie [spring-cors-server-jpa-generic-hibernate-eclipselink] i jej klientem o nazwie [spring-cors-client-generic]:
![]() | ![]() |
Napełnij bazę danych [dbproduitscategories] wartościami, korzystając z konfiguracji wykonawczej [spring-jdbc-generic-04-fillDataBase]:
![]() |
Na koniec wywołaj w przeglądarce następujący adres: URL:
![]() |
21.5. Architektura CORS / JPA OpenJPA / Firebird
Teraz zaimplementujemy następującą architekturę:
![]() |
Ładujemy następujące projekty:
![]() |
Uwaga: naciśnij klawisze Alt-F5 i zregeneruj wszystkie projekty Maven.
Uruchom SGBD Firebird i sprawdź, czy baza danych [dbproduitscategories] rzeczywiście istnieje. Jeśli nie, utwórz ją (punkt 14.1.2).
Użytkowników tworzy się w bazie danych [dbproduitscategories] przy użyciu konfiguracji uruchomieniowej [spring-security-create-users-openjpa]:
![]() | ![]() |

Następnie uruchom usługę internetową CORS z konfiguracją wykonania o nazwie [spring-cors-server-jpa-generic-openjpa]:
![]() | ![]() |
Uruchom klienta CORS przy użyciu konfiguracji [spring-cors-client-generic]:
![]() |
Wypełnij bazę danych [dbproduitscategories] wartościami, korzystając z konfiguracji uruchomienia [spring-jdbc-generic-04-fillDataBase]:
![]() |
Na koniec wywołaj w przeglądarce następujący adres: URL:
![]() |


























































