关于跨域

why?

为什么会有跨域?

我们得先了解下 ==同源策略(SOP, Same Origin Policy)==。

浏览器出于安全方面的考虑,只能访问与包含它的页面位于同一个域中的资源,该策略为通信设置了“相同的协议、相同的域、相同的端口”这一限制。试图访问上述限制之外的资源,都会引发安全错误。这种安全策略可以预防某些恶意行为。

简而言之,

  1. 同协议 Same Protocol
  2. 同域名 Same Hostname
  3. 同端口号 Same Port

Same Protocol && Same Hostname && Same Port


What?

什么是跨域?

==跨域就是采取技术方案突破同源策略的限制,实现不同域之间交互(请求响应)。==


How?

那么如何实现跨域呢?
有以下几种方法。

==方法一==

CORS (Cross-Origin Resource Sharing,跨域源资源共享),是一种ajax跨域请求资源的方式,支持现代浏览器,IE支持10以上,通过XMLHttpRequest实现Ajax通信的一个主要限制就是同源策略。
CORS是W3C的一个工作草案,定义了在必须访问跨境资源时,浏览器和服务器该如何沟通。CORS的基本思想,就时使用自定义的HTTP头部让浏览器和服务器进行沟通,从而决定请求或者响应应该成功还是失败。
实现思路:使用XMLHttpRequest发送请求时,浏览器会给该请求自动添加一个请求头:Origin。服务器经过一系列处理,如果确定请求来源页面属于白名单,则在响应头部加入首部字段:Access-Control-Allow-Origin。浏览器比较请求头部的Origin 和响应头部的 Access-Control-Allow-Origin是否一致,一致的话,浏览器得到响应数据。如果服务器没有设置Access-Control-Allow-Origin 或者这个头部源信息不匹配,浏览器就会驳回请求。

模拟CORS的实现

步骤1.

如何伪装一个网站(在本地)?

1.编辑hosts文件

苹果mac: 直接在git bash上输入命令行操作即可 “sudo vi /etc.hosts” ,或者下载一些图形界面应用软件直接修改。

Windows操作系统:

  1. win键(四个方块的键)+ R = 弹开运行窗口
  2. 复制该文件路径 c:windowssystem32driversetc
  3. 选中hosts文件,右键-属性-安全-选择组或用户名(添加修改保存的权限的对象)- 编辑 - 再次选择组或用户名(添加修改保存的权限的对象 - 勾选权限(选项在此不表)
  4. 打开hosts文件,写入 127.0.0.1 localhost;127.0.0.1 bai.com;127.0.0.1 google.com;可以写入你任何你想模拟的网站,按照这种对应关系格式即可, ip地址+域名。

步骤2.
所需工具
node.js && git bash(模拟服务器),一个简单的html页面里面有个跨域请求的Ajax通信。

<!-- 前端 -->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Google</title><style type="text/css">body>h1 {text-align: center;}h1 {margin: 0 auto;}</style>
</head>
<body><h1>hello world</h1><script type="text/javascript">//CORS的实现var xhr = new XMLHttpRequest();xhr.onload = function(){if( (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {var responseData = xhr.responseText;console.log(responseData)//['NBA Break News','CBA Break News']}}xhr.open('GET', 'http://baidu.com:8080/getNews', true);xhr.send()</script>
</body>
</html>
//nodeJS模拟后端响应CORS的实现
var http = require('http');
var fs = require('fs');
var url = require('url');
var path = require('path');http.createServer(function(req, res){var urlObj = url.parse(req.url, true)switch (urlObj.pathname){case '/getNews':var news = ['NBA Break News','CBA Break News']//CORS的实现res.setHeader('Access-Control-Allow-Origin','http://google.com:8080')/*res.setHeader('Access-Control-Allow-Origin','*')服务器设置公用接口*/res.end(JSON.stringify(news));break;case '/' :if(urlObj.pathname == '/') {urlObj.pathname += 'index.html'}default: var filePath = path.join(__dirname, urlObj.pathname);fs.readFile(filePath,'binary', function(error, fileContent){if(error){console.log('404')res.writeHeader(404, 'not found')res.end('<h1>404,not found</h1>')}else {res.write(fileContent, 'binary')}})}}).listen(8080);

上面代码就是CORS实现的过程。

  1. 在本地修改hosts文件,127.0.0.1 google.com, 页面的url为 http://google.com:8080。
  2. 在title为google的页面上添加一个ajax请求,该请求以get方法会向baiduServer的端口('http://baidu.com:8080/getNews')发送一个请求。
  3. 浏览器会给请求头加上Origin: http://google.com:8080, Request URL: http://baidu.com:8080/getNews。
  4. baiduServer后端,响应头添加首部字段。Access-Control-Allow-Origin: http://google.com:8080。 表明该服务器(baiduServer)接受请求并给予响应。
  5. 浏览器比较请求头部的Origin 和响应头部的 Access-Control-Allow-Origin是否一致,一致的话,浏览器得到响应数据。如果服务器没有设置Access-Control-Allow-Origin: http://google.com:8080 或者这个头部源信息不匹配,浏览器就会驳回请求。

当然服务器也可以设置公用接口, res.setHeader('Access-Control-Allow-Origin','*')

服务器设置公用接口, 任何人都可以使用该服务器这个端口的数据。

==方法二==

JSONP,是JSON with padding的简写(填充式JSON或参数式JSON)。

JSONP的原理,通过动态<script>元素,使用时可以为该元素的src属性添加一个跨域URL, <script>元素的src有能力不受限制地从其他域中,加载资源。
凡是拥有src这个属性的元素都可以跨域,例如<script><img><iframe>。

JSONP和JSON看起来差不多,只不过是被包含在函数调用中的JSON。

JSONP由两个部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的,所以需要对应接口的后端配合才能实现。而数据就是传入回调函数中的JSON数据。

模拟JSONP的实现

HTML

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Google</title><style type="text/css">body {text-align: center;}h1 {margin: 0 auto;}ul, li {list-style: none;}</style></head>
<body><h1>hello world</h1><ul class="news"></ul><!--JSONP的代码实现方法1--><!-- <script type="text/javascript">function getResponseData(jsonData){document.write(jsonData[0] + ', ');document.write(jsonData[1]);//NBA Break News, CBA Break News}</script><script src="http://baidu.com:8080/getNews?newsData=getResponseData">//getResponseData(["NBA Break News","CBA Break News"])</script> --><!--JSONP的代码实现方法2--><script>var script = document.createElement('script');script.setAttribute('src', 'http://baidu.com:8080/getNews?newsData=getResponseData');$('body').appendChild(script);$('body').removeChild(script);function getResponseData(jsonData){var nodeStock = document.createDocumentFragment();for(var i = 0; i < jsonData.length; i++) {var newsNode = document.createElement('li');newsNode.innerText = jsonData[i];nodeStock.appendChild(newsNode)};$('.news').appendChild(nodeStock)// <li>NBA Break News</li>   <li>CBA Break News</li>};function $(selector) {return document.querySelector(selector);};</script></body>
</html>

nodeJS

var http = require('http');
var fs = require('fs');
var path = require('path');
var url = require('url');http.createServer(function(req,res){var urlObj = url.parse(req.url, true);switch(urlObj.pathname) {case '/getNews' :var news = ['NBA Break News','CBA Break News'];res.setHeader('Content-Type','text/javascript; charset=utf-8');if(urlObj.query.newsData){var data = urlObj.query.newsData + '(' + JSON.stringify(news) + ')' ;res.end(data);} else {res.end(JSON.stringify(news))}break;case '/' :if(urlObj.pathname == '/') {urlObj.pathname +=  'index.html'}default:fs.readFile(path.join(__dirname, urlObj.pathname), function(error, data) {if(error) {res.writeHeader(404, 'not found');res.end('<h1>404, Not Found</h1>');} else {res.end(data)}});};
}).listen(8080);

==方法三==

降域,主要应用场景是同一页面下不同源的框架iframe请求

基于iframe实现的跨域,要求两个域都必须属于同一个基础域, 比如 a.xx.com, b.xx.com,都有一个基础域xx.com, 使用同一协议和端口,这样在两个页面中同时添加documet.domain,就可以实现父页面操控子页面(框架)。

关于document.domain, 用来得到当前网页的域名。在浏览器输入URL,wwww.baidu.com。 http://wwww.baidu.com, document.domain 为 "www.baidu.com"。 也可以为document.domain赋值, 不过有限制,就是前面提到的,只能赋值为当前的域名或者基础域名。
范例:

document.domain = "www.baidu.com" //successed 赋值成功, 当前域名。

document.domain = "baidu.com" // successed 赋值成功, 基础域名。

但是下面的赋值会报错(参数无效)。

"VM50:1 Uncaught DOMException: Failed to set the 'domain' property on 'Document': 'a.baidu.com' is not a suffix of 'www.baidu.com'.

at <anonymous>:1:17"。

范例
document.domain = "google.com" // fail, 参数无效

document.domain = "a.baidu.com" // fail, 参数无效

因为google.com 和 a.baidu.com不是当前的域名,也不是当前域名的基础域名。
原因: 浏览器为了防止恶意修改document.domain来实现跨域偷取数据。

-- --
==模拟降域的实现==

错误范例:

hosts 文件设置 win10系统路径为 c:windowssystem32driversetchosts
127.0.0.1 a.com
127.0.0.1 b.com

a.com的一个网页(a.html)里面 利用iframe引入一个b.com里的一个网页(b.html )。在a.html里面可以看到b.html的内容,但不能用Javascript来操作它。
原因: 这两个页面属于不同的域,在操作之前,浏览器会检测两个页面的域是否相等,相等则允许操作,不相等则报错。
这个例子里,不可能把a.html与b.html,利用JS改成同一个域。原因:两个域的基础域名不相等。

在http://a.com:8080/a.html的控制台(console), 输入代码window.frames[0].document.body //VM150:1 Uncaught DOMException: Blocked a frame with origin "http://a.com:8080" from accessing a cross-origin frame.

at <anonymous>:1:18
<!--a.com的一个网页(a.html)里面 利用iframe引入一个b.com里的一个网页(b.html ) -->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>a.com:8080/a.html</title>
</head>
<body><iframe src="http://b.com:8080/b.html" frameborder="1"></iframe>
</body>
</html>
<!-- b.html-->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>b.com:8080/b.html</title>
</head>
<body><h1>this is b.html </h1><input type="text" placeholder="How are you? this is http://b.com:8080/b.html">
</body>
</html>
//nodeJS
var http = require('http');
var fs = require('fs');
var path = require('path');
var url = require('url');http.createServer(function(req,res){var urlObj = url.parse(req.url, true);switch(urlObj.pathname) {case '/getNews' :var news = ['NBA Break News','CBA Break News'];res.setHeader('Content-Type','text/javascript; charset=utf-8');if(urlObj.query.newsData){var data = urlObj.query.newsData + '(' + JSON.stringify(news) + ')' ;res.end(data);} else {res.end(JSON.stringify(news))}break;case '/' :if(urlObj.pathname == '/') {urlObj.pathname +=  'index.html'}default:var filePath = path.join(__dirname, 'static' ,urlObj.pathname);console.log(filePath)fs.readFile(filePath, function(error, data) {if(error) {res.writeHeader(404, 'not found');res.end('<h1>404, Not Found</h1>');} else {res.end(data)}});};
}).listen(8080);

可以把iframe的src改变为"http://a.com:8080/b.html",这样就可以了,是不会有这个问题的,因为域相等。
控制台不会报错,但是这样没完成跨域。可以使用html5中的postMessage来实现,针对基础域不同的框架,这里暂且不表, 在方法四,会用到这种方法。

window.frames[0].document.body

<body>​<h1>​this is b.html ​</h1>​<input type=​"text" placeholder=​"How are you? this is http:​/​/​b.com:​8080/​b.html">​</body>​

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>a.com:8080/a.html</title>
</head>
<body><!-- <iframe src="http://b.com:8080/b.html" frameborder="1"></iframe> --><iframe src="http://a.com:8080/b.html" frameborder="1"></iframe>
</body>
</html>

==正确范例:
降域的实现==

hosts文件设置

基础域名相同

127.0.0.1 a.shawroc.com

127.0.0.1 b.shawroc.com

a.shawroc.com的里面一个网页(a.html)引入b.shawroc.com里的一个网页(b.html),a.shawroc.com还是不能操作b.shawroc.com里面的内容。
原因:document.domain不一样,a.shawroc.com vs b.shawroc.com。
但是两个页面的基础域名是一样的,通过JS,将两个页面的domain改成一样。
在a.html 和 b.html 里都加入
<script type="text/javascript">
document.domian = shawroc.com
</script>

这样在两个页面中同时添加document.domain, 就可以实现父页面操控子页面(框架)。

控制台
window.frames[0].document.body
//console输出

<body><h1>this is http://b.shawroc.com:8080/b.html </h1><input type="text" placeholder="How are you? this is http://b.shawroc.com:8080/b.html"><script>document.domain = 'shawroc.com';</script></body>

代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>a.shawroc.com:8080</title>
</head>
<body><!-- <iframe src="http://b.com:8080/b.html" frameborder="1"></iframe> --><iframe src="http://b.shawroc.com:8080/b.html" frameborder="1" height="400px" width="600px"></iframe><script>document.domain = 'shawroc.com';</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>b.shawroc.com:8080/b.html</title>
</head>
<body><h1>this is http://b.shawroc.com:8080/b.html </h1><input type="text" placeholder="How are you? this is http://b.shawroc.com:8080/b.html"><script>document.domain = 'shawroc.com';</script>
</body>
</html>

==方法四==

html5的postMessage API

html5引入的postMessage()方法,允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

postMessage(data, origin) 方法,接受两个参数。

1.data:要传递的数据,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。

2.origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

范例

模拟postMessage的工作机制

改写hosts文件

127.0.0.1 a.com

127.0.0.1 b.com

<!--a.html--><!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>a.com:8080</title>
</head>
<body><div class="textMessageInACom"><input type="text" placeholder="http://a.com:8080/a.html"></div><iframe src="http://b.com:8080/b.html" frameborder="1" height="400px" width="600px"></iframe><script>$('.textMessageInACom input').addEventListener('input', function(){window.frames[0].postMessage(this.value, 'http://b.com:8080');})//步骤1 a.com:8080/a.html页面下的input发生输入事件时, 向目标窗口发一个MessageEvent事件<iframe src="http://b.com:8080/b.html">, MessageEvent.data可以获得this.value的值。接下来切换到b.html页面window.addEventListener('message', function(messageEvent){$('.textMessageInACom input').value = messageEvent.data})// 步骤4 监听嵌套页面b.com:8080的message事件,把b.com:8080的input.value(message.data)赋值给a.com:8080的input.value, 就可以实现输入内容的同步啦。function $(selector){return document.querySelector(selector);}</script>
</body>
</html>
<!--b.html-->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>b.shawroc.com:8080/b.html</title>
</head>
<body><h1>this is http://b.com:8080/b.html </h1><input id="input" type="text" placeholder="How are you? this is http://b.com:8080/b.html"></body>
<script>window.addEventListener('message', function(e){$('#input').value = e.data;})//步骤2, 在b.com:8080/b.html监听message事件$('#input').addEventListener('input', function() {window.parent.postMessage(this.value, 'http://a.com:8080');})//步骤3,b.com的input发生输入事件时,向嵌套页面的父页面window.parent, a.com:8080 postMessage,然后切回到a.html,function $(selector){return document.querySelector(selector);}</script></html>
//nodeJS  模拟后端
var http = require('http');
var fs = require('fs');
var path = require('path');
var url = require('url');http.createServer(function(req,res){var urlObj = url.parse(req.url, true);switch(urlObj.pathname) {case '/getNews' :var news = ['NBA Break News','CBA Break News'];res.setHeader('Content-Type','text/javascript; charset=utf-8');if(urlObj.query.newsData){var data = urlObj.query.newsData + '(' + JSON.stringify(news) + ')' ;res.end(data);} else {res.end(JSON.stringify(news))}break;case '/' :if(urlObj.pathname == '/') {urlObj.pathname +=  'index.html'}default:var filePath = path.join(__dirname, 'postMessage' ,urlObj.pathname);fs.readFile(filePath, function(error, data) {if(error) {res.writeHeader(404, 'not found');res.end('<h1>404, Not Found</h1>');} else {res.end(data)}});};
}).listen(8080);

解析代码
步骤1, a.com:8080/a.html页面下的input发生输入事件时, 向目标窗口发一个MessageEvent事件<iframe src="http://b.com:8080/b.html">, MessageEvent.data可以获得this.value的值。接下来切换到b.html页面。

步骤2, 在b.com:8080/b.html监听message事件,在这,就可以实现 a.com:8080/a. html的input标签输入什么,嵌入在 a.com:8080/a. html的iframe框架(b.com:8080/b.html)同步父页面(a.com:8080/a. html)的输入内容了。

步骤3,b.com:8080/b.html的input发生输入事件时,向嵌套页面的父页面window.parent(a.com:8080 )postMessage,然后切回到a.html。

步骤4, 监听嵌套页面b.com:8080/b.html的messageEvent事件,把b.com:8080/b.html的input.value(message.data)赋值给a.com:8080的input.value, 实现输入内容的双向同步。

关于跨域以及跨域的实现方式相关推荐

  1. [转]html5: postMessage解决跨域和跨页面通信的问题

    [转]html5: postMessage解决跨域和跨页面通信的问题 参考文章: (1)[转]html5: postMessage解决跨域和跨页面通信的问题 (2)https://www.cnblog ...

  2. python爬虫跨域_AJAX跨域简单讲解【Python版】

    总结自JAVA,这里改成了Python. 什么是AJAX跨域 只要协议.域名.端口有任何一个不同,都被当作是不同的域,不同域之间的请求就是跨域操作.AJAX跨域就是AJAX在A域下对B域发送了请求,一 ...

  3. iframe嵌套页面 跨域_跨域解决方案

    点击上方蓝色"后端开发杂谈"关注我们, 专注于后端日常开发技术分享 上一篇文章跨域资源共享我们讲到了, 在跨域访问的时候出现的问题, 以及基于跨域共享的方案解决跨域的问题, 那么还 ...

  4. 关于跨域,以及跨域的几种方式

    首先我们来想一想 为什么会有跨域这个名词的出现呢? 跨域又是什么呢?为何要跨域? 浏览器的同源策略又是什么?怎么解决? jsonp又是什么? 跨域的原理又是什么呢? 名词解释: 跨域: 浏览器对于ja ...

  5. xmlhttprequest 跨域_跨域资源共享(CORS)安全性

    跨域资源共享(CORS)安全性 背景 提起浏览器的同源策略,大家都很熟悉.不同域的客户端脚本不能读写对方的资源.但是实践中有一些场景需要跨域的读写,所以出现了一些hack的方式来跨域.比如在同域内做一 ...

  6. 跨域会报40几_关于跨域,以及跨域的几种方式

    首先我们来想一想 为什么会有跨域这个名词的出现呢? 跨域又是什么呢?为何要跨域? 浏览器的同源策略又是什么?怎么解决? jsonp又是什么? 跨域的原理又是什么呢? 名词解释: 跨域: 浏览器对于ja ...

  7. cookie跨域_跨域问题的复现与整理

    知乎:Sp4rkW GITHUB:Sp4rkW B站:一只技术君 博客:https://sp4rkw.blog.csdn.net/ 联系邮箱:getf_own@163.com 文章目录 一.同源策略 ...

  8. jquery读取json文件跨域_跨域方法的若干种方式

    一.跨域的解释 那什么是跨域呢,简单地理解就是因为JavaScript同源策略的限制, a.com 域名下的js无法操作 b.com 或是 c.a.com 域名下的对象.当协议.子域名.主域名.端口号 ...

  9. 关于cookie的跨域(一级域)

    第三方cookie和第一方的cookie并不是技术上的区分,而是业务上的区别,我很赞同这句话,因为我觉得第三方和第一方都是一个相对的概念,其实我们操作的都是自己域下的cookie,只是在某种情景下的操 ...

  10. 【论文笔记】SIFA——基于GAN的双向跨模态无监督域适应框架

    本文是论文<Unsupervised Bidirectional Cross-Modality Adaptation via Deeply Synergistic Image and Featu ...

最新文章

  1. html div数据替换,javascript – 将html添加到div而不替换其中的当前内容
  2. html自定义标签提示,用简单的jquery+CSS创建自定义的a标签title提示tooltip_HTML/Xhtml_网页制作...
  3. apache php 升级5.6,升级 apache2.4.41-php5.6.40
  4. 经典面试题 之 子数组之和最大值
  5. MVC之前的那点事儿系列(4):Http Pipeline详细分析(上)
  6. vue 3.0 正式版_Vuejs 3 Release:One Piece. Vuejs 3.0 正式版发布!代号:海贼王
  7. Codeforces Round #700 (Div. 2) D1 D2. Painting the Array 思维
  8. net如何判断浏览器的类别
  9. @查看MySQL版本的方法
  10. 秒杀场景_Sentinel在秒杀场景的应用_05
  11. 《南方都市报》:中国互联网“公共性”正在变形或流失
  12. Spring Cloud微服务下的权限架构调研
  13. 为什么索引可以让查询变快?终于有人说清楚了!
  14. live555 RTSP服务器建立及消息处理流程
  15. 模拟黑洞图像_CSS filter 模拟黑洞照片效果
  16. PC解决电子签名的方法
  17. 便携版的FeedDemon
  18. 【商城秒杀项目】-- 项目总结
  19. 字符串类型变量的相关内置函数详解
  20. 安装paddledetection

热门文章

  1. 推荐几位优秀的程序员和他们的公众号
  2. ngnix有版本要求吗_新版本探秘:比赛/活动等其他系统的新增与优化~
  3. Android环绕地球动画,手机也能带你进入360°全景立体影音世界?---杜比全景声体验全接触...
  4. getdistance mysql_mysql 自定义函数获取两点间距离
  5. java案例代码5--编码的方式--密码
  6. serve注解是加在哪个类_PHP 8新特性之Attributes(注解)
  7. grasshopper for rhino 6下载_漫谈算法设计与脚本语言(grasshopper, python)
  8. 每日一题(开开森森学前端之Object系列)
  9. JavaScript之JSON详解
  10. 如何在mysql下实现事务的提交与回滚