21. 跨域访问管理
21.1. 架构
现在我们将探讨跨域请求的问题。在文档 [Tutoriel AngularJS / Spring 4] 中,我们开发了一个客户端/服务器应用程序,其中客户端是应用程序 AngularJS:
![]() |
- Angular 应用程序中的页面 HTML / CSS / JS 来自服务器 [1];
- 在 [2] 中,服务 [dao] 向另一台服务器(即 [2])发起了请求。然而,运行 Angular 应用程序的浏览器禁止此类操作,因为这属于安全漏洞。 该应用只能向其来源服务器(即服务器 [1])发起请求;
实际上,说浏览器禁止 Angular 应用程序向 [2] 服务器发起请求并不准确。浏览器实际上会向 [2] 服务器查询,以确认该服务器是否允许非本源客户端向其发起请求。 这种共享技术被称为 CORS(跨源资源共享)。[2] 服务器通过发送特定的 HTTP 头部来表示同意。
为了展示可能遇到的问题,我们将创建一个客户端/服务器应用程序,其中:
- 服务器将是我们安全的 Web 服务器 / jSON;
- 客户端是一个简单的 HTML 页面,其中嵌入了向 Web 服务器 / jSON 发送请求的 JavaScript 代码;
我们将采用以下架构:
![]() |
- 在 [1] 中,一个 Web 应用程序提供 HTML / jS 页面;
- 在 [2] 中,浏览器执行 HTML 页面中嵌入的 JavaScript,以查询安全 Web 服务 [3];
21.2. [spring-cors-server-jdbc-generic]项目
21.2.1. 工作环境的搭建
![]() |
- 加载上述项目。[spring-cors-*] 项目位于 [<exemples>\spring-database-generic\spring-cors] 文件夹中;
- 按 Alt-F5 并重新生成所有 Maven 项目;
随后运行名为 [spring-cors-server-jdbc-generic] 的运行配置(需先启动 SGBD 和 MySQL),该配置将在 8081 端口启动 Web 服务:
![]() |
使用名为 [spring-jdbc-generic-04-fillDataBase] 的运行配置填充 [dbproduitscategories] 数据库:
![]() |
执行名为 [spring-cors-client-generic] 的运行配置,该配置将在端口 8082 上启动第二个 Web 应用程序(运行在另一个 Tomcat 实例上):
![]() |
使用浏览器访问 URL [http://localhost:8082/client.html]:
![]() |
- 在 [1] 中,请求所有类别的简短版本;
- 在 [2] 中,获取服务器返回的 jSON 响应;
21.2.2. 客户端的请求 [spring-cors-client-generic]
![]() |
![]() |
文件 [application.properties] 用于设置客户端 Web 应用程序的端口。其内容如下:
server.port=8082
因此:
- 客户端是一个可通过 URL [http://localhost:8082] 访问的 Web 应用程序;
- 服务器是一个可通过 URL [http://localhost:8081] 访问的 Web 应用程序;
由于客户端并非通过与服务器相同的端口进行连接,因此会引发跨域请求问题。实际上,[http://localhost:8081] 和 [http://localhost:8082] 属于两个不同的域名。
21.2.3. Maven 配置
该项目是一个 Maven 项目,包含以下 [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 /> <!-- 从存储库中查找父级 -->
</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>
<!-- 插件 -->
<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>
- 第 14-19 行:这是一个 Spring Boot 项目;
- 第 27-30 行:使用了 [spring-boot-starter-web] 依赖项,该依赖项自带 Tomcat 服务器和 Spring MVC;
21.2.4. jQuery和JavaScript的基础知识
![]() |
该Web应用程序返回以下唯一页面:
![]() |
该页面包含在浏览器中执行的 JavaScript 代码(jS)。 我们将介绍一些JavaScript基础知识,以便理解该代码。客户端将通过jQuery和[https://jquery.com/]库调用HTTP,这些库提供了许多简化JavaScript开发的函数。 我们创建一个静态文件 HTML [jQuery.html],并将其放置在 [static] 文件夹中:
![]() |
该文件内容如下:
<!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>
- 第 6 行:导入 jQuery;
- 第10-12行:ID为[element1]的页面元素。我们将对该元素进行操作。
我们需要下载文件 [jquery-2.1.3.min.js]。可在 jQuery 的最新版本中找到它,即 URL [http://jquery.com/download/]:

将下载的文件放置在 [static / js] 文件夹中:
![]() |
完成上述操作后,使用Chrome浏览器请求静态视图[jQuery.html]:
![]() |
在 Google Chrome 中,执行 [Ctrl-Maj-I] 以调出开发者工具 [3]。通过 [Console] 和 [4] 标签页,可以运行 JavaScript 代码。 下文将提供需输入的 JavaScript 命令并附上说明。
|
:返回所有具有 id [element1],因此通常包含 包含0或1个元素,因为 HTML。 | ![]() |
|
:将文本 [blabla] 应用于集合中的所有元素 。这将导致 页面显示的内容 | ![]() |
|
隐藏了集合中的元素。文本 [blabla] 不再显示。 | ![]() |
|
:重新显示集合。这让我们 可以看到,ID 为 [element1] 的元素 具有 CSS style='display : none;' 属性,这 该元素被隐藏。 | |
|
:显示集合中的元素。文本 [blabla] 再次显示。正是 CSS style='display : block;' 确保了这种 显示效果。 | ![]() |
|
:为所有元素设置一个属性 集合。此处的属性为 [style],其值为 [color: red]。文本 [blabla] 变为红色。 | ![]() |
![]() | |
![]() |
需要注意的是,在整个操作过程中,浏览器的URL并未发生变化。没有与Web服务器进行任何交互。所有操作都在浏览器内部完成。现在,让我们查看该页面的源代码:
<!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>
这是初始文本。它完全没有反映我们在第10-12行对元素所做的操作。在进行JavaScript调试时,这一点非常重要。因此,查看已显示页面的源代码通常是徒劳的。
21.2.5. 应用程序的代码 jS
让我们回到客户端应用程序页面的代码 HTML,该页面将调用 Web 服务 / jSON:
![]() |
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Spring MVC</title>
<script type="text/javascript" src="/js/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="/js/client.js"></script>
</head>
<body>
<h2>Client du service web / jSON</h2>
<form id="formulaire">
<!-- 方法 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 />
<!-- 提交的值 -->
<br /> Chaîne jSON à poster : <input type="text" id="posted" size="50" />
<!-- 确认按钮 -->
<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>
- 第 6 行:导入 jQuery 库;
- 第 7 行:导入我们将要编写的代码;
- 第11、15、17、21行:请记下页面组件的标识符[id]。JavaScript通过这些标识符引用这些组件;
代码 [client.js] 如下:
// 全局数据
var url;
var posted;
var response;
var method;
function requestServer() {
// 获取表单信息
var urlValue = url.val();
var postedValue = posted.val();
method = document.forms[0].elements['method'].value;
// 手动发起Ajax请求
if (method === "get") {
doGet(urlValue);
} else {
doPost(urlValue, postedValue);
}
}
function doGet(url) {
// 手动发起Ajax请求
$.ajax({
headers : {
'Authorization' : 'Basic YWRtaW46YWRtaW4='
},
url : 'http://localhost:8081' + url,
type : 'GET',
dataType : 'tex/plain',
beforeSend : function() {
},
success : function(data) {
// 文本结果
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// 系统错误
response.text(jqXHR.responseText);
}
})
}
function doPost(url, posted) {
// 手动发起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) {
// 文本结果
response.text(data);
},
complete : function() {
},
error : function(jqXHR) {
// 系统错误
response.text(jqXHR.responseText);
}
})
}
// 在加载文档时
$(document).ready(function() {
// 正在获取页面组件的引用
url = $("#url");
posted = $("#posted");
response = $("#response");
});
- 第71-75行:jS代码在文档于浏览器中加载完毕后执行;
- 第 73-75 行:获取文档 HTML 中三个区域的引用;
- 第2-5行:在文件jS中定义的所有函数中均有效的全局变量;
- 第 9 行:获取用户输入的 URL;
- 第 10 行:获取用户要提交的值;
- 第11行:获取用于请求第9行中URL的[get]或[post]方式:
- document 指浏览器加载的文档,即所谓的 DOM(文档对象模型),
- document.forms[0] 表示文档中的第一个表单,一个文档可能包含多个表单。此处仅有一个,
- document.forms[0].elements['method'] 表示具有 [name='method'] 属性的表单元素。共有两个:
<input type="radio" id="get" name="method" value="get" checked="checked" />GET
<input type="radio" id="post" name="method" value="post" />POST
- (续)
- document.forms[0].elements['method'].value 是将要提交给具有 [name='method'] 属性的组件的值。 已知提交的值是被选中单选按钮的 [value] 属性的值。因此,此处将取 ['get', 'post'] 中的某个字符串;
- 第 13-18 行:根据要使用的 HTTP 方法,执行 [doGet] 或 [doPost] 方法;
- 方法 jQuery [$.ajax] 会调用 HTTP;
- 第23-25行:向要求HTTP或[Authorization: Basic code]标头的服务器发起请求。我们为用户[admin / admin]创建该标头,因为只有该用户能够查询服务器;
- 第26行:用户将输入URL类型的[/getAllLongCategories, /saveCategories, ...]。因此需要补充这些URL;
- 第27行:应使用HTTP方法;
- 第28行:服务器返回jSON。将结果类型指定为[text/plain],以便按接收时的原样显示;
- 第33行:显示服务器的文本响应;
- 第 39 行:以文本格式显示可能出现的错误信息;
- 第 44 行:[doPost] 方法接收第二个参数,即待提交的值;
- 第 52 行:指定要提交的值将以字符串形式提交,即 jSON;
21.2.6. 客户端执行
客户端应用程序是一个由以下可执行类 [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);
}
}
- 第 6 行:注解 [@EnableAutoConfiguration] 是项目 [Spring Boot](第 4 行)的注解。 Spring Boot 将检查项目类路径中的文件。在此处,这些文件将包括由文件 [pom.xml] 的唯一依赖项引入的所有 Maven 依赖项:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
该依赖引入了大量包,特别是 Spring MVC 和一个 Tomcat 服务器。由于存在这些依赖,Spring Boot 将使用默认值配置一个在 Tomcat 上运行的 Spring 项目 MVC。 此时,Tomcat 服务器被配置为在 8080 端口上运行。 如果想要摆脱 Spring Boot 选择的默认值,可以使用位于 Classpath 根目录下的 [application.properties] 文件([src / main / resources] 中的所有内容都位于 Classpath 根目录下):
![]() |
我们通过以下方式指定 Tomcat 服务器应在 8082 端口上运行:
server.port=8082
可用参数列表可在 [application.properties] 至 URL(2015年6月)及 [http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html] 中找到;
返回 [Client.java] 的代码:
- 第10行:[SpringApplication.run]方法将把[client.html]页面部署到项目Classpath中的Tomcat服务器上;
21.2.7. URL [/getAllShortCategories]
![]() |
我们启动:
- 在端口 8081 上启动安全的 Web/JSON 服务器(配置 [spring-security-server-jdbc-generic]);
- 该服务器的客户端在8082端口上(配置[spring-cors-client-generic]);
随后我们请求 URL [http://localhost:8082/client.html] [1]:
![]() |
- 在 [2] 中,我们对 URL 和 [http://localhost:8081/getAllShortCategories] 执行 GET 操作;
我们无法从服务器获得响应。查看Chrome开发者控制台(Ctrl+Shift+I)时,发现了一个错误:
![]() |
- 在 [1] 时,当前所在标签页为 [Network];
- 在 [2] 标签页中,可以看到已发出的请求 HTTP 并非 [GET],而是 [OPTIONS]。 对于跨域请求,浏览器会通过向服务器发送请求 HTTP [OPTIONS],来验证是否满足若干条件。 在此情况下,请求即为[5-6]标记所指向的那些;
- 在 [5] 中,浏览器询问是否可以通过 GET 访问目标 URL。 请求 [Access-Control-Request-Method] 要求响应包含标头 HTTP [Access-Control-Allow-Methods],表明所请求的方法被接受;
- 在 [6] 中,浏览器发送了 HTTP [Origin: http://localhost:8081] 标头。 该标头要求在 HTTP [Access-Control-Allow-Origin] 标头中返回响应,表明已接受指定的源;
- 在 [7] 中,浏览器询问是否接受 HTTP、[accept] 和 [authorization] 这三个标头。 请求 [Access-Control-Request-Headers] 等待包含 HTTP 和 [Access-Control-Allow-Headers] 标头的响应,表明所请求的标头已被接受;
- 出现 [3] 错误。点击图标后,出现 [4] 错误;
- 在 [4] 中,消息提示服务器未发送 HTTP [Access-Control-Allow-Origin] 头部,该头部用于指示是否接受请求来源;
- 在 [8] 中,可以发现服务器确实未发送该标头。因此,浏览器拒绝执行最初请求的 HTTP GET 请求;
我们需要修改Web服务器 / jSON。
21.2.8. 一个新的 Web 服务 / json
我们创建一个新的 Maven 项目 [spring-cors-server-jdbc-generic]:
![]() |
新 Web 服务的 Maven 配置如下:
<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>
<!-- 插件 -->
<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>
- 第 30-32 行:我们利用安全 Web 服务器 / JSON 存档,整合了迄今为止所有已完成的工作成果;
最终,依赖项如下:
![]() |
配置类 [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 {
// 跨域请求
@Bean
public boolean isCorsEnabled() {
return true;
}
...
}
- 第 12 行:该类是 Spring 配置类;
- 第 9 行:其他 Spring 组件需从 [spring.cors.server.service] 包中获取;
- 第 14 行:导入 [spring-security-server-jdbc-generic] 项目的 Bean;
- 第18-21行:我们创建了一个名为[isCorsEnabled]的Spring组件,用于控制是否接受服务器域外的客户端;
21.2.9. 控制器
新Web服务包含四个控制器:
![]() |
- [CorsCategorieController] 负责管理处理类别的 URL。 它仅处理来自 Web 客户端的 CORS 请求头。否则,它将工作委托给依赖项 [spring-webjson-server-jdbc-generic] 中的控制器 [CategorieController];
- [CorsProduitController] 和 [CorsAuthenticateController] 同样将工作委托给依赖项 [spring-webjson-server-jdbc-generic] 中的控制器 [ProduitController] 以及依赖项 [AuthenticateController] 中的控制器[spring-security-server-jdbc-generic] 依赖项中的控制器;
- [CorsController] 用于提取上述三个控制器共有的部分;
21.2.9.1. 控制器 [CorsController]
类 [CorsController] 如下所示:
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;
// 向客户端发送选项
public void sendOptions(String origin, HttpServletResponse response) {
// 是否允许跨源请求?
if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
return;
}
// 设置 CORS 头部
response.addHeader("Access-Control-Allow-Origin", origin);
// 允许某些标头
response.addHeader("Access-Control-Allow-Headers", "accept, authorization");
// 允许 GET
response.addHeader("Access-Control-Allow-Methods", "GET");
}
}
- 第 8 行:类 [CorsController] 是一个 Spring 控制器;
- 第11-12行:注入Bean [isCorsEnabled],该Bean用于指示是否需要处理CORS头部;
- 第15-26行:方法[sendOptions]负责响应发送CORS头部的客户端;
- 第17-19行:如果应用程序配置为接受跨域请求,且请求发起方发送了HTTP和[Origin]这两个头部,并且该源以[http://localhost]开头, 则接受跨域请求,否则拒绝;
- 第 21 行:如果客户端位于 [http://localhost:port] 域中,则发送 HTTP 标头:
这意味着服务器接受该客户端的来源;
- 第22-25行:我们在请求HTTP [OPTIONS]中指出了两个特殊的HTTP标头:
针对请求头 HTTP [Access-Control-Request-X],服务器以请求头 HTTP [Access-Control-Allow-X] 进行响应,其中指明了允许的内容。 第22至25行仅重复客户端的请求,以表明该请求已被接受;
21.2.9.2. 控制器 [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) {
// 源方法
return categorieController.getAllShortCategories();
}
...
}
- 第19行:注解[@RestController]使该类同时成为Spring组件和控制器MVC,该控制器会自行向客户端发送响应;
- 第 20 行:类 [CorsCategorieController] 继承了我们刚刚看到的类 [CorsController];
- 第22-23行:将控制器[CategorieController categorieController]注入依赖项[spring-webjson-server-jdbc-generic];
- 第 25-29 行:处理 URL 和 [/cors-getAllShortCategories],当它们被 HTTP 和 [OPTIONS] 命令调用时。 根据约定,我们决定:希望调用安全Web服务中URL [/U]的Web客户端,实际上应调用URL [/cors-U]。 因此,部署的 Web 服务将包含两种类型的 URL:
- [/U]:用于非Web客户端;
- [/cors-U]:用于 Web 客户端;
- 第25行:[/cors-getAllShortCategories]方法支持以下参数:
- 对象 [@RequestHeader(value = "Origin", required = false)] 将从请求中获取标头 HTTP [Origin]。该标头由请求发起者发送:
说明 HTTP [Origin] 头部是可选的 [required = false]。 在此情况下,若该标头缺失,参数 [String origin] 的值将为 null。若使用默认值 [required = true],当标头缺失时将抛出异常。我们希望避免这种情况;
- (续)
- 将返回给发起请求的客户端的对象 [HttpServletResponse response];
这两个参数由 Spring 注入;
- 第28行:将请求的处理委托给父类[CorsController]中的方法[sendOptions];
- 第31-36行:当通过GET调用时,[getAllShortCategories]方法处理URL和[/cors-getAllShortCategories];
- 第 35 行:任务被委托给依赖项 [spring-webjson-server-jdbc-generic] 中的方法 [CategorieController.getAllShortCategories];
现在我们可以进行新的测试了。我们启动了Web服务的新版本,发现问题依然存在。没有任何变化。如果在上文第28行添加控制台输出,该输出从未显示,这表明第25行的[corsGetAllShortCategories]方法从未被调用。
经过一番调查,我们发现Spring MVC会自行处理HTTP和[OPTIONS]命令,并采用默认处理机制。 因此,响应始终由 Spring 处理,第 25 行中的 [corsGetAllShortCategories] 方法从未被调用。Spring MVC 的这种默认行为是可以更改的。我们修改现有的 [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 {
// 跨域请求
@Bean
public boolean isCorsEnabled() {
return true;
}
@Autowired
private DispatcherServlet dispatcherServlet;
@PostConstruct
public void init(){
// 应用程序自行处理请求QZXW2HTMLCSFRUUAZQX[OPTIONS]
dispatcherServlet.setDispatchOptionsRequest(true);
}
}
- 第 23-24 行:注入在依赖项 [spring-webjson-server-jdbc-generic] 中定义的组件 [DispatcherServlet dispatcherServlet];
- 第 26-30 行:注解 [@PostConstruct] 确保在实例化类 [AppConfig] 并完成 Spring 的注入操作后,将执行方法 [init];
- 第 29 行:要求该 Servlet 将命令 HTTP 和 [OPTIONS] 转发给应用程序;
我们使用此新配置重新进行测试。结果如下:
![]() |
- 在 [1] 中,我们看到有两个请求 HTTP 分别指向 URL 和 [http://localhost:8080/getAllCategories];
- 在 [2] 中,请求 [OPTIONS];
- 在 [3] 中,包含我们在服务器响应中刚刚配置的三个 HTTP 头部;
现在让我们来看第二个请求:
![]() |
- 在 [1] 中,即所分析的请求;
- 转换为 [2],即请求 GET。 通过第一个请求 [OPTIONS],浏览器已收到其请求的信息。现在,它执行最初请求的 [GET];
- 在 [3] 中,服务器返回响应;
- 在 [4] 中,服务器发送了 jSON;
- [5],发生错误;
- 在 [6] 中,错误消息;
这里的情况更难解释。服务器的响应 [3] 是正常的 [HTTP/1.1 200 OK]。 因此,我们理应已获得所请求的文档。可能的情况是,服务器确实发送了文档,但浏览器阻止了其使用,因为它要求针对请求 GET,响应中也必须包含标头 HTTP [Access-Control-Allow-Origin:http://localhost:8081]。
我们将处理 GET 的方法从 URL 修改为 [/cors-getAllShortCategories]:
@RequestMapping(value = "/cors-getAllShortCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllShortCategories(
@RequestHeader(value = "Origin", required = false) String origin, HttpServletResponse response) {
// 标头CORS
sendOptions(origin, response);
// 源方法
return categorieController.getAllShortCategories();
}
- 第5行:与处理HTTP和[OPTIONS]请求时相同, 服务器将针对请求 HTTP [GET] 发送请求头 HTTP CORS;
修改后,结果如下:
![]() |
我们确实获得了所有类别的简短版本。
21.2.9.3. URL [GET]
在 [CorsCategorieController, CorsProduitController, CorsAuthenticateController] 控制器中, 处理通过 [GET] 请求的 URL 的操作代码,遵循了之前处理 URL 和 [/cors-getAllShortArticles] 的操作模式。 读者可查阅本文档随附的示例代码。以下是控制器 [CorsProduitController] 中处理 URL 和 [/cors-getAllLongProduits] 的示例:
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) {
// CORS 头信息
sendOptions(origin, response);
// 原始方法
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]
让我们来看以下情况:
![]() |
- 将 POST [1] 映射到 URL [2];
- 在 [3] 中,该值已被发布。这是某个无商品分类的字符串 jSON;
- 最终,我们试图创建一个名为 [categorie[2]] 的分类;
目前我们不修改任何代码。所得结果如下:
![]() |
- 在 [1] 中,与 [GET] 的请求类似,浏览器会发起一个 [OPTIONS] 的请求;
- 在 [2] 时,它会为 [POST] 请求申请访问授权。此前该请求为 [GET];
- 在 [3] 中,它请求发送 HTTP 和 [accept, authorization, content-type] 这两个标头的授权。此前,仅包含前两个标头;
- 在 [4] 中,Web 服务未授予所有请求的权限,从而导致 [5] 错误;
我们将方法 [CorsController.sendOptions] 修改如下:
public void sendOptions(String origin, HttpServletResponse response) {
// 允许正文吗?
if (!isCorsEnabled || origin == null || !origin.startsWith("http://localhost")) {
return;
}
// 设置标头 CORS
response.addHeader("Access-Control-Allow-Origin", origin);
// 允许某些标头
response.addHeader("Access-Control-Allow-Headers", "accept, authorization, content-type");
// 允许 GET 和 POST
response.addHeader("Access-Control-Allow-Methods", "GET, POST");
}
}
- 第 9 行:添加了标头 HTTP [Content-Type](区分大小写);
- 第 11 行:添加了方法 HTTP [POST];
这意味着 [POST] 方法的处理方式与 [GET] 请求相同。 以下是在控制器 [CorsCategorieController] 中处理 URL 和 [/cors-saveCategories] 的示例:
@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) {
// CORS 标头
sendOptions(origin, response);
// 来源方法
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);
}
完成这些修改后,结果如下:
![]() |
类别 [categorie[2]] 已成功添加到数据库中。 SGBD 为其分配了主键 226。可通过方法 GET [/cors-getAllShortCategories] 进行验证:
![]() |
21.2.10. 结论
我们的应用程序现已支持跨域请求。这些请求可通过在类 [AppConfig] 中进行配置来启用或禁用:
package spring.cors.server.config;
...
@Configuration
@ComponentScan(basePackages = { "spring.cors.server.service" })
@Import({ spring.security.config.AppConfig.class })
public class AppConfig {
// 跨域请求
@Bean
public boolean isCorsEnabled() {
return true;
}
...
}
21.3. Eclipse 项目 [spring-cors-server-jpa-generic]
Web 服务 CORS 现将由项目 [spring-cors-server-jpa-generic] 实现,该项目基于项目 [spring-security-server-jpa-generic],后者通过 Spring Data JPA 管理数据库访问:
![]() |
项目 [spring-cors-server-jpa-generic] 是通过复制先前研究过的项目 [spring-cors-server-jdbc-generic] 获得的。
![]() |
接下来需要进行两处修改。第一处修改位于文件 [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>
<!-- 插件 -->
<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>
- 第30-32行:对安全Web服务[spring-security-server-jpa-generic]的依赖;
最终,该项目的依赖关系如下:
![]() |
注:按 Alt-F5 然后重新生成所有项目
第二项修改是更新类中报错的导入语句 [Alt-Maj-O]。
就这样。使用运行配置 [spring-cors-server-jpa-generic-hibernate-eclipselink] 启动 Web 服务 CORS:
![]() | ![]() |
然后启动通用客户端:
![]() |
并通过浏览器请求 URL、[1] 以及 GET。 在 [2] 中,可以看到返回的分类简短版本包含字段 [entityType],而之前的版本 JDBC 中并不包含该字段。
接下来我们将处理另外两个架构 CORS:
- 架构 CORS / JPA EclipseLink / DB2;
- CORS / JPA OpenJpa / Firebird;
21.4. 架构 CORS / JPA EclipseLink / DB2
我们将实现以下架构:
![]() |
加载以下项目:
![]() |
注意:按 Alt-F5 并重新生成所有 Maven 项目。
运行 SGBD 和 DB2,并确认数据库 [dbproduitscategories] 是否存在。如果不存在,请创建它(参见第 12.1.2 节)。
使用运行配置 [spring-security-create-users-hibernate-eclipselink] 在数据库 [dbproduitscategories] 中创建用户:
![]() | ![]() |

随后,使用名为 [spring-cors-server-jpa-generic-hibernate-eclipselink] 的运行配置及其名为 [spring-cors-client-generic] 的客户端启动 Web 服务 CORS:
![]() | ![]() |
使用运行配置 [spring-jdbc-generic-04-fillDataBase] 为表 [dbproduitscategories] 填充数据:
![]() |
最后,在浏览器中请求以下 URL:
![]() |
21.5. CORS / JPA OpenJPA / Firebird
现在我们将实现以下架构:
![]() |
加载以下项目:
![]() |
注意:按 Alt-F5 并重新生成所有 Maven 项目。
启动 SGBD Firebird 并确认 [dbproduitscategories] 数据库确实存在。如果不存在,请创建它(参见第 14.1.2 节)。
使用运行配置 [spring-security-create-users-openjpa] 在数据库 [dbproduitscategories] 中创建用户:
![]() | ![]() |

随后使用名为 [spring-cors-server-jpa-generic-openjpa] 的运行配置启动 Web 服务 CORS:
![]() | ![]() |
使用配置 [spring-cors-client-generic] 启动客户端 CORS:
![]() |
使用运行配置 [spring-jdbc-generic-04-fillDataBase] 将值填入表 [dbproduitscategories]:
![]() |
最后,在浏览器中请求以下 URL:
![]() |


























































