# Spring中CORS 支持
> 摘要:CORS概念,在Spring中CORS的支持:在Controller方法CORS配置、全局配置、自定义配置、CORS 支持的过滤。全局配置又分为JavaConfig和XML。如果您是Spring初学者,请前往官网文档学习。
在介绍Spring中CORS的支持之前,一起回顾一下,什么是CORS?你真的了解CORS吗?CORS的原理是什么?
`跨源资源共享` ([CORS](https://developer.mozilla.org/zh-CN/docs/Glossary/CORS))(或通俗地译为跨域资源共享)是一种基于 [HTTP](https://developer.mozilla.org/zh-CN/docs/Glossary/HTTP) 头的机制,该机制通过允许服务器标示除了它自己以外的其它 [origin](https://developer.mozilla.org/zh-CN/docs/Glossary/Origin)(域,协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的"预检"请求。在预检中,浏览器发送的头中标示有HTTP方法和真实请求中会用到的头。
> 也就是说,在配置CORS,有需要配置相关的域、协议、端口。
跨源HTTP请求的一个例子:运行在 `https://domain-a.com` 的 JavaScript 代码使用 [`XMLHttpRequest`](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest) 来发起一个到 `https://domain-b.com/data.json` 的请求。
看一看下面的跨域示意图,或许你会更加明白CORS到底是怎么回事?
出于安全性,浏览器限制脚本内发起的跨源HTTP请求。 例如,`XMLHttpRequest` 和 [Fetch API](https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API) 遵循[同源策略](https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy)。这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。
![img](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/cors_principle.png)
跨源域资源共享([CORS](https://developer.mozilla.org/zh-CN/docs/Glossary/CORS))机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 [`XMLHttpRequest`](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest) 或 [Fetch](https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API))使用 CORS,以降低跨源 HTTP 请求所带来的风险。
上文降到`跨源资源共享` ([CORS](https://developer.mozilla.org/zh-CN/docs/Glossary/CORS))(或通俗地译为跨域资源共享)是一种基于 [HTTP](https://developer.mozilla.org/zh-CN/docs/Glossary/HTTP) 头的机制。如何实现`HTTP`头,就能实现`CORS`跨源资源共享。
比如说,假如站点 `https://foo.example` 的网页应用想要访问 `https://bar.other` 的资源。`foo.example` 的网页中可能包含类似于下面的 JavaScript 代码:
```
const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/public-data/';
xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();
```
客户端和服务器之间使用 CORS 首部字段来处理权限:
以下是浏览器发送给服务器的请求报文:
```
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
```
请求首部字段 [`Origin`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Origin) 表明该请求来源于 `http://foo.example`。
```
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[XML Data]
```
本例中,服务端返回的 `Access-Control-Allow-Origin: *` 表明,该资源可以被 **任意** 外域访问。
```
Access-Control-Allow-Origin: *
```
使用 [`Origin`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Origin) 和 [`Access-Control-Allow-Origin`](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) 就能完成最简单的访问控制。如果服务端仅允许来自 `https://foo.example` 的访问,该首部字段的内容如下:
```
Access-Control-Allow-Origin: https://foo.example
```
## 简介
出于安全考虑,浏览器禁止AJAX调用驻留在当前来源之外的资源。 例如,当您在一个标签中检查您的银行帐户时,您可以在另一个标签中打开evil.com网站。 evil.com的脚本不能使用您的凭据向您的银行API发出AJAX请求(例如,从您的帐户中提款)!
[Cross-origin resource sharing](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)(CORS) 是 大多数浏览器实现的[W3C](https://www.w3.org/TR/cors/)规范,允许您以灵活的方式指定什么样的跨域请求被授权,而不是使用一些较不安全和不太强大的黑客工具(如IFRAME或JSONP)。
从Spring Framework 4.2开始,CORS支持开箱即用。. CORS 请求([including preflight ones with an`OPTIONS`method](https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java#L906)) 被自动调度到各种已注册的HandlerMappings. 由于CorsProcessor的实现(默认为DefaultCorsProcessor),为了根据您提供的CORS配置添加相关的CORS响应头(如Access-Control-Allow-Origin),它们处理CORS预检请求并拦截CORS简单实际的请求。
## Controller 方法CORS 配置
你如果想使用CORS ,可以在`@RequestMapping上增加`[`@CrossOrigin`](http://docs.spring.io/spring-framework/docs/5.0.0.M5/javadoc-api/org/springframework/web/bind/annotation/CrossOrigin.html)注解. 默认情况下@CrossOrigin允许@RequestMapping注释中指定的所有起始点和HTTP方法:
```java
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@RequestMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
```
对于整个 controller的CORS 的支持:
```java
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@RequestMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
```
在上面的例子中,`retrieve()` 和 `remove()对实现了对CORS 的支持`,因此你可以使用`@CrossOrigin` 属性对自已的程序进行自定义的CORS 配置。
您甚至可以同时使用控制器级和方法级的CORS配置; 然后,Spring将组合两个注释的属性以创建合并的CORS配置。
```java
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("http://domain2.com")
@RequestMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
```
## 全局CORS 配置
除了细粒度,基于注释的配置,您也可能想要定义一些全局CORS配置。 这与使用过滤器类似,但可以在Spring MVC中声明,并结合细粒度的@CrossOrigin配置。 默认情况下,所有起始点和GET,HEAD和POST方法都是允许的。
### JavaConfig
配置全局CORS 如下所示:
```java
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}
```
你也可以轻易的改变任意属性,以及仅将此CORS配置应用于特定路径模式:
```java
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(false).maxAge(3600);
}
}
```
不过,在Spring Framework5中上述方法实现已进过时。来看看新的方法中是如何实现的?
这是过时的
![image-20220503212708360](C:\Users\Zhoujiankang\AppData\Roaming\Typora\typora-user-images\image-20220503212708360.png)
这是新的实现。
```java
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author : zhoujiankang
* @Desc:
* @since : 2022/5/3 20:45
*/
public class CorsConfig implements WebMvcConfigurer
{
@Override
public void addCorsMappings(CorsRegistry registry)
{
// 路径匹配
registry.addMapping("/api/**")
//允许请求源
.allowedOrigins("https://zhoujk.top")
//允许请求方法类型
.allowedMethods("GET","POST", "DELETE")
//允许请求头
.allowedHeaders("header1", "header2", "header3")
// 暴露头
.exposedHeaders("header1", "header2")
// 允许凭据
// false :不允许发送Cookie maxAge 预检请求的有效期 最长30分钟
.allowCredentials(false).maxAge(3600);
}
}
```
### XML 命名空间
以下最小的XML配置为/ **路径模式启用CORS,具有与上述`JavaConfig`示例相同的默认属性:
```xml
<mvc:cors>
<mvc:mapping path="/**" />
</mvc:cors>
```
也可以使用自定义属性声明几个CORS映射:
```xml
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="http://domain1.com, http://domain2.com"
allowed-methods="GET, PUT"
allowed-headers="header1, header2, header3"
exposed-headers="header1, header2" allow-credentials="false"
max-age="123" />
<mvc:mapping path="/resources/**"
allowed-origins="http://domain1.com" />
</mvc:cors>
```
## 高级自定义
[CorsConfiguration](http://docs.spring.io/spring-framework/docs/5.0.0.M5/javadoc-api/org/springframework/web/cors/CorsConfiguration.html) 允许你定义CORS 请求怎样被处理: 允许origins, headers, methods, 如.:他可以使用以下几种方式处理:
- [`AbstractHandlerMapping#setCorsConfiguration()`](http://docs.spring.io/spring-framework/docs/5.0.0.M5/javadoc-api/org/springframework/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfiguration-java.util.Map-)允许指定映射到路径模式(如/ api / **)的几个 [CorsConfiguration](http://docs.spring.io/spring-framework/docs/5.0.0.M5/javadoc-api/org/springframework/web/cors/CorsConfiguration.html)实例的Map
- 子类通过重载`AbstractHandlerMapping#getCorsConfiguration(Object, HttpServletRequest)`方法提供自已的`CorsConfiguration`.
- Handlers 通过实现 [`CorsConfigurationSource`](http://docs.spring.io/spring-framework/docs/5.0.0.M5/javadoc-api/org/springframework/web/cors/CorsConfigurationSource.html)接口(像[`ResourceHttpRequestHandler`](https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java)现在做的一样) 对每个请求提供一个[CorsConfiguration](http://docs.spring.io/spring-framework/docs/5.0.0.M5/javadoc-api/org/springframework/web/cors/CorsConfiguration.html) 实例.
## 基于 CORS 支持的过滤
为了支持基于过滤器的安全框架(如[Spring Security](http://projects.spring.io/spring-security/))的CORS,或者与其他不支持本地CORS的库一起支持CORS,Spring Framework还提供了一个 [`CorsFilter`](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/filter/CorsFilter.html). 而不是使用@CrossOrigin或WebMvcConfigurer#addCorsMappings(CorsRegistry),您需要注册一个自定义过滤器,如下所示:
```java
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
public class MyCorsFilter extends CorsFilter {
public MyCorsFilter() {
super(configurationSource());
}
private static UrlBasedCorsConfigurationSource configurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("http://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}
```
您需要确保`CorsFilter`在其他过滤器之前过滤, 请参考[this blog post](https://spring.io/blog/2015/06/08/cors-support-in-spring-framework#filter-based-cors-support) ,了解如何配置Spring Boot。
# 附录1
## 一、常用的http请求头
### **1.Accept**
- **Accept: text/html** 浏览器可以接受服务器回发的类型为 text/html。
- **Accept: \*/\*** 代表浏览器可以处理所有类型,(一般浏览器发给服务器都是发这个)。
### 2.Accept-Encoding
- **Accept-Encoding: gzip, deflate** 浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate),(注意:这不是只字符编码)。
### 3.Accept-Language
- **Accept-Language:zh-CN,zh;q=0.9** 浏览器申明自己接收的语言。
### 4.Connection
- **Connection: keep-alive** 当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。
- **Connection: close** 代表一个Request完成后,客户端和服务器之间用于传输HTTP数据的TCP连接会关闭, 当客户端再次发送Request,需要重新建立TCP连接。
### 5.Host(发送请求时,该报头域是必需的)
- **Host:www.baidu.com** 请求报头域主要用于指定被请求资源的Internet主机和端口号,它通常从HTTP URL中提取出来的。
### 6.Referer
- **Referer:https://www.baidu.com/?tn=62095104_8_oem_dg** 当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器我是从哪个页面链接过来的,服务器籍此可以获得一些信息用于处理。
### 7.User-Agent
- **User-Agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36** 告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本。
### 8.Cache-Control
- **Cache-Control:private** 默认为private 响应只能够作为私有的缓存,不能再用户间共享
- **`\**Cache-Control:public\** `**响应会被缓存,并且在多用户间共享。正常情况, 如果要求HTTP认证,响应会自动设置为 private.
- **Cache-Control:must-revalidate** 响应在特定条件下会被重用,以满足接下来的请求,但是它必须到服务器端去验证它是不是仍然是最新的。
- **Cache-Control:no-cache** 响应不会被缓存,而是实时向服务器端请求资源。
- **Cache-Control:max-age=10** 设置缓存最大的有效时间,但是这个参数定义的是时间大小(比如:60)而不是确定的时间点。单位是[秒 seconds]。
- **`Cache-Control:no-store `**在任何条件下,响应都不会被缓存,并且不会被写入到客户端的磁盘里,这也是基于安全考虑的某些敏感的响应才会使用这个。
### 9.Cookie
Cookie是用来存储一些用户信息以便让服务器辨别用户身份的(大多数需要登录的网站上面会比较常见),比如cookie会存储一些用户的用户名和密码,当用户登录后就会在客户端产生一个cookie来存储相关信息,这样浏览器通过读取cookie的信息去服务器上验证并通过后会判定你是合法用户,从而允许查看相应网页。当然cookie里面的数据不仅仅是上述范围,还有很多信息可以存储是cookie里面,比如sessionid等。
### 10.Range(用于断点续传)
- **Range:bytes=0-5** 指定第一个字节的位置和最后一个字节的位置。用于告诉服务器自己想取对象的哪部分。
## 二、常用的http响应头
### 1.Cache-Control(对应请求中的Cache-Control)
- **Cache-Control:private** 默认为private 响应只能够作为私有的缓存,不能再用户间共享
- **Cache-Control:public** 浏览器和缓存服务器都可以缓存页面信息。
- **Cache-Control:must-revalidate** 对于客户机的每次请求,代理服务器必须想服务器验证缓存是否过时。
- **Cache-Control:no-cache** 浏览器和缓存服务器都不应该缓存页面信息。
- **Cache-Control:max-age=10** 是通知浏览器10秒之内不要烦我,自己从缓冲区中刷新。
- **Cache-Control:no-store** 请求和响应的信息都不应该被存储在对方的磁盘系统中。
### 2.Content-Type
- **Content-Type:text/html;charset=UTF-8** 告诉客户端,资源文件的类型,还有字符编码,客户端通过utf-8对资源进行解码,然后对资源进行html解析。通常我们会看到有些网站是乱码的,往往就是服务器端没有返回正确的编码。
### 3.Content-Encoding
- **Content-Encoding:gzip** 告诉客户端,服务端发送的资源是采用gzip编码的,客户端看到这个信息后,应该采用gzip对资源进行解码。
### 4.Date
- **Date: Tue, 03 Apr 2018 03:52:28 GMT** 这个是服务端发送资源时的服务器时间,GMT是格林尼治所在地的标准时间。http协议中发送的时间都是GMT的,这主要是解决在互联网上,不同时区在相互请求资源的时候,时间混乱问题。
### 5.Server
- **Server:Tengine/1.4.6** 这个是服务器和相对应的版本,只是告诉客户端服务器信息**。**
### 6.Transfer-Encoding
- **Transfer-Encoding:chunked** 这个响应头告诉客户端,服务器发送的资源的方式是分块发送的。一般分块发送的资源都是服务器动态生成的,在发送时还不知道发送资源的大小,所以采用分块发送,每一块都是独立的,独立的块都能标示自己的长度,最后一块是0长度的,当客户端读到这个0长度的块时,就可以确定资源已经传输完了。
### 7.Expires
- **Expires:Sun, 1 Jan 2000 01:00:00 GMT** 这个响应头也是跟缓存有关的,告诉客户端在这个时间前,可以直接访问缓存副本,很显然这个值会存在问题,因为客户端和服务器的时间不一定会都是相同的,如果时间不同就会导致问题。所以这个响应头是没有Cache-Control:max-age=*这个响应头准确的,因为max-age=date中的date是个相对时间,不仅更好理解,也更准确。
### 8.Last-Modified
- **Last-Modified: Dec, 26 Dec 2015 17:30:00 GMT** 所请求的对象的最后修改日期(按照 RFC 7231 中定义的“超文本传输协议日期”格式来表示)
### 9.Connection
- **Connection:keep-alive** 这个字段作为回应客户端的Connection:keep-alive,告诉客户端服务器的tcp连接也是一个长连接,客户端可以继续使用这个tcp连接发送http请求。
### **10.Etag**
- **ETag: "737060cd8c284d8af7ad3082f209582d"** 就是一个对象(比如URL)的标志值,就一个对象而言,比如一个html文件,如果被修改了,其Etag也会别修改,所以,ETag的作用跟Last-Modified的作用差不多,主要供WEB服务器判断一个对象是否改变了。比如前一次请求某个html文件时,获得了其 ETag,当这次又请求这个文件时,浏览器就会把先前获得ETag值发送给WEB服务器,然后WEB服务器会把这个ETag跟该文件的当前ETag进行对比,然后就知道这个文件有没有改变了。
### 11.Refresh
- **Refresh: 5; url=http://baidu.com** 用于重定向,或者当一个新的资源被创建时。默认会在5秒后刷新重定向。
### 12.Access-Control-Allow-Origin
- **Access-Control-Allow-Origin: \*** *号代表所有网站可以跨域资源共享,如果当前字段为*那么Access-Control-Allow-Credentials就不能为true
- **Access-Control-Allow-Origin: www.baidu.com** 指定哪些网站可以跨域资源共享
### 13.Access-Control-Allow-Methods
- **Access-Control-Allow-Methods:GET,POST,PUT,DELETE** 允许哪些方法来访问
### 14.Access-Control-Allow-Credentials
- **Access-Control-Allow-Credentials: true** 是否允许发送cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。如果access-control-allow-origin为*,当前字段就不能为true
### **15.Content-Range**
- **Content-Range: bytes 0-5/7877** 指定整个实体中的一部分的插入位置,他也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。
# 附录2
CORS 请求失败会产生错误,但是为了安全,在 JavaScript 代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。
Spring中CORS 支持