资讯专栏INFORMATION COLUMN

跨域,拒绝说概念,上demo

IamDLY / 2005人阅读

摘要:文章列出解决方案以及对应的,拒绝说概念,不在稀里糊涂。服务器据此决定,该实际请求是否被允许。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

文章列出解决方案以及对应的demo, 拒绝说概念,不在稀里糊涂。
什么情况出现跨域?

协议不同

域名不同

端口不同

跨域解决方案 1.同一个主域下不同子域之间的跨域请求 - document.domain+iframe

同一个 origin 下,父页面可以通过 iframe.contentWindow 直接访问 iframe 的全局变量、DOM 树等,iframe 可以也通过 parent/top 对父页面做同样的事情。

domain.html


  
  

domain2.html


  2222222222
  

完整demo

2. 完全不同源 - postMessage

html5新增API, 支持IE8+。

otherWindow.postMessage(message, targetOrigin, [transfer]);

otherWindow 其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。

message 将要发送到其他 window的数据

targetOrigin 通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串""(表示无限制)或者一个URI。如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的targetOrigin,而不是。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。

transfer 可选 是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

传递过来的message的属性有:

data 从其他 window 中传递过来的对象。

origin 调用 postMessage 时消息发送方窗口的 origin . 这个字符串由 协议、“://“、域名、“ : 端口号”拼接而成

source 对发送消息的窗口对象的引用; 您可以使用此来在具有不同origin的两个窗口之间建立双向通信

下面index.html和index2.html通信
index.html


  
  

  

index2.html


  子窗口
  

  

完整demo

3. 完全不同源 - location.hash+iframe

原理是利用location.hash来进行传值。改变hash并不会导致页面刷新,所以可以利用hash值来进行数据传递,当然数据容量是有限的。
例如:假设a.tblog.com:3004 和 192.168.101.5:3004/index2.html通信
原理:a.tblog.com:3004中index.html以iframe将192.168.101.5:3004/index2.html页面引入,在192.168.101.5:3004/index2.html中插入新的iframe, 此iframe引入的页面和a.tblog.com:3004同源,就可将192.168.101.5:3004/index2.html的hash数据传入a.tblog.com:3004页面的hash值中。parent.parent.location.hash = self.location.hash.substring(1);
a.tblog.com:3004/index.html

 

192.168.101.5:3004/ index2.html


  

a.tblog.com:3004/index3.html


  

完整demo

4. window.name + iframe 跨域

window.name 获取/设置窗口的名称。
窗口的名字主要用于为超链接和表单设置目标(targets)。窗口不需要有名称。
window.name属性可设置或者返回存放窗口名称的一个字符串, name值在不同页面或者不同域下加载后依旧存在,没有修改就不会发生变化,并且可以存储非常长的name(2MB)。
场景1 - 同源
a.html

 
    
  

b.html


  

场景2 - 不同源
利用iframe中window.name在不同页面或者不同域下加载后依旧存在的特性。
a.tblog.com:3004/a.html中通过iframe添加192.168.0.103:3004/b.html(数据页面, 指定window.name 的值),监听iframe的load, 改变iframe的src与a.tblog.com:3004/a.html同源代理页面a.tblog.com:3004/c.html(空页面)。
a.tblog.com:3004/a.html

const iframe = document.createElement("iframe");
iframe.style.display = "none";
let state = 0;

iframe.onload = function () {
  console.log("iframe.onload", state, iframe.contentWindow);
  if (state === 1) {
    const data = JSON.parse(iframe.contentWindow.name);
    console.log(data, state);
    iframe.contentWindow.document.write("");
    iframe.contentWindow.close();
    document.body.removeChild(iframe);
  } else if (state === 0) {
    state = 1;
    console.log("数据", window.name)
    iframe.contentWindow.location = "http://a.tblog.com:3004/c.html";
  }
};

iframe.src = "http://192.168.0.103:3004/b.html";
document.body.appendChild(iframe);

完整demo

5. 跨域jsonp

jsonp原理:

首先是利用script标签的src属性来实现跨域。

客户端注册callback方法名,携带在URL上, 如"http://127.0.0.1:8080/getNews?callback=getData"

服务器响应后生成json, 将json放在刚才接收到的callback的函数中,就生成一段getData(json)

客户端浏览器将script 标签插入 DOM,解析script标签后,会执行getData(json)。

由于使用script标签的src属性,因此只支持get方法
客户端代码


  

  

服务端代码

const http = require("http");
const fs = require("fs");
const path = require("path");
const url = require("url");

http.createServer(function(req, res){
  const pathObj = url.parse(req.url, true);
  switch(pathObj.pathname){        
    case "/getNews":            
      const news = [{id: 678}];
      res.setHeader("Content-type", "text/json; charset=utf-8");           
      if(pathObj.query.callback){
          res.end(pathObj.query.callback + "(" + JSON.stringify(news) + ")");
      }else {
          res.end(JSON.stringify(news));
      }            
    break; 
    default:
      res.writeHead(404, "not found");
    }
}).listen(8080);

完整demo

6. CORS跨域

原理
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。跨域资源共享( CORS )机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。
什么情况下需要CORS

前文提到的由 XMLHttpRequest 或 Fetch 发起的跨域 HTTP 请求。

Web 字体 (CSS 中通过 @font-face 使用跨域字体资源), 因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。

WebGL 贴图

使用 drawImage 将 Images/video 画面绘制到 canvas

样式表(使用 CSSOM)

功能概述
跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。允许服务器声明哪些源站通过浏览器有权限访问哪些资源。对于get以外的请求,浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。 真个过程浏览器自动完成,服务器会添加一些附加的头信息, 因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
简单请求
某些请求不会触发 CORS 预检请求。本文称这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。只要同时满足以下两大条件,就属于简单请求:

(1) 请求方法是以下三种方法之一:
  HEAD
  GET
  POST
  
(2)HTTP的头信息不超出以下几种字段:
  Accept
  Accept-Language
  Content-Language
  Last-Event-ID
  Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

请求响应结果多出的字段:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

Access-Control-Allow-Origin

Access-Control-Allow-Origin: | *; 该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求, 有次响应头字段就可以跨域

Access-Control-Allow-Credentials

Access-Control-Allow-Credentials: true; 当浏览器的credentials设置为true时, 此响应头表示是否允许浏览器读取response的内容,返回true则可以,其他值均不可以,Credentials可以是 cookies, authorization headers 或 TLS client certificates。
Access-Control-Allow-Credentials 头 工作中与XMLHttpRequest.withCredentials 或Fetch API中的Request() 构造器中的credentials 选项结合使用。Credentials必须在前后端都被配置(即the Access-Control-Allow-Credentials header 和 XHR 或Fetch request中都要配置)才能使带credentials的CORS请求成功。 如果withCredentials 为false,服务器同意发送Cookie,浏览器也不会发送,或者,服务器要求设置Cookie,浏览器也不会处理。
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

// 允许credentials:  
Access-Control-Allow-Credentials: true

// 使用带credentials的 XHR :
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://example.com/", true); 
xhr.withCredentials = true; 
xhr.send(null);

// 使用带credentials的 Fetch :
fetch(url, {
  credentials: "include"  
})

Access-Control-Expose-Headers

在跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma, 如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader("FooBar")可以返回FooBar字段的值。

代码如下:


http.createServer(function(req, res){
  const pathObj = url.parse(req.url, true);
  switch(pathObj.pathname){        
    case "/user":            
      const news = [{id: 678}];
      res.setHeader("Content-type", "text/json; charset=utf-8");    
      res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
      // res.setHeader("Access-Control-Allow-Origin", "*");

      // 需要cookie等凭证是必须
      res.setHeader("Access-Control-Allow-Credentials", true);

      res.end(JSON.stringify(news));           
    break; 
    default:
      res.writeHead(404, "not found");
    }
}).listen(8080, (err) => {
  if (!err) {
    console.log("8080已启动");
  }
});


  

完整demo

非简单请求
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
当请求满足下述任一条件时,即应首先发送预检请求:

使用了下面任一 HTTP 方法:

put

delete

connect

OPTIONS

trace

patch

人为设置了对cors安全首部字段集合外的其他首部字段, 该集合为:

Accept

Accept-Language

Content-Language

Content-Type

DPR

Downlink

Save-data

Viewport-Width

Width

Content-Type的值不属于下列之一:

application/x-www-form-urlencoded

multipart/form-data

text/plain

请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器。

请求中使用了ReadableStream对象。

如下是一个需要执行预检请求的 HTTP 请求:


  



http.createServer(function(req, res){
  const pathObj = url.parse(req.url, true);
  switch(pathObj.pathname){        
    case "/user":            
      const news = {id: 678};
      res.setHeader("Content-type", "text/json; charset=utf-8");    
      res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
      // res.setHeader("Access-Control-Allow-Origin", "*");

      // 需要cookie等凭证是必须
      res.setHeader("Access-Control-Allow-Credentials", true);

      res.end(JSON.stringify(news));           
    break; 
    default:
      res.writeHead(404, "not found");
    }
}).listen(8080, (err) => {
  if (!err) {
    console.log("8080已启动");
  }
});

浏览器请求结果

cors2.html:1 Access to XMLHttpRequest at "http://localhost:8080/user" from origin "http://127.0.0.1:3004" has been blocked by CORS policy: Request header field x-pingother is not allowed by Access-Control-Allow-Headers in preflight response.


如图所示发起了预检请求,请求头部多了两个字段:

Access-Control-Request-Method: POST;  // 该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法.
Access-Control-Request-Headers: Content-Type, X-PINGOTHER;  告知服务器,实际请求将携带两个自定义请求首部字段:X-PINGOTHER 与 Content-Type。服务器据此决定,该实际请求是否被允许。

上例需要成功响应数据,服务端需要同意

http.createServer(function(req, res){
  const pathObj = url.parse(req.url, true);
  switch(pathObj.pathname){        
    case "/user":            
      const news = {id: 678};
      res.setHeader("Content-type", "text/json; charset=utf-8");    
      res.setHeader("Access-Control-Allow-Origin", req.headers.origin);

      // 新增的
      res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
      res.setHeader("Access-Control-Allow-Headers", "X-PINGOTHER, Content-Type");
      res.setHeader("Access-Control-Max-Age", 86400);

      res.end(JSON.stringify(news));           
    break; 
    default:
      res.writeHead(404, "not found");
    }
}).listen(8080, (err) => {
  if (!err) {
    console.log("8080已启动");
  }
});

服务段新增的字段:

Access-Control-Allow-Origin: req.headers.origin  
Access-Control-Allow-Methods: POST, GET, OPTIONS  // 表明服务器允许客户端使用 POST, GET 和 OPTIONS 方法发起请求。该字段与 HTTP/1.1 Allow: response header 类似,但仅限于在需要访问控制的场景中使用。这是为了避免多次"预检"请求。
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type  // 如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
Access-Control-Max-Age: 86400  // 表明该响应的有效时间为 86400 秒,也就是 24 小时。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。
7. nodejs代理跨域

node中间件实现跨域代理,是通过一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登陆认证。
原理:服务器之间数据请求不存在跨域限制(同源策略是浏览器行为), 所以先将请求代理到代理服务器, 代理服务器在内部请求真实的服务器得到结果后end连接。


http.createServer(function(req, res){
  const pathObj = url.parse(req.url, true);
  console.log("server", pathObj.pathname)  

  switch(pathObj.pathname){        
    case "/user":            
      const news = {id: 678};
      res.end(JSON.stringify(news));           
      break; 
    default:
      res.setHeader("Content-type", "text/json; charset=utf-8");  
      res.end("未知错误");   
    }
}).listen(4000, (err) => {
  if (!err) {
    console.log("4000已启动");
  }
});

http.createServer(function(req, res){
  const pathObj = url.parse(req.url, true);

  switch(pathObj.pathname){        
    case "/user":            
      res.setHeader("Content-type", "text/json; charset=utf-8");    
      res.writeHead(200, {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "POST, GET, OPTIONS",
        "Access-Control-Allow-Headers": "X-PINGOTHER, Content-Type",
      });
      console.log("proxy", req.method, pathObj.pathname);
       // 请求真实服务器
      const proxyRequest = http.request({
          host: "127.0.0.1",
          port: 4000,
          url: "/",
          path: pathObj.pathname,
          method: req.method,
          headers: req.headers
      }, (proxyRes) => {
          let body = "";
          proxyRes.on("data", (chunk) => {
            body += chunk;
          });
          proxyRes.on("end", () => {
              console.log("响应的数据 " + body );
              res.end(body);
          })

      }).end();
         
      break; 
    default:
      res.writeHead(404, "not found");
      res.end(body);
      break; 
    }
}).listen(8080, (err) => {
  if (!err) {
    console.log("8080已启动");
  }
});


  
注意:
服务器和浏览器数据交互也需要遵循同源策略

-- 持续更新 --

Tips:  
代码地址。~ github

WeChat

参考文章  
https://developer.mozilla.org...
http://vinc.top/2017/02/09/%E...
http://www.ruanyifeng.com/blo...
https://segmentfault.com/a/11...
https://developer.mozilla.org...
https://developer.mozilla.org...
http://www.ruanyifeng.com/blo...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103925.html

相关文章

  • 再也不学AJAX了!(三)跨域获取资源 ① - 同源策略

    摘要:浏览器的同源策略浏览器所遵守的同源策略是指限制不同源之间执行特定操作。这正是同源策略想要规避的安全隐患。目前为止,你已经充分了解同源策略这个主题。 我们之前提到过,AJAX技术使开发者能够专注于互联网中数据的传输,而不再拘泥于数据传输的载体。通过AJAX技术,我们获取数据的方式变得更加灵活,可控和优雅。 但是AJAX技术并不是一把万能钥匙,互联网中的数据隐私和数据安全(例如你的银行账号...

    godlong_X 评论0 收藏0
  • [杂谈]了解一些额外知识,让前端开发锦添花

    摘要:对的请求,也是要有一个了解,比如协议,请求方式,请求过程,结果状态码等。教程协议详解经典面试题一个故事讲完响应状态码上面提到响应状态码,在这里也简单写下。 劝了别人无数次,让别人喝了鸡汤,帮别人填坑,自己却掉了坑 1.前言 在前端学习里面,很多人都是注重学习代码(html,css,js)。或者是一些框架,库(jquery,vue,react),或者是各种工具(webpack,gulp)...

    vvpvvp 评论0 收藏0
  • [杂谈]了解一些额外知识,让前端开发锦添花

    摘要:对的请求,也是要有一个了解,比如协议,请求方式,请求过程,结果状态码等。教程协议详解经典面试题一个故事讲完响应状态码上面提到响应状态码,在这里也简单写下。 劝了别人无数次,让别人喝了鸡汤,帮别人填坑,自己却掉了坑 1.前言 在前端学习里面,很多人都是注重学习代码(html,css,js)。或者是一些框架,库(jquery,vue,react),或者是各种工具(webpack,gulp)...

    张率功 评论0 收藏0
  • [杂谈]了解一些额外知识,让前端开发锦添花

    摘要:对的请求,也是要有一个了解,比如协议,请求方式,请求过程,结果状态码等。教程协议详解经典面试题一个故事讲完响应状态码上面提到响应状态码,在这里也简单写下。 劝了别人无数次,让别人喝了鸡汤,帮别人填坑,自己却掉了坑 1.前言 在前端学习里面,很多人都是注重学习代码(html,css,js)。或者是一些框架,库(jquery,vue,react),或者是各种工具(webpack,gulp)...

    zhichangterry 评论0 收藏0
  • 跨域问题的一次深入研究

    摘要:前言最近在业务代码中深受跨域问题困扰,因此特别写一篇博客来记录一下自己对跨域的理解以及使用到的参考资料。内嵌式跨域通常也是允许的。而我使用时因为这个响应报文最后被认为是跨域问题,无法从中获得的状态码。它代表服务器支持跨域时携带认证信息。 前言 最近在业务代码中深受跨域问题困扰,因此特别写一篇博客来记录一下自己对跨域的理解以及使用到的参考资料。本文的项目背景基于vue+vuex+axio...

    X_AirDu 评论0 收藏0

发表评论

0条评论

IamDLY

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<