浏览器跨域-CORS

时间:2022-03-12 17:08:33

CORS原理分析

一、简介

  1. CORS全称为”跨域资源共享”(Cross-origin resource sharing)
    它允许浏览器跨服务器发起XMLHttpRequest 请求,从而解决Ajax只能同源使用的限制。

  2. CORS需要浏览器和服务器同时支持。目前大部分浏览器都支持该功能,但IE浏览器不能低于IE10。

  3. 只要浏览器支持CORS,主要重任就落到了服务端,需要服务端根据需求配置CORS响应头信息

  4. 整个CORS通信过程,都由浏览器自动完成,不需要用户参与。浏览器一旦发现Ajax请求跨域,就会自动添加一些附加的请求头信息(比如origin:xxx等),但用户不会察觉。

    Accept:*/*
    Accept-Encoding:gzip, deflate
    Accept-Language:zh-CN,zh;q=0.8
    Connection:keep-alive
    Content-Length:0
    Host:172.0.2.218:3000 //Ajax请求端口3000
    Origin:http://172.0.2.218:4000 //源前端口4000
    Referer:http://172.0.2.218:4000/
    User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
  5. 跨域请求开始后,服务端收到请求后,如果同意跨域访问,必须进行配置。比如必要字段Access-Control-Allow-Origin。服务端配置响应头信息:

    //服务端配置
    res.setHeader("Access-Control-Allow-Origin", "*"); //`*`表示接受任意域名的请求


    //配置后的请求响应头信息
    Access-Control-Allow-Origin:*
    Connection:keep-alive
    Content-Length:274
    Content-Type:application/json; charset=utf-8
    Date:Tue, 07 Nov 2017 06:15:56 GMT
    ETag:W/"112-bBbMBo5MbTrlOpNo1daLKcUeKqs"
    X-Powered-By:Express

    后,浏览器解析到服务端响应头信息Access-Control-Allow-Origin 字段满足要求,就继续正常请求;如果不满足,浏览器则抛出错误

    Failed to load http://172.0.2.218:3000/client/api/000003: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://172.0.2.218:4000' is therefore not allowed access.

二、简单请求和非简单请求

  1. 对于简单请求和非简单请求浏览器没有等同视之,而有不同的处理规则。

  2. 对于简单请求:浏览器直接发出CORS请求。只不过在请求的头信息中增加了一个origin 字段(表示源服务器)。服务端只需要根据业务需求在响应头信息中配置Access-Control-Allow-Origin字段即可。

  3. 对于非简单请求:浏览器首先发送预检请求 去询问服务器的跨域策略,如果配置符合,浏览器再继续发送CORS请求。如果不符合,浏览器报错误:禁止跨域访问

    Request URL:http://172.0.2.218:3000/client/api/000003
    Request Method:OPTIONS //预检请求方法
    Status Code:200 OK
    Remote Address:172.0.2.218:3000
    Referrer Policy:no-referrer-when-downgrade
  4. 此时服务器就需要在预检请求 中去配置响应头信息

  5. 什么是预检请求:预检请求 就是一个请求方式为OPTIONS 的请求。只需要再服务端增加 预检请求路由 即可。

  6. 一旦预检请求通过,以后的每次CORS请求,就类似于简单请求,只需要服务器每次响应一个头信息即可。

三、什么是简单请求和非简单请求?

  1. 只要满足以下两个条件,就属于简单请求:

    • 请求方法是以下三种方法之一
      • HEAD
      • GET
      • POST
    • HTTP的头信息不超出以下几种字段:
      • Accept
      • Accept-Language
      • Content-Language
      • Last-Event-ID
      • Conent-Type: 仅限于 application/x-www-form-urlencodedmultipart/form-datatext/plain
  2. 只要不满足以上两则条件都是非简单请求

四、服务器配置字段

  1. 必要字段Access-Control-Allow-Origin
    该字段的值可以是指定的域名或者指定的一组域名或者是*。浏览器根据该字段的值进行判断是否可以跨域。如果是* 表示服务端接受任意域名请求;如果是一个域名:表示服务器只接受指定的唯一域名;如果是一组域名:表示服务器接受指定的部分域名。

  2. 可选字段Access-Control-Allow-Credentials
    该值是一个布尔值,表示是否允许发送cookie。

  3. 可选字段Access-Control-Allow-Headers
    该字段表示服务器支持的所有头信息字段,如果跨域请求头中得请求字段不在此服务器支持范围,则浏览器禁止同源访问。

  4. 可选字段Access-Control-Max-Age
    用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

实战解析

一、服务器环境node、利用同一ip不同端口来模拟跨域请求(源端口为:4000,资源端口为:3000)。非简单请求利用Ajax发送常见的Content-Type=application/json 实现

二、 源服务器文件:start.js 代码如下:

 "use strict"

var http = require("http")
var fs = require("fs")
var path = require("path")

var root = path.resolve(process.argv[2] || ".")

var server = http.createServer(function(req, res) {
var filePath = path.join(root, "cors.html")
fs.stat(filePath, function(err, stats) {
if(!err && stats.isFile()) {
res.writeHead(200, {"Content-Type": "text/html"});
fs.createReadStream(filePath).pipe(res)
}else {
console.log(err)
}
})
})

server.listen(4000)
console.log("server is running at http://172.0.2.218:4000/")

三、源服务器html文件cors.html(包含Ajax请求脚本)。代码如下:

<!DOCTYPE>
<html>
<head>
<meta charset="UTF-8">
<title>跨域资源共享</title>
</head>
<body>
<button onclick="simpleRequest()">Ajax简单请求</button>
<button onclick="noSimpleRequest()">Ajax非简单请求</button>
<h5>请求结果为:</h5>
<p></p>
</body>

<script>
function simpleRequest() {//Ajax简单请求
var pDoc = document.getElementsByTagName("p")[0]
pDoc.innerHTML = ""
var xmlHttp
if(window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest()
}else {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP")
}
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
pDoc.innerHTML = xmlHttp.responseText
}
}
xmlHttp.open("POST", "http://172.0.2.218:3000/client/api/000003", true)
xmlHttp.send()
}//172.0.2.218外网地址改为localhost

function noSimpleRequest() {//Ajax非简单请求
var pDoc = document.getElementsByTagName("p")[0]
pDoc.innerHTML = ""
var xmlHttp
if(window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest()
}else {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP")
}
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
pDoc.innerHTML = xmlHttp.responseText
}
}
xmlHttp.open("POST", "http://172.0.2.218:3000/client/api/000003", true)
xmlHttp.setRequestHeader("Content-Type","application/json")
xmlHttp.send()
}
</script>
</html>

四、目的服务器文件名:app.js 部分代码如下:

/**
* 临时接口
* 处理cors跨域
*/

//配置预检请求方式1
/*
app.all("*", function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
next();
})
app.post("/client/api/000003", function(req, res) {
var jsonData = {
code: "0000",
msg: "success",
result: [
{
notice_id: "18",
notice_title: "清算问题",
publish_date: "2017-10-30",
publish_time: "10:34:42",
type: "2"
},
{
notice_id: "12",
notice_title: "A股交易规则",
publish_date: "2017-10-19",
publish_time: "09:33:22",
type: "2"
}
]
}
res.json(jsonData);
})
*/

//配置预检请求方式2:
//预检请求方法OPTIONS
app.options("/client/api/000003", function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
next();
})
app.post("/client/api/000003", function(req, res) {
var jsonData = {
code: "0000",
msg: "success",
result: [
{
notice_id: "18",
notice_title: "清算问题",
publish_date: "2017-10-30",
publish_time: "10:34:42",
type: "2"
},
{
notice_id: "12",
notice_title: "A股交易规则",
publish_date: "2017-10-19",
publish_time: "09:33:22",
type: "2"
}
]
}
res.setHeader("Access-Control-Allow-Origin", "*");//单独配置跨域响应头信息
res.json(jsonData);
})
/**
* 方式2和方式1的不同
* 方式1: 每次请求发起的时候都必须经过`all("*")`所以每次请求头都配置了跨域信息
* 方式2; option仅仅是接受预检请求,只是表示浏览器前期询问跨域同意。而其他请求的时候还需要单独配置跨域响应头信息
*/

//监听端口
var port = process.env.PORT || 3000
app.listen(port)
console.log(`app is running at ${port}`)

五、打开命令行,进入到origin_server目录,启动源服务器

$ node start.js

六、新开命令行界面,进入到resource_server目录,启动目的服务器

$ node app.js

七、浏览器中输入4000端口,效果如下:
浏览器跨域-CORS

八、点击简单请求和非简单请求:
浏览器跨域-CORS

demo链接:https://github.com/MayerFan/CORS_Demo
参考链接:http://www.ruanyifeng.com/blog/2016/04/cors.html