Skip to main content

CORS 跨域服务器设置

CORS 即 Cross-Origin Resource Sharing,跨域资源共享 CORS 分为两种

一:简单的跨域请求,流程如下

网页:当 HTTP 请求同时满足以下两种情况时,浏览器认为是简单跨请求

1),请求的方法是 get,head 或者 post,同时 Content-Type 是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain 中的一个值,或者不设置也可以,一般默认就是 application/x-www-form-urlencoded。

2),请求中没有自定义的 HTTP 头部,如 x-token。(应该是这几种头部 Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type)

浏览器:把客户端脚本所在的域填充到 Origin header 里,向其他域的服务器请求资源。

服务器:根据资源权限配置,在响应头中添加 Access-Control-Allow-Origin Header,返回结果

浏览器:比较服务器返回的 Access-Control-Allow-Origin Header 和请求域的 Origin,如果当前域已经得到授权,则将结果返回给页面。否则浏览器忽略此次响应。

网页:收到返回结果或者浏览器的错误提示。

总结:对于简单的跨域请求,只要服务器设置的 Access-Control-Allow-Origin Header 和请求来源匹配,浏览器就允许跨域。服务器端设置的 Access-Control-Allow-Methods 和 Access-Control-Allow-Headers 对简单跨域没有作用。

二:带预检(Preflighted)的跨域请求,流程如下

网页:当 HTTP 请求出现以下两种情况时之一,浏览器认为是带预检(Preflighted)的跨域请求:

1),请求的方法不是 GET, HEAD 或者 POST 三种,或者是这三种,但是 Content-Type 不是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain 中的一种。

2),请求中有自定义 HTTP 头部。

浏览器:发现请求属于以上两种情况,向服务器发送一个 OPTIONS 预检请求,检测服务器端是否支持真实请求进行跨域资源访问,浏览器会在发送 OPTIONS 请求时会自动添加 Origin Header 、Access-Control-Request-Method Header 和 Access-Control-Request-Headers Header。

服务器:响应 OPTIONS 请求,会在 responseHead 里添加 Access-Control-Allow-Methods head。这其中的 method 的值是服务器给的默认值,可能不同的服务器添加的值不一样。服务器还会添加 Access-Control-Allow-Origin Header 和 Access-Control-Allow-Headers Header。这些取决于服务器对 OPTIONS 请求具体如何做出响应。如果服务器对 OPTIONS 响应不合你的要求,你可以手动在服务器配置 OPTIONS 响应,以应对带预检的跨域请求。在配置服务器 OPTIONS 的响应时,可以添加 Access-Control-Max-Age head 告诉浏览器在一定时间内无需再次发送预检请求,但是如果浏览器禁用缓存则无效。

浏览器:接到 OPTIONS 的响应,比较真实请求的 method 是否属于返回的 Access-Control-Allow-Methods head 的值之一,还有 origin, head 也会进行比较是否匹配。如果通过,浏览器就继续向服务器发送真实请求。 否则就会报预检错误,如下几种:

请求来源不被 options 响应允许:Failed to load...Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin http://127.0.0.1:8080 is therefore not allowed access.

请求方法不被 options 响应允许:Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.

请求中有自定义 header 不被 options 响应允许:Failed to load... Request header field myHeader is not allowed by Access-Control-Allow-Headers in preflight response.

服务器:响应真实请求,在响应头中放入 Access-Control-Allow-Origin Header、Access-Control-Allow-Methods 和 Access-Control-Allow-Headers Header,分别表示允许跨域资源请求的域、请求方法和请求头,并返回数据。(如果服务器对真实请求的响应另外设置有 Access-Control-Allow-Methods,它的值不会生效,个人理解是因为刚刚在服务器响应 OPTIONS 响应时,就已经验证过真实请求的 method 是属于 Access-Control-Allow-Methods head 的值之一)。也可以在响应真实请求时添加 Access-Control-Max-Age head。

浏览器:接受服务器对真实请求的返回结果,返回给网页

网页:收到返回结果或者浏览器的错误提示。

总结:也就是说 Access-Control-Allow-Methods 和 Access-Control-Allow-Headers 只在响应 options 请求时有作用,Access-Control-Allow-Origin 在响应 options 请求和响应真实请求时都是有作用的,两者必须同时包含要跨域的源。

服务器设置 OPTIONS 响应一般要同时满足这些条件,一是跨域,二是有带预检的请求,三是服务器对 OPTIONS 响应默认值不符合要求,如果是不存在跨域情况,就不需要在服务器手动设置 OPTIONS 响应。

image image XMLHttpRequest 支持通过 withCredentials 属性跨域请求携带身份信息(Credential,例如 Cookie 或者 HTTP 认证信息)。浏览器将携带 Cookie Header 的请求发送到服务器端后,如果服务器没有响应 Access-Control-Allow-Credentials Header,那么浏览器会忽略掉这次响应。如果服务器设置 Access-Control-Allow-Credentials 为 true,那么就不能再设置 Access-Control-Allow-Origin 为*,必须用具体的域名。

express 框架跨域设置:

// 以node-express框架为例
const app = require("express")();
app.options("/", (req, res) => {
//express框架有res.set()和res.header()两种方式设置header,没有setHeader方法。
res.set({
"Access-Control-Allow-Origin": "http://127.0.0.1:8080",
"Access-Control-Allow-Methods": "OPTIONS,HEAD,DELETE,GET,PUT,POST",
"Access-Control-Allow-Headers":
"x-requested-with, accept, origin, content-type",
"Access-Control-Max-Age": 10000,
"Access-Control-Allow-Credentials": true,
});
const obj = {
msg: "options请求",
};
res.send(obj);
});

app.post("/", (req, res) => {
console.log("post请求");
res.set({
"Access-Control-Allow-Origin": "http://127.0.0.1:8080",
// 'Access-Control-Allow-Methods': 'POST',//无需设置。因为如果是带预检的跨域请求时,是否是允许的该请求方法取决于options请求响应时的response head里的access-control-allow-methods head.如果是简单的跨域请求,只有Access-Control-Allow-Origin会参与匹配,此设置依然没有作用。
// 'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type,A',//不需设置,原因同上。
});
const obj = {
msg: "post请求",
};
res.send(obj);
});

app.get("/", (req, res, next) => {
console.log("get请求");
res.set({
"Access-Control-Allow-Origin": "http://127.0.0.1:8080",
});
const obj = {
msg: "get请求",
};
res.send(obj);
});

app.put("/", (req, res) => {
res.set({
"Access-Control-Allow-Origin": "http://127.0.0.1:8080",
});
const obj = {
msg: "put请求",
};
res.send(obj);
});
app.listen(3333, function () {
console.log("express start at port 3333");
});

koa 框架跨域设置:

//以node-koa框架为例
const Koa = require("koa");
const app = new Koa();
const _cors = (ctx, next) => {
//指定服务器端允许进行跨域资源访问的来源域。可以用通配符*表示允许任何域的JavaScript访问资源,但是在响应一个携带身份信息(Credential)的HTTP请求时,必需指定具体的域,不能用通配符
ctx.set("Access-Control-Allow-Origin", "http://127.0.0.1:8080");

//指定服务器允许进行跨域资源访问的请求方法列表,一般用在响应预检请求上
ctx.set("Access-Control-Allow-Methods", "OPTIONS,POST,GET,HEAD,DELETE,PUT");

//必需。指定服务器允许进行跨域资源访问的请求头列表,一般用在响应预检请求上
ctx.set(
"Access-Control-Allow-Headers",
"x-requested-with, accept, origin, content-type"
);

//告诉客户端返回数据的MIME的类型,这只是一个标识信息,并不是真正的数据文件的一部分
ctx.set("Content-Type", "application/json;charset=utf-8");

//可选,单位为秒,指定浏览器在本次预检请求的有效期内,无需再发送预检请求进行协商,直接用本次协商结果即可。当请求方法是PUT或DELETE等特殊方法或者Content-Type字段的类型是application/json时,服务器会提前发送一次请求进行验证
ctx.set("Access-Control-Max-Age", 300);

//可选。它的值是一个布尔值,表示是否允许客户端跨域请求时携带身份信息(Cookie或者HTTP认证信息)。默认情况下,Cookie不包括在CORS请求之中。当设置成允许请求携带cookie时,需要保证"Access-Control-Allow-Origin"是服务器有的域名,而不能是"*";如果没有设置这个值,浏览器会忽略此次响应。
ctx.set("Access-Control-Allow-Credentials", true);

//可选。跨域请求时,客户端xhr对象的getResponseHeader()方法只能拿到6个基本字段,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。要获取其他字段时,使用Access-Control-Expose-Headers,xhr.getResponseHeader('myData')可以返回我们所需的值
ctx.set("Access-Control-Expose-Headers", "myData");

next();
};
const main = function (ctx) {
const _method = ctx.request.method;
ctx.response.body = { 请求方式: _method };
};

app.use(_cors);
app.use(main);

app.listen(5000, function () {
console.log("koa start at port 5000");
});
// 前台代码,用jqAjax和axios两种请求方式对比
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<table>
<tr>
<th col="2">Server</th>
</tr>
<tr>
<th><button id="express" class="server" onclick='changeServer("http://localhost:3333",this)'>express_3333</button></th>
<th><button id="koa" class="server" onclick='changeServer("http://localhost:5000",this)'>koa_5000</button></th>
</tr>
</table>

<table>
<tr>
<th col="4">content-Type</th>
</tr>
<td><button id="x-www-form-urlencoded" class="ContentType" onclick='changeContentType("application/x-www-form-urlencoded",this)'>application/x-www-form-urlencoded</button></td>
<td><button class="ContentType" onclick='changeContentType("multipart/form-data",this)'>multipart/form-data</button></td>
<td><button class="ContentType" onclick='changeContentType("text/plain",this)'>text/plain</button></td>
<td><button class="ContentType" onclick='changeContentType("application/json",this)'>application/json</button></td>
</table>

<table>

<tr>
<tr>
<th col="2">Method</th>
</tr>
<td>JQuery</td>
<td>axios</td>
</tr>
<tr>
<td><button onclick='jq_request("GET")'>jq_get</button></td>
<td><button onclick='axios_request("GET")'>axios_get</button></td>
</tr>
<tr>
<td><button onclick='jq_request("HEAD")'>jq_head</button></td>
<td><button onclick='axios_request("HEAD")'>axios_head</button></td>
</tr>
<tr>
<td><button onclick='jq_request("POST")'>jq_post</button></td>
<td><button onclick='axios_request("POST")'>axios_post</button></td>
</tr>
<tr>
<td><button onclick='jq_request("PUT")'>jq_put</button></td>
<td><button onclick='axios_request("PUT")'>axios_put</button></td>
</tr>
<tr>
<td><button onclick='jq_request("DELETE")'>jq_delete</button></td>
<td><button onclick='axios_request("DELETE")'>axios_delete</button></td>
</tr>
<tr>
<td><button onclick='jq_request("OPTIONS")'>jq_options</button></td>
<td><button onclick='axios_request("OPTIONS")'>axios_options</button></td>
</tr>
</table>

</body>
<script src="https://cdn.bootcss.com/jquery/3.3.0/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script>

let url='http://localhost:3333';
let contentType="application/x-www-form-urlencoded";

const changeServer=(a,self)=>{
url=a;
$('.server').css('background','#eee')
$(self).css('background','gray')
}

const changeContentType=(e,self)=>{
contentType=e;
$('.ContentType').css('background','#eee')
$(self).css('background','gray')
}

$('#koa').click();
$('#x-www-form-urlencoded').click();

const jq_request = (method) => {
$.ajax({
url: url,
type: method,
contentType:contentType,
dataType: "json",
success: function (data) {
console.log(data)
},
error: function (err) {
console.log(err)
}
})
}

const axios_request=(method)=>{
axios({
method: method,
url: url,
headers: {
'Content-Type':contentType,
},

responseType: 'json',
}).then(res => {
console.log(res.data)
}).catch(error => {
console.log(error);
});
}
</script>
</html>

参考