CORS简介以及在SpringBoot应用下开启CORS

同源策略(Same-origin Policy)

  • 协议相同
  • 域名相同
  • 端口相同

同源包括 Cookie,iframeAjax 同源, 但是html的 <script/>, <link/>, <img/> 标签 和 用ajax提交的form不受同源策略影响.

浏览器为何要遵循同源策略?

这是为了安全考虑. 简单的讲, 即使浏览器遵循了同源策略, 还可能会导致CSRF攻击, 如果不遵循同源, 那浏览器就无安全可言了.

如果服务端不支持CORS, 跨域请求会失败(浏览器访问地址是localhost, 端口号不同, 因此是跨域)

CORS

跨来源资源共享Cross-origin resource sharing (CORS)是现代浏览器的跨域解决方案
请求/响应头中添加Access-Control-*header来控制跨域, 根据请求的方法, 参数和Header, 会分为简单请求和非简单请求

  • 简单请求会直接向后端服务发起请求, 请求内容其实是返回到浏览器端的, 如果不符合跨域规范, 会被浏览器的xhr拦截掉
  • 非简单请求会先发起OPTIONS请求, 如果有跨域访问权限, 则再发起真实的请求(并且在超时时间内不用再次发起OPTIONS)请求

一张来自wiki的流程图如下:

CORS历史

  • 2006年5月 W3C草案版本提交
  • 2009年3月 草案版本重命名为”Cross-Origin Resource Sharing”
  • 2014年2月 CORS作为W3C的推荐标准

CORS之简单请求

同时满足以下两个条件的请求为简单请求

  1. 请求方法为HEAD, GET, POST
  2. HTTP请求头只能为以下几个字段
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值application/x-www-form-urlencoded,multipart/form-data,text/plain

简单请求发起

如果非同源, xhr会自动在请求头上带上Origin, 值为(协议 + 域名 + 端口, http://abc.com)

简单请求返回头

  • Access-Control-Allow-Origin
    必填, 应该是请求头的Origin的值, 或者为* 表示所有

  • Access-Control-Allow-Credentials
    可选, 或者填false(springboot中填false后去掉该响应头), 表示服务器明确许可Cookie可以包含在请求头中. 如果没有该响应头, 浏览器同样可以发送cookie到服务端.
    主要在浏览器端, 需要把xhr的withCredentials设置为true. 发送的cookie与请求的域名依然符合同源策略 (即发送的cookie不是当前页面地址对应域名的cookie, 而是发起跨域请求的域名的cookie)

  • Access-Control-Expose-Headers
    可选, 默认情况下, 客户端xhr的获取响应头方法只能得到如下六个字段的值Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma. 如果需要获得更多, 需要在此header里面指定.

简单请求无论是否满足Access-Control-Allow-Origin, 请求均已经发送到服务端, 并且正常返回. 只是在浏览器的xhr中被中断了. 浏览器访问不到返回值.

  • 一个正常的CORS跨域请求:

非简单请求

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP OPTION查询请求,称为”预检”请求(preflight)。
Access-Control-Max-Age 时间内, 预检请求不会再发起第二次

  • 一个正常的非简单CORS跨域请求:

CORS 与 JSONP

在新的应用中, 推荐使用CORS来替代JSONP
原因如下:

  • JSONP仅支持GET, CORS支持其他HTTP方法
  • JSONP不是使用标准的XMLHttpRequest方式与平时的ajax写法不相同, 而CORS是标准的, 写法相同
  • JSONP在早期浏览器不支持CORS的时候用来实现跨域, CORS是现代浏览器浏览器(市面上主流浏览器, IE不能低于IE10)的跨域解决方案
  • JSONP可能会导致XSS攻击

SpringBoot下开启CORS支持

可通过WebMvcConfigurerAdapter配置全局的cors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootApplication
public class Application {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/users/**");
}
};
}

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

在controller也可通过@CrossOrigin注解配置单独的CORS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RestController
public class MainController {
@CrossOrigin
@GetMapping("book/withCors")
public String withCors(HttpServletResponse response) {
Cookie cookie = new Cookie("test", "11111");
cookie.setPath("/");
response.addCookie(cookie);
return "ok";
}

@GetMapping("book/noCors")
public String noCors() {
System.out.println("----------------");
return "ok";
}

@GetMapping("users")
public Object getAllUser() {
return new String[]{
"shangs", "wangw"
};
}

@GetMapping("users/info")
public Object getInfo(@RequestHeader Map<String, String> headers) {
return headers;
}
}

测试用index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<script type="text/javascript">
const urls = [
'http://localhost:8080/book/withCors',
'http://localhost:8080/book/noCors',
'http://localhost:8080/users',
]

async function getRequest(urls) {
for (let url of urls) {
try {
let response = await axios.get(url, {
withCredentials: true
});
console.log(`success, url: ${url}, result: ${JSON.stringify(response.data)}`);
} catch (e) {
console.log(`error, url: ${url}`);
}
}
}

async function complexRequest() {
let response = await axios.get("http://localhost:8080/users/info", {
withCredentials: true,
headers: {
custom1: "aaa"
}
});
console.log(response.data);
}

// 简单的CORS, 请求是已经到达服务端, 并且返回到浏览器的. 只不过从浏览器拿不到数据.
getRequest(urls);

// 非简单请求
complexRequest();
</script>
</body>
</html>

参考

阮一峰, CORS讲解
wiki CORS
阮一峰, 同源策略
CSRF跨域
XSS攻击

0%