Spring MVC 与 CORS跨域的详细介绍
1. cors 简介
同源策略(same origin policy)是浏览器安全的基石。在同源策略的限制下,非同源的网站之间不能发送 ajax 请求的。
为了解决这个问题,w3c 提出了跨源资源共享,即 cors(cross-origin resource sharing)。
cors 做到了两点:
- 不破坏即有规则
- 服务器实现了 cors 接口,就可以跨源通信
基于这两点,cors 将请求分为两类:简单请求和非简单请求。
1.1 简单请求
可以先看下 cors 出现前的情况:跨源时能够通过 script 或者 image 标签触发 get 请求或通过表单发送一条 post 请求,但这两种请求 http 头信息中都不能包含任何自定义字段。
简单请求对应该规则,因此对简单请求的定义为:
请求方法是 head、get 或 post 且 http 头信息不超过以下几个字段:accept、accept-language、content-language、last-event-id、content-type(只限于 application/x-www-form-urlencoded、multipart/form-data、text/plain)。
比如有一个简单请求:
get /test http/1.1 accept: */* accept-encoding: gzip, deflate, sdch, br origin: http://www.examples.com host: www.examples.com
对于这样的简单请求,cors 的策略是请求时,**在头信息中添加一个 origin 字段**,服务器收到请求后,根据该字段判断是否允许该请求。
- 如果允许,则在 http 头信息中添加 access-control-allow-origin 字段,并返回正确的结果
- 如果不允许,则不在头信息中添加 access-control-allow-origin 字段。
浏览器先于用户得到返回结果,根据有无 access-control-allow-origin 字段来决定是否拦截该返回结果。
对于 cors 出现前的一些服务,cors 对他们的影响分两种情况:
- script 或者 image 触发的 get 请求不包含 origin 头,所以不受到 cors 的限制,依旧可用。
- 如果是 ajax 请求,http 头信息中会包含 origin 字段,由于服务器没有做任何配置,所以返回结果不会包含 access-control-allow-origin,因此返回结果会被浏览器拦截,接口依旧不可以被 ajax 跨源访问。
可以看出,cors 的出现,没有对”旧的“服务造成任何影响。
另外,除了提到的 access-control-allow-origin 还有几个字段用于描述 cors 返回结果:
- access-control-allow-credentials: 可选,用户是否可以发送、处理 cookie。
- access-control-expose-headers:可选,可以让用户拿到的字段。有几个字段无论设置与否都可以拿到的,包括:cache-control、content-language、content-type、expires、last-modified、pragma。
1.2 非简单请求
除了简单请求之外的请求,就是非简单请求。
对于非简单请求的跨源请求,**浏览器会在真实请求发出前**,增加一次 option 请求,称为预检请求(preflight request)。预检请求将真实请求的信息,包括请求方法、自定义头字段、源信息添加到 http 头信息字段中,询问服务器是否允许这样的操作。
比如对于 delete 请求:
options /test http/1.1 origin: http://www.examples.com access-control-request-method: delete access-control-request-headers: x-custom-header host: www.examples.com
与 cors 相关的字段有:
- access-control-request-method: 真实请求使用的 http 方法。
- access-control-request-headers: 真实请求中包含的自定义头字段。
服务器收到请求时,需要分别对 origin、access-control-request-method、access-control-request-headers 进行验证,验证通过后,会在返回 http 头信息中添加
access-control-allow-origin: http://www.examples.com access-control-allow-methods: get, post, put, delete access-control-allow-headers: x-custom-header access-control-allow-credentials: true access-control-max-age: 1728000
他们的含义分别是:
- access-control-allow-methods: 真实请求允许的方法
- access-control-allow-headers: 服务器允许使用的字段
- access-control-allow-credentials: 是否允许用户发送、处理 cookie
- access-control-max-age: 预检请求的有效期,单位为秒。有效期内,不会重复发送预检请求
当预检请求通过后,浏览器会发送真实请求到服务器。这就实现了跨源请求。
了解完 cors,接下来我们来搭建简单的 spring mvc 服务,并进一步了解 spring mvc 如何配置 cors。
2. spring mvc 环境搭建
打开 ,添加 web dependency,然后选择 generate project,下载 zip 文件,就得到了一个 spring boot demo。
解压 zip 文件,双击 pom.xml 打开或用 idea、eclipse 将项目按照 maven 导入。
根据 group、artifact、dependencies 填写的不同,项目目录结构可能有些许差别,我的项目结构如下:
├── src │ ├── main/java │ | └── net/xiayule/spring/cors │ | └── springbootcorstestapplication.java | └── resources | ├── static | ├── templates | └── application.properties | └── pom.xml
我们需要关心的只有 springbootcorstestapplication.java。在 springbootcorstestapplication.java 添加以下代码:
@restcontroller @springbootapplication public class springbootcorstestapplication { @requestmapping(value = "/test") public string greetings() { return "{\"project\":\"just a test\"}"; } public static void main(string[] args) { springapplication.run(springbootcorstestapplication.class, args); } }
@requestmapping(value = "/test")
的含义是该方法接受来自 /test 的请求,并且提供了对 http 的 get、post、delete 等方法的支持。
运行项目的方法为,在项目根目录执行 mvn spring-boot:run 启动应用,打开浏览器访问 http://localhost:8080 即可看到效果:
后端服务搭建好了,接着实现前端。为了简单,直接创建一个 test.html 文件,添加以下内容:
<!doctype html> <html> <head> <title>hello cors</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <script> $(document).ready(function() { $.ajax({ url: "http://localhost:8080/test", method: "post", contenttype: "application/json; charset=utf-8" }).then(function(data, status, jqxhr) { alert(data) }); }); </script> </head> <body> </body> </html>
使用浏览器打开该文件,就会触发一条向 http://localhost:8080 的请求。可以通过修改上面代码的 method,来触发不同类型的请求。
由于是直接使用浏览器打开,**网页的源为 null**, 当向源 http://localhost:8080 请求时,就变成了跨源请求,因此如果后端不加 cors 的配置,返回的 http 头信息中不会包含 access-control-allow-origin,因此浏览器会报出如下错误:
3. 配置 cors
一个应用可能会有多个 cors 配置,并且可以设置每个 cors 配置针对一个接口或一系列接口或者对所有接口生效。
举例来说,我们需要:
- 让 /test 接口支持跨源访问,而 /test/1 或 /api 等其它接口不支持跨源访问
- 让 /test/* 这一类接口支持跨源访问,而 /api 等其它接口不支持跨源访问
- 站点所有的接口都支持跨源访问
对第一种情况,如果想要对某一接口配置 cors,可以在方法上添加 crossorigin 注解:
@crossorigin(origins = {"http://localhost:9000", "null"}) @requestmapping(value = "/test", method = requestmethod.get) public string greetings() { return "{\"project\":\"just a test\"}"; }
第二种情况,如果想对一系列接口添加 cors 配置,可以在类上添加注解,对该类声明所有接口都有效:
crossorigin(origins = {"http://localhost:9000", "null"}) @restcontroller @springbootapplication public class springbootcorstestapplication { // xxx }
第三种情况,添加全局配置,则需要添加一个配置类:
@configuration public class webconfig extends webmvcconfigureradapter { @override public void addcorsmappings(corsregistry registry) { registry.addmapping("/**") .allowedorigins("http://localhost:9000", "null") .allowedmethods("post", "get", "put", "options", "delete") .maxage(3600) .allowcredentials(true); } }
另外,还可以通过添加 filter 的方式,配置 cors 规则,并手动指定对哪些接口有效。
@bean public filterregistrationbean corsfilter() { urlbasedcorsconfigurationsource source = new urlbasedcorsconfigurationsource(); corsconfiguration config = new corsconfiguration(); config.setallowcredentials(true); config.addallowedorigin("http://localhost:9000"); config.addallowedorigin("null"); config.addallowedheader("*"); config.addallowedmethod("*"); source.registercorsconfiguration("/**", config); // cors 配置对所有接口都有效 filterregistrationbean bean = newfilterregistrationbean(new corsfilter(source)); bean.setorder(0); return bean; }
4. 实现剖析
无论是通过哪种方式配置 cors,其实都是在构造 corsconfiguration。
一个 cors 配置用一个 corsconfiguration 类来表示,它的定义如下:
public class corsconfiguration { private list<string> allowedorigins; private list<string> allowedmethods; private list<string> allowedheaders; private list<string> exposedheaders; private boolean allowcredentials; private long maxage; }
spring mvc 中对 cors 规则的校验,都是通过委托给 defaultcorsprocessor 实现的。
defaultcorsprocessor 处理过程如下:
- 判断依据是 header 中是否包含 origin。如果包含则说明为 cors 请求,转到 2;否则,说明不是 cors 请求,不作任何处理。
- 判断 response 的 header 是否已经包含 access-control-allow-origin,如果包含,证明已经被处理过了, 转到 3,否则不再处理。
- 判断是否同源,如果是则转交给负责该请求的类处理
- 是否配置了 cors 规则,如果没有配置,且是预检请求,则拒绝该请求,如果没有配置,且不是预检请求,则交给负责该请求的类处理。如果配置了,则对该请求进行校验。
校验就是根据 corsconfiguration 这个类的配置进行判断:
- 判断 origin 是否合法
- 判断 method 是否合法
- 判断 header 是否合法
- 如果全部合法,则在 response header 中添加响应的字段,并交给负责该请求的类处理,如果不合法,则拒绝该请求。
5. 总结
本文介绍了 cors 的知识以及如何在 spring mvc 中配置 cors。希望对大家的学习有所帮助,也希望大家多多支持。