Skip to content

21. Cross-domain access management

21.1. Architecture

We will now examine the issue of cross-domain requests. In document [Tutoriel AngularJS / Spring 4], we develop a client/server application where the client is an application AngularJS:

  • the HTML / CSS / JS pages of the Angular application come from the [1] server;
  • in [2], the [dao] service makes a request to another server, the [2] server. Well, that is prohibited by the browser running the Angular application because it is a security vulnerability. The application can only query the server it came from, i.e., the [1] server;

In fact, it is inaccurate to say that the browser prevents the Angular application from querying the server [2]. The browser actually queries the server [2] to find out if it allows a client that does not originate from its own domain to query it. This sharing technique is called Cross-Origin Resource Sharing (CORS). The [2] server grants permission by sending specific headers.

To demonstrate the issues that can arise, we will create a client/server application where:

  1. the server will be our secure web server;
  2. the client will be a simple HTML page equipped with Javascript code that will make requests to the web server / jSON;

We will implement the following architecture:

  • in [1], a web application delivers pages HTML / jS;
  • In [2], the browser executes the Javascript embedded in the HTML pages to query the secure web service [3];

21.2. The [spring-cors-server-jdbc-generic] project

21.2.1. Setting up the work environment

  
  1. Load the projects listed above. The [spring-cors-*] projects can be found in the [<exemples>\spring-database-generic\spring-cors] folder;
  2. Press Alt-F5 and regenerate all Maven projects;

Then run the run configuration named [spring-cors-server-jdbc-generic] (SGBD and MySQL must be launched), which starts a web service on port 8081:

 

Populate the [dbproduitscategories] database with the run configuration named [spring-jdbc-generic-04-fillDataBase]:

 

Run the execution configuration named [spring-cors-client-generic], which launches a second web application (on a different Tomcat instance) on port 8082:

 

Using a browser, request URL from [http://localhost:8082/client.html]:

  1. In [1], we request the short version of all categories from version;
  2. in [2], the server’s response jSON;

21.2.2. The client project [spring-cors-client-generic]

  

The file [application.properties] allows us to set the port for the client web application. Its contents are as follows:


server.port=8082

Thus:

  • the client is a web application available at URL [http://localhost:8082];
  • the server is a web application available at URL [http://localhost:8081];

Because the client is not accessed from the same port as the server, the issue of cross-domain requests arises. Indeed, [http://localhost:8081] and [http://localhost:8082] are two different domains.

21.2.3. Maven Configuration

The project is a Maven project with the following [pom.xml] file:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>dvp.spring.database</groupId>
    <artifactId>spring-cors-client-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
 
    <name>spring-cors-client-generic</name>
    <description>Client cors for webjson server</description>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
 
    <!-- plugins -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
 
</project>
  1. lines 14–19: this is a Spring Boot project;
  2. lines 27-30: we use the [spring-boot-starter-web] dependency, which includes a Tomcat server and Spring MVC;

21.2.4. Basics of jQuery and Javascript

The web application delivers the following single page:

 

It includes Javascript (jS) code that runs in the browser. We will cover some basics of Javascript to help us understand the code. The client will make HTTP calls using the jQuery [https://jquery.com/] library, which provides numerous functions that facilitate Javascript development. We create a static file HTML [jQuery.html] and place it in the [static] folder:

 

This file will have the following content:


<!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>
  • line 6: import of jQuery;
  • lines 10-12: an element from the id [element1] page. We’re going to play around with this element.

We need to download the [jquery-2.1.3.min.js] file. It can be found in the last version from jQuery to URL [http://jquery.com/download/]:

Image

Place the downloaded file in the [static / js] folder:

  

Once this is done, request the static view [jQuery.html] using Chrome [1-2]:

In Google Chrome, press [Ctrl-Maj-I] to bring up the developer tools [3]. The [Console] [4] tab allows you to run code Javascript. Below, we provide Javascript commands to type and explain them.

JS
result
$("#element1")
: returns the collection of all elements in id
[element1], so normally a collection
of 0 or 1 element because you cannot have
two identical id elements on a HTML page.
$("#element1").text("blabla")
: sets the text to [blabla] for all elements in
the collection. This changes the
content displayed by the page
$("#element1").hide()
hides the elements in the collection. The text
[blabla] is no longer displayed.
$("#element1")
: displays the collection again. This
allows us to see that the element id [element1] has
the attribute CSS style='display: none;', which
the element to be hidden.
$("#element1").show()
: displays the elements in the collection. The text
[blabla] appears again. It is the
CSS style='display: block;' that ensures this
display.
$("#element1").attr('style','color: red')
: sets an attribute on all elements in the
collection. The attribute here is [style] and its value
[color: red]. The text [blabla] turns red.
Table
Dictionary

Note that the browser's URL has not changed during all these operations. There has been no communication with the web server. Everything happens within the browser. Now, let's view the page's source code:


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

This is the original text. It does not reflect the changes made to the element in lines 10–12. It is important to keep this in mind when debugging Javascript. In such cases, it is often unnecessary to view the source code of the displayed page.

21.2.5. The application's jS code

Let’s return to the code for the client application page that will query the web service:

 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Spring MVC</title>
<script type="text/javascript" src="/js/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="/js/client.js"></script>
</head>
<body>
    <h2>Client du service web / jSON</h2>
    <form id="formulaire">
        <!--  method HTTP -->
        Méthode HTTP :
        <!--  -->
        <input type="radio" id="get" name="method" value="get" checked="checked" />GET
        <!--  -->
        <input type="radio" id="post" name="method" value="post" />POST
        <!--  URL -->
        <br /> <br />URL cible : <input type="text" id="url" size="30"><br />
        <!-- posted value -->
        <br /> Chaîne jSON à poster : <input type="text" id="posted" size="50" />
        <!-- validation button -->
        <br /> <br /> <input type="submit" value="Valider" onclick="javascript:requestServer(); return false;"></input>
    </form>
    <hr />
    <h2>Réponse du serveur</h2>
    <div id="response"></div>
</body>
</html>
  • line 6: we import the jQuery library;
  • line 7: we import code that we will write;
  • lines 11, 15, 17, 21: note the [id] identifiers of the page components. javascript references these components via these identifiers;

The code [client.js] is as follows:


// global data
var url;
var posted;
var response;
var method;
 
function requestServer() {
    // retrieve information from the form
    var urlValue = url.val();
    var postedValue = posted.val();
    method = document.forms[0].elements['method'].value;
    // make a manual Ajax call
    if (method === "get") {
        doGet(urlValue);
    } else {
        doPost(urlValue, postedValue);
    }
}
 
function doGet(url) {
    // make a manual Ajax call
    $.ajax({
        headers : {
            'Authorization' : 'Basic YWRtaW46YWRtaW4='
        },
        url : 'http://localhost:8081' + url,
        type : 'GET',
        dataType : 'tex/plain',
        beforeSend : function() {
        },
        success : function(data) {
            // text result
            response.text(data);
        },
        complete : function() {
        },
        error : function(jqXHR) {
            // system error
            response.text(jqXHR.responseText);
        }
    })
}
 
function doPost(url, posted) {
    // make a manual Ajax call
    $.ajax({
        headers : {
            'Authorization' : 'Basic YWRtaW46YWRtaW4='
        },
        url : 'http://localhost:8081    ' + url,
        type : 'POST',
        contentType : 'application/json',
        data : posted,
        dataType : 'tex/plain',
        beforeSend : function() {
        },
        success : function(data) {
            // text result
            response.text(data);
        },
        complete : function() {
        },
        error : function(jqXHR) {
            // system error
            response.text(jqXHR.responseText);
        }
    })
}
 
// document loading
$(document).ready(function() {
    // retrieve page component references
    url = $("#url");
    posted = $("#posted");
    response = $("#response");
});
  • lines 71-75: jS code executed after the document has finished loading in the browser;
  • lines 73-75: retrieves the references of three fields from the HTML document;
  • lines 2-5: global variables known throughout all functions defined in the jS file;
  • line 9: retrieves the URL entered by the user;
  • line 10: retrieve the value the user wants to post;
  • line 11: retrieve the method ([get] or [post]) to use for requesting the URL from line 9:
    • document refers to the document loaded by the browser, known as the DOM (Document Object Model),
    • document.forms[0] refers to the first form in the document; a document may contain multiple forms. Here, there is only one,
    • document.forms[0].elements['method'] refers to the form element that has the attribute [name='method']. There are two of them:

<input type="radio" id="get" name="method" value="get" checked="checked" />GET
<input type="radio" id="post" name="method" value="post" />POST
  • (continued)
    • document.forms[0].elements['method'].value is the value that will be posted for the component with the attribute [name='method']. We know that the posted value is the value of the [value] attribute of the selected radio button. Here, it will therefore be one of the strings ['get', 'post'];
  • lines 13–18: depending on which HTTP method to use, either the [doGet] or [doPost] method is executed;
  • The method jQuery [$.ajax] makes a call to HTTP;
  • Lines 23–25: We are communicating with a server that requires a header HTTP [Authorization: Basic code]. We create this header for the user [admin / admin], who is the only one authorized to query the server;
  • line 26: the user will enter URL of the type [/getAllLongCategories, /saveCategories, ...]. These URL must therefore be completed;
  • line 27: HTTP method to be used;
  • line 28: the server returns jSON. We specify the type [text/plain] as the result type in order to display it as received;
  • line 33: display of the server's text response;
  • line 39: display any error message in text format;
  • line 44: the [doPost] method receives a second parameter, which is the value to be posted;
  • line 52: to indicate that the posted value will be in the form of a jSON string;

21.2.6. Client Execution

The client application is a console application launched by the following executable class [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);
    }
}
  • Line 6: The annotation [@EnableAutoConfiguration] is an annotation from the [Spring Boot] project (line 4). Spring Boot will inspect the archives present in the project's classpath. In this case, these will be all the Maven dependencies brought in by the single dependency of the [pom.xml] file:

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

This dependency includes a large number of artifacts, notably Spring MVC and a Tomcat server. Because of these dependencies, Spring Boot will configure, using default values, a Spring MVC project running on Tomcat. The Tomcat server is then configured to run on port 8080. If you want to override the default values chosen by Spring Boot, you can use the [application.properties] file at the root of the Classpath (everything in [src / main / resources] is at the root of the Classpath):

  

We specify that the Tomcat server should run on port 8082 as follows:


server.port=8082

The list of parameters that can be used in [application.properties] can be found in URL (June 2015) [http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html];

Back to the code in [Client.java]:

  • line 10: the [SpringApplication.run] method will deploy the [client.html] page to the Tomcat server present in the project’s classpath;

21.2.7. URL [/getAllShortCategories]

We launch:

  1. the secure web server / json on port 8081 (configuration [spring-security-server-jdbc-generic]);
  2. the client for this server on port 8082 (configuration [spring-cors-client-generic]);

then we request URL [http://localhost:8082/client.html] [1]:

  • in [2], we perform a GET on URL and [http://localhost:8081/getAllShortCategories];

We do not receive a response from the server. When we look at the Chrome DevTools (Ctrl-Shift-I), we see an error:

  1. in [1], we are in the [Network] tab;
  2. In [2], we see that the request HTTP that was made is not [GET] but [OPTIONS]. In the case of a cross-domain request, the browser checks with the server to ensure that certain conditions are met by sending it a request HTTP [OPTIONS]. In this case, the requests are those indicated by the dots [5-6];
  3. in [5], the browser asks whether the URL target can be reached with a GET. The [Access-Control-Request-Method] request asks for a response with a HTTP [Access-Control-Allow-Methods] header indicating that the requested method is accepted;
  4. in [6], the browser sends the header HTTP [Origin: http://localhost:8081]. This header requests a response in a HTTP [Access-Control-Allow-Origin] header indicating that the specified origin is accepted;
  5. In [7], the browser asks whether the headers HTTP, [accept], and [authorization] are accepted. The [Access-Control-Request-Headers] request expects a response with a HTTP or [Access-Control-Allow-Headers] header indicating that the requested headers are accepted;
  6. an error occurs in [3]. Clicking on the icon results in the error [4];
  7. in [4], the message indicates that the server did not send the header HTTP [Access-Control-Allow-Origin], which indicates whether the origin of the request is accepted;
  8. in [8], we can see that the server did indeed not send this header. As a result, the browser refused to make the HTTP GET request that was initially requested;

We need to modify the web server / jSON.

21.2.8. A new web service / json

We create a new Maven project [spring-cors-server-jdbc-generic]:

 

The Maven configuration for the new web service is as follows:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>dvp.spring.database</groupId>
    <artifactId>spring-cors-server-jdbc-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>
 
    <name>spring-cors-server-jdbc-generic</name>
    <description>démo spring cors</description>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>
 
    <!-- plugins -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
 
    <dependencies>
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-security-server-jdbc-generic</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
  • Lines 30–32: We incorporate all the work done so far by using the secure web server archive /json;

In the end, the dependencies are as follows:

  

The configuration class [AppConfig] is as follows:

  

package spring.cors.server.config;
 
import javax.annotation.PostConstruct;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.DispatcherServlet;

@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ spring.security.config.AppConfig.class })
public class AppConfig {
 
    // cross-domain queries
    @Bean
    public boolean isCorsEnabled() {
        return true;
    }
...
}
  • line 12: the class is a Spring configuration class;
  • line 9: other Spring components can be found in the [spring.cors.server.service] package;
  • line 14: we import the beans from the [spring-security-server-jdbc-generic] project;
  • lines 18–21: we create a Spring component named [isCorsEnabled] that determines whether or not to accept clients beans from outside the server domain;

21.2.9. The controllers

The new web service has four controllers:

  
  1. [CorsCategorieController] handles URL requests for category processing. It handles only the CORS headers of the clients web requests. Otherwise, it delegates the work to the [CategorieController] controller of the [spring-webjson-server-jdbc-generic] dependency;
  2. [CorsProduitController] and [CorsAuthenticateController] do the same by delegating the work to the [ProduitController] controllers of the [spring-webjson-server-jdbc-generic] dependency and the [AuthenticateController] controllers of the[spring-security-server-jdbc-generic];
  3. [CorsController] is used to factor out what is common to the three previous controllers;

21.2.9.1. The controller [CorsController]

The [CorsController] class is as follows:


package spring.cors.server.service;
 
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CorsController {
 
    @Autowired
    private boolean isCorsEnabled;
 
    // sending options to the customer
    public void sendOptions(String origin, HttpServletResponse response) {
        // Cors allowed ?
        if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
            return;
        }
        // set header CORS
        response.addHeader("Access-Control-Allow-Origin", origin);
        // certain headers are allowed
        response.addHeader("Access-Control-Allow-Headers", "accept, authorization");
        // we authorize GET
        response.addHeader("Access-Control-Allow-Methods", "GET");
    }
}
  1. line 8: the [CorsController] class is a Spring controller;
  2. lines 11-12: injection of the [isCorsEnabled] bean, which indicates whether or not to handle the CORS headers;
  3. lines 15–26: the [sendOptions] method handles responses to clients requests that send CORS headers;
  4. lines 17-19: if the application is configured to accept cross-domain requests, and if the sender has sent the HTTP header, and if this origin begins with [http://localhost], then the cross-domain request is accepted; otherwise, it is rejected;
  5. line 21: if the client is in the domain [http://localhost:port], we send the header HTTP:
Access-Control-Allow-Origin:  http://localhost:port

which means that the server accepts the client's origin;

  1. lines 22–25: we have specified two specific HTTP headers in the HTTP [OPTIONS] request:
Access-Control-Request-Method: GET
Access-Control-Request-Headers: accept, authorization

In response to the HTTP [Access-Control-Request-X] header, the server responds with a HTTP [Access-Control-Allow-X] header in which it specifies what is authorized. Lines 22–25 simply repeat the client’s request to indicate that it has been accepted;

21.2.9.2. The controller [CorsCategorieController]


package spring.cors.server.service;
 
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import spring.jdbc.entities.Categorie;
import spring.webjson.server.entities.CoreCategorie;
import spring.webjson.server.service.CategorieController;
import spring.webjson.server.service.Response;
 
@RestController
public class CorsCategorieController extends CorsController {
 
    @Autowired
    private CategorieController categorieController;
 
    @RequestMapping(value = "/cors-getAllShortCategories", method = RequestMethod.OPTIONS)
    public void corsGetAllShortCategories(@RequestHeader(value = "Origin", required = false) String origin,
            HttpServletResponse response) {
        sendOptions(origin, response);
    }
 
    @RequestMapping(value = "/cors-getAllShortCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllShortCategories(
            @RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse response) {
        // original method
        return categorieController.getAllShortCategories();
    }
 
...
}
  1. line 19: the annotation [@RestController] makes the class both a Spring component and a MVC controller that sends its own responses to the client;
  2. line 20: the [CorsCategorieController] class extends the [CorsController] class we just saw;
  3. lines 22–23: inject the [CategorieController categorieController] controller from the [spring-webjson-server-jdbc-generic] dependency;
  4. lines 25–29: handle the URL [/cors-getAllShortCategories] when it is requested with the HTTP [OPTIONS] command. By convention, we decide that web services that want to call the URL [/U] of the secure web service must actually call theURL [/cors-U]. The deployed web service will thus have two types of URL:
    1. [/U]: for non-web clients;
    2. [/cors-U]: for web-based clients;
  5. Line 25: The [/cors-getAllShortCategories] method accepts the following parameters:
    1. the [@RequestHeader(value = "Origin", required = false)] object, which retrieves the HTTP [Origin] header from the request. This header was sent by the request sender:
Origin:http://localhost:8082

It is specified that the HTTP [Origin] header is optional [required = false]. In this case, if the header is missing, the [String origin] parameter will have a null value. With [required = true], which is the default value, an exception is thrown if the header is missing. We wanted to avoid this scenario;

  • (continued)
    • the [HttpServletResponse response] object that will be returned to the client who made the request;

These two parameters are injected by Spring;

  1. line 28: we delegate the processing of the request to the [sendOptions] method of the parent class [CorsController];
  2. lines 31–36: the [getAllShortCategories] method processes URL and [/cors-getAllShortCategories] when called with a GET;
  3. line 35: the task is delegated to the [CategorieController.getAllShortCategories] method of the [spring-webjson-server-jdbc-generic] dependency;

We are now ready for further testing. We launch the new version from the web service and find that the problem remains. Nothing has changed. If we add a console output on line 28 above, it is never displayed, indicating that the [corsGetAllShortCategories] method on line 25 is never called.

After some research, we discover that Spring MVC handles the commands HTTP and [OPTIONS] itself using default processing. Therefore, it is always Spring that responds, and never the [corsGetAllShortCategories] method on line 25. This default behavior of Spring MVC can be changed. We modify the existing [AppConfig] class:

  

package spring.cors.server.config;
 
import javax.annotation.PostConstruct;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.DispatcherServlet;
 
@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ spring.security.config.AppConfig.class })
public class AppConfig {
 
    // cross-domain queries
    @Bean
    public boolean isCorsEnabled() {
        return true;
    }
    
    @Autowired
    private DispatcherServlet dispatcherServlet;
    
    @PostConstruct
    public void init(){
        // the application processes requests itself HTTP [OPTIONS]
        dispatcherServlet.setDispatchOptionsRequest(true);
    }
}
 
  1. lines 23-24: the [DispatcherServlet dispatcherServlet] component, which was defined in the [spring-webjson-server-jdbc-generic] dependency, is injected;
  2. lines 26-30: the [@PostConstruct] annotation ensures that the [init] method will be executed after the [AppConfig] class is instantiated and after the injections performed by Spring;
  3. line 29: we instruct the servlet to forward the commands HTTP and [OPTIONS] to the application;

We rerun the tests with this new configuration. We obtain the following result:

  • in [1], we see that there are two requests HTTP to URL and [http://localhost:8080/getAllCategories];
  • in [2], the request [OPTIONS];
  • in [3], the three headers HTTP that we just configured in the server response;

Let’s now examine the second request:

  1. in [1], the request being examined;
  2. in [2], this is the request GET. Thanks to the first request, [OPTIONS], the browser received the information it requested. It is now making the request [GET] that was initially requested;
  3. in [3], the server’s response;
  4. in [4], the server sends jSON;
  5. in [5], an error occurred;
  6. in [6], the error message;

It is more difficult to explain what happened here. The server’s response [3] is normal [HTTP/1.1 200 OK]. We should therefore have the requested document. It is possible that the server did indeed send the document but that the browser is preventing its use because it requires that the response for the GET request also include the HTTP header [Access-Control-Allow-Origin:http://localhost:8081].

We modify the method that handles GET from URL [/cors-getAllShortCategories]:


    @RequestMapping(value = "/cors-getAllShortCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllShortCategories(
            @RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse response) {
        // headers CORS
        sendOptions(origin, response);
        // original method
        return categorieController.getAllShortCategories();
}
  1. line 5: as with the request HTTP [OPTIONS], the server will send the headers HTTP CORS for a request HTTP [GET];

After this change, the results are as follows:

 

We have indeed obtained the short version for all categories.

21.2.9.3. The URL [GET]

In the [CorsCategorieController, CorsProduitController, CorsAuthenticateController] controllers, the code for the actions that process the requested URL with a [GET] follows the pattern of the actions that previously processed the URL and [/cors-getAllShortArticles]. The reader can verify the code in the examples provided with this document. Here is an example for the URL and [/cors-getAllLongProduits] from the [CorsProduitController] controller:


package spring.cors.server.service;
 
import java.util.List;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import spring.jdbc.entities.Produit;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.service.ProduitController;
import spring.webjson.server.service.Response;
 
@RestController
public class CorsProduitController extends CorsController {
 
    @Autowired
    private ProduitController produitController;
 
@RequestMapping(value = "/cors-getAllLongProduits", method = RequestMethod.GET)
    public Response<List<Produit>> getAllLongProduits(@RequestHeader(value = "Origin", required = false) String origin,HttpServletResponse response) {
        // headers CORS
        sendOptions(origin, response);
        // original method
        return produitController.getAllLongProduits();
 
    }
 
    @RequestMapping(value = "/cors-getAllLongProduits", method = RequestMethod.OPTIONS)
    public void corsGetAllLongProduits(@RequestHeader(value = "Origin", required = false) String origin,
            HttpServletResponse response) {
        sendOptions(origin, response);
    }
...
}
 

21.2.9.4. URL [POST]

Let's examine the following case:

  • we perform a POST [1] to the URL [2];
  • in [3], the posted value. This is the string jSON from a category with no products;
  • ultimately, we want to create a category named [categorie[2]];

We are not modifying any code at this time. The result obtained is as follows:

  • in [1], as with the [GET] requests, a [OPTIONS] request is made by the browser;
  • in [2], it requests access authorization for a [POST] request. Previously, this was [GET];
  • in [3], it requests authorization to send the headers HTTP and [accept, authorization, content-type]. Previously, only the first two headers were present;
  • in [4], the web service does not grant all the requested permissions, which causes the error [5];

We modify the [CorsController.sendOptions] method as follows:


    public void sendOptions(String origin, HttpServletResponse response) {
        // Cors allowed ?
        if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
            return;
        }
        // set header CORS
        response.addHeader("Access-Control-Allow-Origin", origin);
        // certain headers are allowed
        response.addHeader("Access-Control-Allow-Headers", "accept, authorization, content-type");
        // we authorize GET and POST
        response.addHeader("Access-Control-Allow-Methods", "GET, POST");
    }
}
  • line 9: we added the header HTTP [Content-Type] (case is not important);
  • line 11: the method HTTP [POST] has been added;

This means that the [POST] methods are handled in the same way as the [GET] requests. Here is an example of URL and [/cors-saveCategories] in the [CorsCategorieController] controller:


    @RequestMapping(value = "/cors-saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request,
            @RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse response) {
        // headers CORS
        sendOptions(origin, response);
        // original method
        return categorieController.saveCategories(request);
    }
 
    @RequestMapping(value = "/cors-saveCategories", method = RequestMethod.OPTIONS)
    public void corsSaveCategories(@RequestHeader(value = "Origin", required = false) String origin,
            HttpServletResponse response) {
        sendOptions(origin, response);
}

With these changes made, the result is as follows:

 

The category [categorie[2]] has been successfully added to the database. SGBD assigned it the primary key 226. This can be verified using the GET [/cors-getAllShortCategories] method:

 

21.2.10. Conclusion

Our application now supports cross-domain requests. These can be enabled or disabled via configuration in the [AppConfig] class:


package spring.cors.server.config;
 
...
 
@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ spring.security.config.AppConfig.class })
public class AppConfig {
 
    // cross-domain queries
    @Bean
    public boolean isCorsEnabled() {
        return true;
    }
...
}

21.3. The Eclipse project [spring-cors-server-jpa-generic]

The CORS web service will now be implemented by the [spring-cors-server-jpa-generic] project, whichis based on the [spring-security-server-jpa-generic] project, which manages database access using Spring Data JPA:

The [spring-cors-server-jpa-generic] project is created by cloning the previously examined [spring-cors-server-jdbc-generic] project.

  

Next, there are two changes to make. The first is in the file [pom.xml]:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>dvp.spring.database</groupId>
    <artifactId>spring-cors-server-jpa-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>
 
    <name>spring-cors-server-jpa-generic</name>
    <description>démo spring cors</description>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>
 
    <!-- plugins -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
 
    <dependencies>
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-security-server-jpa-generic</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
  1. lines 30–32: the dependency on the secure web service [spring-security-server-jpa-generic];

In the end, the project dependencies are as follows:

  

Note: Press Alt-F5, then regenerate all projects

The second change is to update the imports in the classes reporting [Alt-Maj-O] errors.

That’s it. We launch the CORS web service with the [spring-cors-server-jpa-generic-hibernate-eclipselink] runtime configuration:

Then launch the generic client:

and using a browser, request URL and [1] with GET. In [2], we see that the list of returned categories in version includes the [entityType] field, which was not present in the previous version or JDBC.

We will now examine two other CORS architectures:

  1. CORS / JPA EclipseLink / DB2;
  2. CORS / JPA OpenJpa / Firebird;

We will implement the following architecture:

Load the following projects:

  

Note: Press Alt-F5 and regenerate all Maven projects.

Run SGBD and DB2 and verify that the [dbproduitscategories] database exists. If not, create it (section 12.1.2).

Users are created in the [dbproduitscategories] database using the [spring-security-create-users-hibernate-eclipselink] runtime configuration:

Image

Then start the CORS web service with the execution configuration named [spring-cors-server-jpa-generic-hibernate-eclipselink] and its client named [spring-cors-client-generic]:

Populate the [dbproduitscategories] database with values using the [spring-jdbc-generic-04-fillDataBase] runtime configuration:

 

Finally, request the following URL in a browser:

 

21.5. Architecture CORS / JPA OpenJPA / Firebird

We will now implement the following architecture:

Load the following projects:

  

Note: Press Alt-F5 and regenerate all Maven projects.

Launch SGBD Firebird and verify that the [dbproduitscategories] database exists. If not, create it (section 14.1.2).

We create users in the [dbproduitscategories] database using the [spring-security-create-users-openjpa] runtime configuration:

Image

Then start the CORS web service with the execution configuration named [spring-cors-server-jpa-generic-openjpa]:

Start the client CORS with the configuration [spring-cors-client-generic]:

 

Populate the [dbproduitscategories] database with values using the [spring-jdbc-generic-04-fillDataBase] runtime configuration:

 

Finally, request the following URL in a browser: