前端跨域深入理解

p.p1 { margin: 0.0px 0.0px 12.8px 0.0px; line-height: 18.0px; font: 19.2px "PT Sans"; color: #000000 }
p.p2 { margin: 0.0px 0.0px 14.4px 0.0px; line-height: 18.0px; font: 14.4px "PT Sans"; color: #000000 }
p.p3 { margin: 0.0px 0.0px 12.0px 0.0px; line-height: 18.0px; font: 12.0px ".PingFang SC"; color: #000000 }
p.p4 { margin: 0.0px 0.0px 12.0px 0.0px; line-height: 18.0px; font: 12.0px ".PingFang SC Semibold"; color: #000000 }
p.p5 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 18.0px; font: 12.0px Courier; color: #000000 }
p.p6 { margin: 0.0px 0.0px 15.9px 0.0px; line-height: 18.0px; font: 12.0px "PT Sans"; color: #000000 }
p.p8 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 18.0px; font: 12.0px Courier; color: #000000; min-height: 14.0px }
p.p9 { margin: 0.0px 0.0px 12.0px 0.0px; line-height: 18.0px; font: 12.0px Courier; color: #000000 }
p.p10 { margin: 0.0px 0.0px 12.0px 0.0px; line-height: 18.0px; font: 12.0px ".PingFang SC"; color: #0000ee }
li.li5 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 18.0px; font: 12.0px Courier; color: #000000 }
li.li7 { margin: 0.0px 0.0px 0.0px 0.0px; line-height: 18.0px; font: 12.0px ".PingFang SC"; color: #000000 }
span.s1 { }
span.s2 { font: 12.0px ".PingFang SC" }
span.s3 { font: 12.0px Courier }
span.s4 { font: 12.0px ".PingFang SC" }
span.s5 { color: #000000 }
span.s6 { text-decoration: underline; color: #0000ee }
ol.ol1 { list-style-type: decimal }
ul.ul1 { list-style-type: disc }

同源策略(跨域的由来)

同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。

“同源”是指:协议相同、域名相同、端口相同

同源政策的目的:是为了保证用户信息的安全,防止恶意的网站窃取数据。

同源的限制范围:

1. Cookie、LocalStorage 和 IndexDB 无法读取

2. DOM 无法获得

3. AJAX 请求不能发送

这里需要明确的一点是:所谓的域跟js的存放服务器没有关系,比如baidu.com的页面加载了google.com的js,那么此js的所在域是baidu.com而不是google.com。也就是说,此时该js能操作baidu.com的页面对象,而不能操作google.com的页面对象。

跨域的方法

一、使用JSONP跨域(单项跨域-一般用于获取数据)

原理:因为通过script标签引入的js是不受同源策略的限制的(正如前文提到的baidu.com的页面加载了google.com的js)。所以我们可以通过script标签引入一个js或者是一个其他后缀形式(如php,jsp等)的文件,此文件返回一个js函数的调用,如返回一个对象:

JSONP_getPerson({

"name":"怪诞咖啡",

"age":18,

"job":"前端攻城狮",

});

也就是说此文件返回的结果调用了JSONP_getPerson函数,并且把{"name":"怪诞咖啡","age":18,"job":"前端攻城狮"}传进去,这{"name":"怪诞咖啡","age":18,"job":"前端攻城狮"}是一个对象。此时我们的页面中有一个JSONP_getPerson函数,函数JSONP_getPerson就被调用到,并且传入了一个对象。此时就实现了在本域获取其他域数据的功能,也就是跨域。

JSONP_getPerson:前端引入远程js并定义好JSONP_getPerson函数,注意需要先定义好JSONP_getPerson函数,避免在远程js加载完成并调用JSONP_getPerson时,此函数不存在

例子代码:

<script>

function JSONP_getPerson(users) {

console.dir(users);

}

</script>

<script src="http://xxx/index.php"></script>

// 前端代码调用script标签块必须在函数标签块之后

地址说明:http://xxx/index.php,其中的xxx是表示不同域名下的文件,测试的时候,把前端和后端代码放到不同的源中测试

<?php

echo ‘JSONP_getPerson({

"name":"怪诞咖啡",

"age":18,

"job":"前端攻城狮",

})‘;//返回一个js函数的调用

?>

为什么script标签引入的文件不受同源策略的限制?

  1. 因为script标签引入的文件内容是不能被客户端的js获取到的,不会影响到被引用文件的安全,所以没必要使script标签引入的文件遵循浏览器的同源策略。
  2. 通过ajax加载的文件内容是能够被客户端js获取到的,所以ajax必须遵循同源策略,否则被引入文件的内容会泄漏或者存在其他风险。

JSONP的缺点则是:

  1. 它只支持GET请求而不支持POST等其它类型的HTTP请求(虽然采用post+动态生成iframe是可以达到post跨域的目的,但这样做是一个比较极端的方式,不建议采用)。明确说明:jquery使用POST请求jsonp可以成功是由于jquery自动把POST转化成GET,实际还是GET请求
  2. 一般get请求能完成所有功能。如:如果需要给其他域服务器传送参数可以在请求后挂参数(注意不要挂隐私数据),即 <script src="http://xxx/getPerson.php?name=Hello&age=18"></script>

地址说明:http://xxx/getPerson.php?name=Hello&age=18,其中的xxx是表示不同域名下的文件,测试的时候,把前端和后端代码放到不同的源中测试

感悟:

  1. JSONP易于实现,但是也会存在一些安全隐患,如果第三方的脚本随意地执行,那么它就可以篡改页面内容,截获敏感数据。
  2. 在受信任的双方传递数据,JSONP是非常合适的选择。
  3. 可以看出来JSONP跨域一般用于获取其他域的数据。
  4. 一般能够用JSONP实现跨域就用JSONP实现,这也是前端用的最多的跨域方法。

二、动态创建script标签(单项跨域-一般用于获取数据)

这种方法其实是JSONP跨域的简化版,JSONP只是在此基础上加入了回调函数。比如上例中的 getPerson.php 返回的如果不是一个js函数的调用,而是一个js变量,如:

<?php echo ‘var person = {"name":"怪诞咖啡","age":18,"job":"前端攻城狮"}‘ ?>

那么在当前域下就可以取到person变量,这里需要注意判断script节点是否加载完毕,如:

<script>

var head= document.getElementsByTagName(‘head‘)[0];

var scriptPerson= document.createElement(‘script‘);

scriptPerson.type= ‘text/javascript‘;

scriptPerson.src= ‘http://xxx/index.php‘;

head.appendChild(scriptPerson);

scriptPerson.onload = scriptPerson.onreadystatechange = function() {

if (!this.readyState || this.readyState === ‘loaded‘ || this.readyState === ‘complete‘) {

console.log(person); //此处取出其他域的数据

scriptPerson.onload = scriptPerson.onreadystatechange = null;

}

};

</script>

地址说明:http://xxx/index.php,其中的xxx是表示不同域名下的文件,测试的时候,把前端和后端代码放到不同的源中测试

三、window.name属性(单项跨域-一般用于获取数据)

来源:

  1. window.name 传输技术,原本是 Thomas Frank 用于解决 cookie 的一些劣势(每个域名 4 x 20 Kb的限制、数据只能是字符串、设置和获取 cookie 语法的复杂等等)而发明的。后来 Kris Zyp 在此方法的基础上强化了 window.name 传输 ,用来解决跨域数据传输问题。
  2. window.name 的美妙之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

特征:

  1. 浏览器窗口有 window.name 属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
  2. 即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

基本原理:

  1. 当在浏览器中打开一个页面,或者在页面中添加一个iframe时,即会创建一个对应的window对象,当页面加载另一个新的页面时,window的name属性是不会变的。
  2. 这样就可以利用在页面动态添加一个iframe然后src加载数据页面,在数据页面将需要的数据赋值给window.name。
  3. 然而此时承载iframe的index页面(也就是请求数据的页面)还是不能直接访问,不在同一域下iframe的name属性,这时只需要将iframe再加载一个与承载页面同域的空白页面,即可对window.name进行数据读取

例子:(index.html经过3秒跳转到data.html)

<script>

// 页面index.html

window.name =‘我是index.html页面的window.name值‘;

setTimeout(function(){

window.location = ‘http://xxx/data.html‘;

},3000);

</script>

// 页面data.html,两者不同域名

<script>

console.log(window.name);

</script>

代码说明:实际运行中能够看到在 data.html 页面上成功获取到了,它的上一个页面 index.html 给window.name设置的值。如果在之后所有载入的页面都没对 window.name 进行修改的话,那么所有这些页面获取到的 window.name 的值都是 index.html 页面设置的那个值。当然,如果有需要,其中的任何一个页面都可以对window.name的值进行修改。注意, window.name 的值只能是字符串的形式,这个字符串的大小最大能允许2M左右,具体情况取决于不同的浏览器,但一般是够用了。

上面的例子中,我们用到的页面 index.html 和 data.html 在同域和不同域环境下都进行了测试,结果都一样,这也正是利用window.name进行跨域的原理。

问题:这样获取到的 window.name 的值需要跳转页面获取,自然不是我们想要的结果,我们想要的是页面不跳转也可以获取到数据,上面的例子是为了体现和理解 window.name 的跨域能力,这种简单的方法才更有利于初学者理解和学习。

实现不跳转请求数据:接下来我们运用 iframe+window.name 来实现,页面不跳转来获取数据,方法就是:在 index.html 页面中使用一个隐藏的 iframe 来充当一个中间人的角色,由 iframe 去获取 data.index 的数据,然后 index.html 再去得到 iframe 获取到的数据。

例子 => 三个文件:

  1. index.html是主文件,用于请求数据的主文件
  2. empty.html是空文件,是代理文件,用于过渡使用
  3. data.html是请求数据的文件

index.html和empty.html必须在同一域,data.html为其他域的数据文件

<script>

// index.html

var state = 0,

iframe = document.createElement(‘iframe‘),

loadfn = function() {

if (state === 1) {

var data = iframe.contentWindow.name; // 读取数据

console.log(data); // 打印出‘{"name":"怪诞咖啡","age":18,"job":"前端攻城狮"}‘,是字符串类型

} else if (state === 0) {

state = 1;

iframe.contentWindow.location = "empty.html"; // 设置的代理文件

}

};

iframe.src = ‘http://xxx/data.html‘;

if (iframe.attachEvent) {

iframe.attachEvent(‘onload‘, loadfn);

} else {

iframe.onload = loadfn;

}

document.body.appendChild(iframe);

</script>

<script>

// data.html

window.name = ‘{"name":"怪诞咖啡","age":18,"job":"前端攻城狮"}‘;

</script>

地址说明:http://xxx/data.html,其中的xxx是表示不同域名下的文件,测试的时候,把前端和后端代码放到不同的源中测试

我在最初探索window.name+iframe的时候遇到的坑:把 index.html 和 empty.html 没有体现在同一域中(比如页面地址使用 http://localhost/index.html ,里面iframe 的跳转地址使用 iframe.contentWindow.location = ‘http://127.0.0.1/empty.html‘)是不可以的,检测为不是同一个域,原因请看下面介绍

localhost与127.0.0.1的区别是什么?

localhost也叫local ,正确的解释是:本地服务器

127.0.0.1在windows等系统的正确解释是:本机地址(本机服务器)

他们的解析通过本机的host文件,windows自动将localhost解析为127.0.0.1

  • localhot(local)是不经网卡传输!这点很重要,它不受网络防火墙和网卡相关的的限制。
  • 127.0.0.1是通过网卡传输,依赖网卡,并受到网络防火墙和网卡相关的限制。
  • 一般设置程序时本地服务用localhost是最好的,localhost不会解析成ip,也不会占用网卡、网络资源。
  • 有时候用localhost可以,但用127.0.0.1就不可以的情况就是在于此。猜想localhost访问时,系统带的本机当前用户的权限去访问,而用ip的时候,等于本机是通过网络再去访问本机,可能涉及到网络用户的权限。

双向跨域:两个iframe之间或者两个页面之间,一般用于获取对方数据,document.domain方式还可以直接操作对方DOM

四、document.domain(两个iframe之间或者相同一级域名不同的二级域名cookie传递,属于双向跨域)

cookie:

Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。

举例来说,A网页是 http://w1.example.com/a.html ,B网页是 http://w2.example.com/b.html ,那么只要设置相同的 document.domain ,两个网页就可以共享Cookie。

两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域

iframe:

和cookie同理,都是需要设置,document.domain

<script>

document.domain = ‘example.com‘;

</script>

问题:1、安全性,当一个站点被攻击后,另一个站点会引起安全漏洞。2、如果一个页面中引入多个iframe,要想能够操作所有iframe,必须都得设置相同domain。

五、window.postMessage(两个iframe之间或者两个页面之间,属于双向跨域)

HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。

这个API为 window 对象新增了一个 window.postMessage 方法,允许跨窗口通信,不论这两个窗口是否同源。

window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机 (两个页面的模数 Document.domain 设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

六、location.hash(两个iframe之间,属于双向跨域),又称FIM,Fragment identifier Messaging的简写

概念:片段标识符(fragment identifier)指的是,URL的#号后面的部分,比如http://example.com/x.html#fragment的#fragment。如果只是改变片段标识符,页面不会重新刷新。

下面是hash不刷新页面,更新hash的例子:

setTimeout(function(){

location.href= ‘index.html‘ + "#" + ‘{"name":"怪诞咖啡","age":18,"job":"前端攻城狮"}‘;

},1000);

window.onhashchange = checkMessage;

function checkMessage() {

var message = window.location.hash;

console.log(message.slice(1)); //{"name":"怪诞咖啡","age":18,"job":"前端攻城狮"}

}

hash实现跨域的方式:两个文件,一个 index.html 文件,一个是不同域名下的 index.php 文件

// index.html

<script>

function getData(url, fn) {

var iframe = document.createElement(‘iframe‘);

iframe.style.display = ‘none‘;

iframe.src = url;

iframe.onload = function() {

fn(iframe.contentWindow.location.hash.substring(1));

window.location.hash = ‘‘;

document.body.removeChild(iframe);

};

document.body.appendChild(iframe);

}

// get data from server

var url = ‘http://127.0.0.1/index.php‘;

getData(url, function(data) {

var jsondata = JSON.parse(data);

console.log(jsondata);

});

</script>

// index.php

<?php

$data = ‘{\"name\":\"怪诞咖啡\",\"age\":18,\"job\":\"前端攻城狮\"}‘;

echo

"

<script>

window.location = ‘http://localhost/index.html‘ + ‘#‘ + \"$data\";

</script>

"

?>

如果看懂了之前利用 window.name+iframe 跨域获取数据,那么使用 window.hash+iframe 也就很好理解了。一样都是动态插入一个iframe,然后把iframe的src指向服务端地址,而服务端同样都是输出一段js代码,同样都是利用和子窗口之间的通信完成数据传输,同样要针对同源策略做出处理。

HTML标签和跨域

看到之前的介绍,能够了解到跨域都是通过HTML标签做一些事情,HTML有:script、img、iframe、link

CSS中,有伟大的 background 属性,也可以实现跨域

跨域的方法,可以说很多很多,不仅仅局限于上面的方法,今后慢慢来总结这块

时间: 2024-10-08 11:38:50

前端跨域深入理解的相关文章

解决前端跨域请求的几种方式

利用 JSONP 实现跨域调用 说道跨域调用,可能大家首先想到的或者听说过的就是 JSONP 了. 1.1 什么是JSONP JSONP 是 JSON 的一种使用模式,可以解决主流浏览器的跨域数据访问问题.其原理是根据 XmlHttpRequest 对象受到同源策略的影响,而 <script> 标签元素却不受同源策略影响,可以加载跨域服务器上的脚本,网页可以从其他来源动态产生 JSON 资料.用 JSONP 获取的不是 JSON 数据,而是可以直接运行的 JavaScript 语句. 1.2

前端跨域请求原理及实践

前端跨域请求原理及实践 2017-03-03 前端大全 (点击上方公众号,可快速关注) 作者:高鹏 tingandpeng.com/2016/09/05/前端跨域请求原理及实践/ 如有好文章投稿,请点击 → 这里了解详情 一. 跨域请求的含义 浏览器的同源策略,出于防范跨站脚本的攻击,禁止客户端脚本(如 JavaScript)对不同域的服务进行跨站调用. 一般的,只要网站的 协议名protocol. 主机host. 端口号port 这三个中的任意一个不同,网站间的数据请求与传输便构成了跨域调用.

前端跨域的那些事

这一节,我们来讲一讲,前端跨域的那些事,主要分成这样的几部分来讲解, 一.为什么要跨域? 二.常见的几种跨域与使用场景 2.1 JSONP跨域 2.2 iframe跨域 2.3 window.name 跨域 2.4 document.domain 跨域 2.5 cookie跨域 2.6 postMessage跨域 三.总结 一.为什么要跨域 跨域,通常情况下是说在两个不通过的域名下面无法进行正常的通信,或者说是无法获取其他域名下面的数据,这个主要的原因是,浏览器出于安全问题的考虑,采用了同源策略

JAVA解决前端跨域问题。

什么是跨域? 通俗来说,跨域按照我自己的想法来理解,是不同的域名之间的访问,就是跨域.不同浏览器,在对js文件进行解析是不同的,浏览器会默认阻止,所以 现在我来说下用java代码解决前端跨域问题. 用java代码解决前端跨域问题? 找到WEB-INF下面的web.xml文件,输入下面代码,在web.xml文件下面: 1 <!-- 解决跨域访问的问题 --> 2 <filter> 3 <filter-name>cors</filter-name> 4 <

前端跨域问题的几种解决方案

前端跨域问题 一:同源策略 1.what's this 所谓同源是指,域名,协议,端口相同.当浏览器运行一个JS脚本时会进行同源检测,如果不同源是不能执行的. 2.源继承 来自about:blank,javascript:和data:URLs中的内容,继承了将其载入的文档所指定的源,因为它们的URL本身未指定任何关于自身源的信息. 3.变更源 变更源可以实现基础域相同的不同页面的跨域问题. 如:a.baidu.com/index.html 通过 iframe 引入 b.baidu.com/ind

前端跨域访问

1. JSONP 2. CORS(Cross-origin resource sharing) 2.1 运行模式 2.2 JQuery支持CORS 2.3 与JSONP相比 3. 跨域访问在点评的应用 References 在互联网应用中: 一个页面需要请求多个域名下的web服务端接口 同时一个web服务接口可能会被很多不同域名下的页面请求. 一个web应用如果支持为了支持以上模式而申请多个域名是不合算的,因为域名申请和管理所占用的资源比较大,因此服务端支持跨域就成了一个更合理的解决方案.解决跨

前端跨域几种方式

跨域问题的直接原因是浏览器存在同源策略,浏览器同源指的是:两个页面的协议.端口和主机相同,则两个页面具有相同的源.IE下满足协议.主机相同,就认为是同源. 想象一下,如果没有同源策略,谁都可以修改你站点上的内容,读取你的cookie,后果难以想象 前端跨域的几种方式 修改document.domain document.domain 用来获取当前网页的域名,document.domain可以被赋值 document.domain只能修改成当前域名的主域名或者基础域名,如当前域名是b.360.cn

前端跨域杂谈

1.前端跨域之表单(post) 项目需求,需要跨域向另一台服务器传送大量值,从ajax角度跨域是get方式,基于此,方案采用构造<form>表单,通过action发送到对方服务器,对方服务器需要做一些配合. 发送方post.html <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta content="width=device-width,

.net mvc webapi 解决前端跨域问题

跨域问题的原因不解释了,直接设置两步就可以解决前端跨域问题 1.Gloabel.asax文件中 //解决跨域问题 protected void Application_BeginRequest(object sender, EventArgs e) { RegisterRoutes(RouteTable.Routes); if (HttpContext.Current.Request.HttpMethod == "OPTIONS") { HttpContext.Current.Resp