什么是跨域资源共享(CORS)?
2023-02-06 10:15:32
## CORS
跨域资源共享(Cross-origin Resource Sharing,CORS)是一个 W3C 标准,允许浏览器向跨域服务器发送请求,CORS 需要浏览器和服务器同时支持,目前主流浏览器(IE10及以上)使用 XMLHttpRequest 对象都可支持该功。
<br/>
CORS 整个通信过程都是浏览器自动完成,浏览器一旦发现 ajax 请求跨源,就会自动在头信息中增加 Origin 字段,用来说明本次请求来自哪个源(`协议 `+ `域名` + `端口`)。因此,实现 CORS 通信的关键是服务器,需要服务器配置 `Access-Control-Allow-Origin` 头信息。当 CORS 请求需要携带 cookie 时,需要服务端配置 Access-Control-Allow-Credentials 头信息,前端也需要设置 withCredentials。
<br/>
浏览器将 CORS 请求分成两类:`简单请求` 和 `非简单请求`。简单请求需要满足以下两大条件:
* 请求方法是以下三种方法之一:HEAD、GET、POST。
* HTTP 的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain。
CORS 简单请求跨域实现流程
![cors.png](https://static.daimaku.net/post/202302/06/50ee7f168601baf47c844267a07b4273.png)
如果是 `非简单请求`,浏览器会先使用 `OPTIONS` 方法发起一个预检请求,预检请求获知服务器是否允许该跨域请求,如果允许才发起第二次真实的请求,如果不允许,则拦截第二次请求。
## 前端
CORS 简单请求跨域代码示例
```
var xhr = new XMLHttpRequest(); // IE8、9 需用 XDomainRequest 兼容
xhr.withCredentials = true; // 前端设置是否带cookie
xhr.open('post', 'http://localhost:8080/api/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=jack&password=123456');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
```
如果前端使用 **axios** 发起 ajax 请求,不需要特别处理,示例如下:
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Demo</title>
<script src="https://cdn.bootcss.com/qs/6.7.0/qs.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
</head>
<body>
<button onclick="test()">测试</button>
<script>
var qs = Qs
function test() {
var data = {
username: 'jack',
password: '123456'
}
axios({
method: 'POST',
url:'http://localhost:8080/api/login',
headers: {'content-type': 'application/x-www-form-urlencoded'},
data: qs.stringify(data),
}).then(res => {
console.log(res);
})
}
</script>
</body>
</html>
```
下面是一个 `非简单` 请求的示例,会先发起 OPTIONS 类型的请求(因为 Content-Type 不符合简单请求的规定)
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Demo</title>
</head>
<body>
<button onclick="test2()">测试2</button>
<script src="https://cdn.bootcss.com/qs/6.7.0/qs.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script>
function test2() {
var data = {
username: 'jack',
password: '123456'
}
axios({
method: 'POST',
url:'http://localhost:8080/api/test',
headers: { 'content-type': 'application/json' },
data: data,
}).then(res => {
console.log(res);
})
}
</script>
</body>
</html>
```
## 后端
Java Spring Boot 项目,在控制器类或方法上添加 `@CrossOrigin` 注解后,我们可以观察到响应的头信息中,增加了 `Access-Control-Allow-Origin` 相关信息
```
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@CrossOrigin
public class TestController {
@PostMapping("/api/login")
public String version(String username, String password) {
System.out.println(username);
System.out.println(password);
return "hello";
}
}
```
发起请求
```
$ curl -i 'http://localhost:8080/api/login' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Origin: localhost' \
--data-raw 'username=jack&password=123456'
```
观察响应数据
```
HTTP/1.1 200
Access-Control-Allow-Origin: *
Content-Type: text/plain;charset=UTF-8
Content-Length: 5
hello
```
也可以用下面的方式配置全局 CORS
```
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.PostConstruct;
@Configuration
public class Cors implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
// .allowedOrigins("*") // 设置允许跨域请求的域名
.allowedOriginPatterns("*") // SpringBoot升级2.4.0+
.allowedMethods("*") // 设置允许的方法 "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"
.allowCredentials(true) // 是否允许 cookies
.maxAge(3600)
.allowedHeaders("*");
}
}
```
## 扩展阅读
[什么是浏览器的同源策略?](https://www.daimaku.net/post/view/23091)