跨域问题

跨域问题

跨域

出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。(不一定是浏览器限制了发起跨站请求,也可能是跨站请求可以正常发起,但是返回结果被浏览器拦截了。)

浏览器中的跨域报错

浏览器同源策略

同源定义

如果两个 URL 的 protocol、port (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。(“元组” 是指一组项目构成的整体,双重/三重/四重/五重/等的通用形式)。

例子: 和 http://oasis-f.com/index.html 的源进行对比。

URL 是否同源 原因
http://oasis-f.com/foo/bar.html 同源 只有路径不同
https://oasis-f.com/foo/bar.html 协议不同
http://oasis-f.com:8080/foo/bar.html 端口不同
http://bad-oasis-f.com/foo/bar.html 主机不同

不受同源限制的情况

  • <script src="..."></script> 标签嵌入跨域脚本。语法错误信息只能被同源脚本中捕捉到。
  • <link rel="stylesheet" href="..."> 标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的 HTTP 头部 Content-Type 。不同浏览器有不同的限制: IE, Firefox, Chrome, Safari (跳至CVE-2010-0051)部分 和 Opera。
  • 通过<img>展示的图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG,...
  • 通过<video> <audio> 播放的多媒体资源。
  • 通过 <object><embed> <applet> 嵌入的插件。
  • 通过 @font-face 引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。
  • 通过 <iframe> 载入的任何资源。站点可以使用 X-Frame-Options 消息头来阻止这种形式的跨域交互。

JSONP

利用 script 标签不受同源策略控制的特点来访问不同源下的资源。具体可以查看这篇文章,虽然年代比较早但是讲的很清楚。
说说JSON和JSONP,也许你会豁然开朗,含jQuery用例

不过使用 jsonp 也有一些额外的问题:

  • 只能发起 GET 请求
  • 因为浏览器的限制无法返回捕获到错误码
  • 安全性低

CORS

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。

跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。

CORS请求失败会产生错误,但是为了安全,在JavaScript代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。

简单请求

不会触发 CORS 预检请求,后端程序只需要在返回的响应头中加上 Access-Control-Allow-Origin 字段,并且把该字段的值设置为 跨域请求的来源地址或简单的设置为 * 就可以了。
简单请求需满足下列所述条件

  • 使用下列方法:
    • GET
    • HEAD
    • POST
  • 只设置下列 header:
    • 用户代理自动设置(Connection,User-Agent
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(只允许下列值)
      • text/plain
      • mujltipart/form-data
      • application/x-www-urlencoded
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • 请求中的任意 XMLHttpRequestUpload 对象没有注册任何事件监听器。
  • 请求中没有使用 ReadableStream 对象

Node Express 例子

// 客户端 静态服务运行于 http://192.168.0.119:8000
    function sendRequest(){
        fetch('http://192.168.0.119:3000')
        .then(res=>console.log(res)).catch(e=>console.error(e))
    }
// 服务端
const express = require('express');
const app = express();
const port = 3000;
app.get('/',(req,res) => res.send('cors app'));
app.listen(port,()=> console.log(`cors app listening on port ${port}`));

此时请求跨域
简单请求的跨域报错

设置CORS相关请求头

const express = require('express');
const app = express();
const port = 3000;

// CORS 中间件
const allowCrossDomain = (req,res,next)=>{
    res.header('Access-Control-Allow-Origin','http://192.168.0.119:8000');
    next()
}
app.use(allowCrossDomain)

app.get('/',(req,res) => res.send('cors app'));
app.listen(port,()=> console.log(`cors app listening on port ${port}`));

这时候就可以成功请求到了。
简单的跨域请求成功

但是完整的应用不可能只有 GET 请求呀。需要有 更多的 method,cookie 等

   fetch('http://192.168.0.119:3000',{
            credentials: 'include',  // 发送带凭据的请求
            method: 'POST',
            body:JSON.stringify({foo:'bar'}),
            headers:new Headers({
                'Content-Type':'application/json',
                'Custom-Header':'8888'
            })
        })
        .then(res=>console.log(res)).catch(e=>console.error(e))
...
const allowCrossDomain = (req,res,next)=>{
    res.header('Access-Control-Allow-Origin','http://192.168.0.119:8000');
    res.header('Access-Control-Allow-Methods','POST');
    res.header('Access-Control-Allow-Headers',"Content-Type,Custom-Header");
    res.header('Access-Control-Allow-Credentials','true');
    next()
}
...

这里有个需要注意的地方:

对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“”。
这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“
”,请求将会失败。而将 Access-Control-Allow-Origin 的值设置为 http://foo.example,则请求将成功执行。
另外,响应首部中也携带了 Set-Cookie 字段,尝试对 Cookie 进行修改。如果操作失败,将会抛出异常。

参考链接

posted @ 2020-07-29 19:46  oasis_cuke  阅读(166)  评论(0编辑  收藏  举报