浏览器跨域-CORS

来源:互联网 发布:西瓜影音播放网络文件 编辑:程序博客网 时间:2024/06/04 19:45

      • CORS原理分析
      • 实战解析

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, deflateAccept-Language:zh-CN,zh;q=0.8Connection:keep-aliveContent-Length:0Host:172.0.2.218:3000                //Ajax请求端口3000Origin:http://172.0.2.218:4000       //源前端口4000Referer: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-aliveContent-Length:274Content-Type:application/json; charset=utf-8Date:Tue, 07 Nov 2017 06:15:56 GMTETag: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/000003Request Method:OPTIONS //预检请求方法Status Code:200 OKRemote Address:172.0.2.218:3000Referrer 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: //预检请求方法OPTIONSapp.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 || 3000app.listen(port)console.log(`app is running at ${port}`)

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

$ node start.js

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

$ node app.js

七、浏览器中输入4000端口,效果如下:
这里写图片描述

八、点击简单请求和非简单请求:
这里写图片描述

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

原创粉丝点击