CORS跨域服務(wù)器設(shè)置

CORS即Cross-Origin Resource Sharing,跨域資源共享

CORS分為兩種

一:簡(jiǎn)單的跨域請(qǐng)求,流程如下

網(wǎng)頁(yè):當(dāng)HTTP請(qǐng)求同時(shí)滿足以下兩種情況時(shí),瀏覽器認(rèn)為是簡(jiǎn)單跨請(qǐng)求

1),請(qǐng)求的方法是get,head或者post,同時(shí)Content-Type是application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一個(gè)值,或者不設(shè)置也可以,一般默認(rèn)就是application/x-www-form-urlencoded。

2),請(qǐng)求中沒有自定義的HTTP頭部,如x-token。(應(yīng)該是這幾種頭部 Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type)

瀏覽器:把客戶端腳本所在的域填充到Origin header里,向其他域的服務(wù)器請(qǐng)求資源。

服務(wù)器:根據(jù)資源權(quán)限配置,在響應(yīng)頭中添加Access-Control-Allow-Origin Header,返回結(jié)果

瀏覽器:比較服務(wù)器返回的Access-Control-Allow-Origin Header和請(qǐng)求域的Origin,如果當(dāng)前域已經(jīng)得到授權(quán),則將結(jié)果返回給頁(yè)面。否則瀏覽器忽略此次響應(yīng)。

網(wǎng)頁(yè):收到返回結(jié)果或者瀏覽器的錯(cuò)誤提示。

總結(jié):對(duì)于簡(jiǎn)單的跨域請(qǐng)求,只要服務(wù)器設(shè)置的Access-Control-Allow-Origin Header和請(qǐng)求來源匹配,瀏覽器就允許跨域。服務(wù)器端設(shè)置的Access-Control-Allow-Methods和Access-Control-Allow-Headers對(duì)簡(jiǎn)單跨域沒有作用。

二:帶預(yù)檢(Preflighted)的跨域請(qǐng)求,流程如下

網(wǎng)頁(yè):當(dāng)HTTP請(qǐng)求出現(xiàn)以下兩種情況時(shí)之一,瀏覽器認(rèn)為是帶預(yù)檢(Preflighted)的跨域請(qǐng)求:

1),請(qǐng)求的方法不是 GET, HEAD或者POST三種,或者是這三種,但是Content-Type不是application/x-www-form-urlencoded, multipart/form-data或text/plain中的一種。

2),請(qǐng)求中有自定義HTTP頭部。

瀏覽器:發(fā)現(xiàn)請(qǐng)求屬于以上兩種情況,向服務(wù)器發(fā)送一個(gè)OPTIONS預(yù)檢請(qǐng)求,檢測(cè)服務(wù)器端是否支持真實(shí)請(qǐng)求進(jìn)行跨域資源訪問,瀏覽器會(huì)在發(fā)送OPTIONS請(qǐng)求時(shí)會(huì)自動(dòng)添加Origin Header 、Access-Control-Request-Method Header和Access-Control-Request-Headers Header。

服務(wù)器:響應(yīng)OPTIONS請(qǐng)求,會(huì)在responseHead里添加Access-Control-Allow-Methods head。這其中的method的值是服務(wù)器給的默認(rèn)值,可能不同的服務(wù)器添加的值不一樣。服務(wù)器還會(huì)添加Access-Control-Allow-Origin Header和Access-Control-Allow-Headers Header。這些取決于服務(wù)器對(duì)OPTIONS請(qǐng)求具體如何做出響應(yīng)。如果服務(wù)器對(duì)OPTIONS響應(yīng)不合你的要求,你可以手動(dòng)在服務(wù)器配置OPTIONS響應(yīng),以應(yīng)對(duì)帶預(yù)檢的跨域請(qǐng)求。在配置服務(wù)器OPTIONS的響應(yīng)時(shí),可以添加Access-Control-Max-Age head告訴瀏覽器在一定時(shí)間內(nèi)無需再次發(fā)送預(yù)檢請(qǐng)求,但是如果瀏覽器禁用緩存則無效。

瀏覽器:接到OPTIONS的響應(yīng),比較真實(shí)請(qǐng)求的method是否屬于返回的Access-Control-Allow-Methods head的值之一,還有origin, head也會(huì)進(jìn)行比較是否匹配。如果通過,瀏覽器就繼續(xù)向服務(wù)器發(fā)送真實(shí)請(qǐng)求。 否則就會(huì)報(bào)預(yù)檢錯(cuò)誤,如下幾種:

??請(qǐng)求來源不被options響應(yīng)允許: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.

??請(qǐng)求方法不被options響應(yīng)允許:Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.

??請(qǐng)求中有自定義header不被options響應(yīng)允許:Failed to load... Request header field myHeader is not allowed by Access-Control-Allow-Headers in preflight response.

服務(wù)器:響應(yīng)真實(shí)請(qǐng)求,在響應(yīng)頭中放入Access-Control-Allow-Origin Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers Header,分別表示允許跨域資源請(qǐng)求的域、請(qǐng)求方法和請(qǐng)求頭,并返回?cái)?shù)據(jù)。(如果服務(wù)器對(duì)真實(shí)請(qǐng)求的響應(yīng)另外設(shè)置有Access-Control-Allow-Methods,它的值不會(huì)生效,個(gè)人理解是因?yàn)閯倓傇诜?wù)器響應(yīng)OPTIONS響應(yīng)時(shí),就已經(jīng)驗(yàn)證過真實(shí)請(qǐng)求的method是屬于Access-Control-Allow-Methods head的值之一)。也可以在響應(yīng)真實(shí)請(qǐng)求時(shí)添加Access-Control-Max-Age head。

瀏覽器:接受服務(wù)器對(duì)真實(shí)請(qǐng)求的返回結(jié)果,返回給網(wǎng)頁(yè)

網(wǎng)頁(yè):收到返回結(jié)果或者瀏覽器的錯(cuò)誤提示。

總結(jié):也就是說Access-Control-Allow-Methods和Access-Control-Allow-Headers只在響應(yīng)options請(qǐng)求時(shí)有作用,Access-Control-Allow-Origin在響應(yīng)options請(qǐng)求和響應(yīng)真實(shí)請(qǐng)求時(shí)都是有作用的,兩者必須同時(shí)包含要跨域的源。

服務(wù)器設(shè)置OPTIONS響應(yīng)一般要同時(shí)滿足這些條件,一是跨域,二是有帶預(yù)檢的請(qǐng)求,三是服務(wù)器對(duì)OPTIONS響應(yīng)默認(rèn)值不符合要求,如果是不存在跨域情況,就不需要在服務(wù)器手動(dòng)設(shè)置OPTIONS響應(yīng)。

image
image

XMLHttpRequest支持通過withCredentials屬性跨域請(qǐng)求攜帶身份信息(Credential,例如Cookie或者HTTP認(rèn)證信息)。瀏覽器將攜帶Cookie Header的請(qǐng)求發(fā)送到服務(wù)器端后,如果服務(wù)器沒有響應(yīng)Access-Control-Allow-Credentials Header,那么瀏覽器會(huì)忽略掉這次響應(yīng)。如果服務(wù)器設(shè)置Access-Control-Allow-Credentials為true,那么就不能再設(shè)置Access-Control-Allow-Origin為*,必須用具體的域名。

express框架跨域設(shè)置:

// 以node-express框架為例
const app = require('express')();
app.options('/', (req, res) => {
//express框架有res.set()和res.header()兩種方式設(shè)置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請(qǐng)求"
    }
    res.send(obj)
})
 
app.post('/', (req, res) => {
    console.log('post請(qǐng)求')
    res.set({
        "Access-Control-Allow-Origin": "http://127.0.0.1:8080",
  // 'Access-Control-Allow-Methods': 'POST',//無需設(shè)置。因?yàn)槿绻菐ьA(yù)檢的跨域請(qǐng)求時(shí),是否是允許的該請(qǐng)求方法取決于options請(qǐng)求響應(yīng)時(shí)的response head里的access-control-allow-methods head.如果是簡(jiǎn)單的跨域請(qǐng)求,只有Access-Control-Allow-Origin會(huì)參與匹配,此設(shè)置依然沒有作用。
// 'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type,A',//不需設(shè)置,原因同上。
    });
    const obj = {
        "msg": "post請(qǐng)求"
    }
    res.send(obj)
})
 
app.get('/', (req, res, next) => {
    console.log('get請(qǐng)求')
    res.set({
        "Access-Control-Allow-Origin": "http://127.0.0.1:8080",
    });
    const obj = {
        msg: 'get請(qǐng)求'
    }
    res.send(obj)
})
 
app.put('/', (req, res) => {
    res.set({
        "Access-Control-Allow-Origin": "http://127.0.0.1:8080"
    });
    const obj = {
        "msg": "put請(qǐng)求"
    }
    res.send(obj)
})
app.listen(3333, function () {
    console.log('express start at port 3333')
})

koa框架跨域設(shè)置:

//以node-koa框架為例
const Koa = require('koa');
const app = new Koa();
const _cors=(ctx,next)=>{
    //指定服務(wù)器端允許進(jìn)行跨域資源訪問的來源域。可以用通配符*表示允許任何域的JavaScript訪問資源,但是在響應(yīng)一個(gè)攜帶身份信息(Credential)的HTTP請(qǐng)求時(shí),必需指定具體的域,不能用通配符
    ctx.set("Access-Control-Allow-Origin", "http://127.0.0.1:8080");
 
    //指定服務(wù)器允許進(jìn)行跨域資源訪問的請(qǐng)求方法列表,一般用在響應(yīng)預(yù)檢請(qǐng)求上
    ctx.set("Access-Control-Allow-Methods", "OPTIONS,POST,GET,HEAD,DELETE,PUT");
    
    //必需。指定服務(wù)器允許進(jìn)行跨域資源訪問的請(qǐng)求頭列表,一般用在響應(yīng)預(yù)檢請(qǐng)求上
    ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");
    
    //告訴客戶端返回?cái)?shù)據(jù)的MIME的類型,這只是一個(gè)標(biāo)識(shí)信息,并不是真正的數(shù)據(jù)文件的一部分
    ctx.set("Content-Type", "application/json;charset=utf-8");
    
    //可選,單位為秒,指定瀏覽器在本次預(yù)檢請(qǐng)求的有效期內(nèi),無需再發(fā)送預(yù)檢請(qǐng)求進(jìn)行協(xié)商,直接用本次協(xié)商結(jié)果即可。當(dāng)請(qǐng)求方法是PUT或DELETE等特殊方法或者Content-Type字段的類型是application/json時(shí),服務(wù)器會(huì)提前發(fā)送一次請(qǐng)求進(jìn)行驗(yàn)證
    ctx.set("Access-Control-Max-Age", 300);
 
    //可選。它的值是一個(gè)布爾值,表示是否允許客戶端跨域請(qǐng)求時(shí)攜帶身份信息(Cookie或者HTTP認(rèn)證信息)。默認(rèn)情況下,Cookie不包括在CORS請(qǐng)求之中。當(dāng)設(shè)置成允許請(qǐng)求攜帶cookie時(shí),需要保證"Access-Control-Allow-Origin"是服務(wù)器有的域名,而不能是"*";如果沒有設(shè)置這個(gè)值,瀏覽器會(huì)忽略此次響應(yīng)。
    ctx.set("Access-Control-Allow-Credentials", true);
 
    //可選。跨域請(qǐng)求時(shí),客戶端xhr對(duì)象的getResponseHeader()方法只能拿到6個(gè)基本字段,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。要獲取其他字段時(shí),使用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={"請(qǐng)求方式":_method};
};
 
app.use(_cors)
app.use(main)
 
app.listen(5000, function () {
    console.log('koa start at port 5000')
})
// 前臺(tái)代碼,用jqAjax和axios兩種請(qǐng)求方式對(duì)比
<!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

http://www.lxweimin.com/p/5b3acded5182

歡迎補(bǔ)充,討論。轉(zhuǎn)載或引用請(qǐng)注明出處

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 歡迎關(guān)注微信公眾號(hào):全棧工廠 本文主要參考跨域資源共享 CORS 詳解[http://www.ruanyifeng...
    liqingbiubiu閱讀 1,897評(píng)論 0 3
  • CORS是一個(gè)W3C標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)。 ...
    奇特思維家閱讀 1,140評(píng)論 0 3
  • 什么是跨域 跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器對(duì)JavaScript實(shí)...
    Yaoxue9閱讀 1,325評(píng)論 0 6
  • 引用自HTTP訪問控制(CORS) 當(dāng) Web 資源請(qǐng)求由其它域名或端口提供的資源時(shí),會(huì)發(fā)起跨域 HTTP 請(qǐng)求(...
    有涯逐無涯閱讀 2,613評(píng)論 0 4
  • 有一個(gè)說法叫:斗米恩升米仇。唐代詩(shī)人白居易亦曾說過:行路難,不在水不在山,只在人情反覆間。可見忖度人心,把握好分寸...
    饞嘴貓呀閱讀 925評(píng)論 2 7