Fork me on GitHub

CORS-预检请求

CORS 的预检请求
CORS 跨域的前后端配置
简单请求和非简单请求

CORS 预检请求

简单请求

条件(同时满足)
请求方法:HEAD,GET,POST
请求头:Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type:只限于 application/x-www-form-urlencoded、multipart/form-data、text/plain

验证过程
客户端发送简单请求,会在请求头增加一个 Origin 头信息,指定该请求发送方的域,提交给服务器用于判断是否接受该跨域请求。
两种情况下服务器会接受该跨域:

  1. Access-Control-Allow-Origin:*
    如果服务器同意该跨域访问,正常返回数据,并在响应头中携带添加Access-Control-Allow-Origin 头信息。
  2. Access-Control-Allow-Origin设置的允许跨域的地址中包含请求的Origin

非简单请求

不满足上面简单请求规则的请求都是非简单请求。比如请求方法是 PUT 或 DELETE,或者 Content-Type 字段的类型是 application/json。
避免多次请求,axios 的 post 封装为简单请求

1
2
3
4
5
6
7
8
9
import QS from "qs";
export function post(url: string, data: any) {
return axios({
method: "post",
url,
data: QS.stringify(data),
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
}

后端通过 app.use(bodyParser())将 req.body 解析为对象

预检请求

当浏览器检测到需要发送一个非简单的跨域请求时,浏览器会拦截该请求,发送一个预检请求到服务器,旨在询问服务器是否同意这个非简单的跨域请求,服务器通过返回指定内容表示是否同意。

  • 成功的预检请求响应
    跨域请求成功3

    • Access-Control-Allow-Credentials:是否携带 cookie
    • Access-Control-Allow-Methods
      该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支 持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。
    • Access-Control-Allow-Headers
      如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Headers 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。
    • Access-Control-Max-Age
      该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是 20 天(1728000 秒),即允许缓存该条回应 1728000 秒(即 20 天),在此期间,不用发出另一条预检请求。

预检通过
一旦服务器通过了”预检”请求,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样,会有一个 Origin 头信息字段。服务器的回应,也都会有一个 Access-Control-Allow-Origin 头信息字段。

完整的复杂请求例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// index.html
let xhr = new XMLHttpRequest()
document.cookie = 'name=xiamen' // cookie不能跨域
xhr.withCredentials = true // 前端设置是否带cookie
xhr.open('PUT', 'http://localhost:4000/getData', true)
xhr.setRequestHeader('name', 'xiamen')
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
console.log(xhr.response)
//得到响应头,后台需设置Access-Control-Expose-Headers
console.log(xhr.getResponseHeader('name'))
}
}
}
xhr.send()

server

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
let express = require('express')
let app = express()
let whitList = ['http://localhost:3000'] //设置白名单
app.use(function(req, res, next) {
let {origin} = req.headers;
if (whitList.includes(origin)) {
// 设置哪个源可以访问我
res.setHeader('Access-Control-Allow-Origin', origin)
// 允许携带哪个头访问我
res.setHeader('Access-Control-Allow-Headers', 'name')
// 允许哪个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// 允许携带cookie
res.setHeader('Access-Control-Allow-Credentials', true)
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// 允许返回的头
res.setHeader('Access-Control-Expose-Headers', 'name')
if (req.method === 'OPTIONS') {
res.end() // OPTIONS请求不做任何处理
}
}
next()
})
app.put('/getData', function(req, res) {
console.log(req.headers)
res.setHeader('name', 'jw') //返回一个响应头,后台需设置
res.end('我不爱你')
})
app.get('/getData', function(req, res) {
console.log(req.headers)
res.end('我不爱你')
})
app.use(express.static(__dirname))
app.listen(4000)

cookie 参数

1
2
3
4
5
path:指定 cookie 影响到的路径
expires: 指定时间格式
maxAge:指定 cookie 什么时候过期
secure:当 secure 值为 true 时,在 HTTPS 中才有效;反之,cookie 在 HTTP 中是有效。
httpOnly:浏览器不允许脚本操作 document.cookie 去更改 cookie。设置为true可以避免被 xss 攻击拿到 cookie
-------------本文结束感谢阅读-------------