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>

参考了以下几篇博客:
https://blog.csdn.net/enter89/article/details/51205752

https://github.com/hstarorg/HstarDoc/blob/master/%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/CORS%E8%AF%A6%E8%A7%A3.md

https://www.cnblogs.com/MrZouJian/p/8568414.html

https://www.jianshu.com/p/5b3acded5182

欢迎补充,讨论。转载或引用请注明出处

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容