同源策略与跨域实现
同源策略
它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面,当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。 如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。
浏览器同源策略有两种限制,其限制之一是不能通过ajax的方法去请求不同源中的文档。 第二个限制是浏览器中不同域的框架之间不能进行js的交互操作的,
同源的条件
- 协议相同
- 端口相同
- 域名相同
下表给出了相对http://store.company.com/dir/page.html
同源检测的示例:
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 成功 | 只有路径不同 |
http://store.company.com/dir/inner/another.html | 成功 | 只有路径不同 |
https://store.company.com/secure.html | 失败 | 不同协议(http和https) |
http://store.company.com:81/dir/etc.html | 失败 | 不同端口 |
http://news.company.com/dir/other.html | 失败 | 不同域名 |
关于同源举个例子
一个班级,里面有很多的学生,每个学生都有自己的家长,班级有一个窗口,家长们想要了解孩子的情况就可以通过窗口来询问,当然窗口首先会核实家长是否属实,然后才能回复情况。不是同一个家庭的便不能咨询情况
这里的情形,孩子和家长就好比是一个个的网站,窗口就是浏览器,家长和孩子便是同源。只有同源的才能进行访问。
所以如果Web世界没有同源策略,当你登录Gmail邮箱并打开另一个站点时,这个站点上的JavaScript就可以跨域读取你的Gmail邮箱数据,这样整个Web世界就无隐私可言了。
但另一方面,安全性和方便性是成反比的,十位数的密码提高了安全性,但是不方便记忆。同样,同源策略提升了Web前端的安全性,但牺牲了Web拓展上的灵活性。设想若把html、js、css、flash,image等文件全部布置在一台服务器上,小网站这样凑活还行,大中网站如果这样做服务器根本受不了的,可用性都不能保证的话,安全性还算个吊?
所以,现代浏览器在安全性和可用性之间选择了一个平衡点。在遵循同源策略的基础上,选择性地为同源策略“开放了后门”。
例如img script style等标签,都允许垮域引用资源,严格说这都是不符合同源要求的。然而,你也只能是引用这些资源而已,并不能读取这些资源的内容,不信你可以试试:在你自己的域内读取百度logo图片的内容,以读取到二进制数据为准。
你很快发现这是不可能的,你顶多只能判断这张图片是否存在(使用的img标签的onerror属性 )。因此浏览器降低了那么一点点安全性,却大大提升网站布置的灵活性。尽管浏览器开放了“后门”,然而现代文明却仍不满足。打破同源策略的方法有很多,抛开漏洞不谈,Server端的大数据整合和浏览器的插件往往就充当着这样一个角色。我有一个同事,由于在淘宝上买过一个自慰器,从此不管他打开哪个网站都会有自慰器的广告投递,弄得人家好尴尬。我手把手教他清除Cookie,发现者根本不奏效。(吐槽:我们使用的是一个公网IP,后来我吃惊的发现我的电脑中也出现了自慰器的广告).好吧,这样的例子还有很多。虽然,攻城狮们越来越肆无忌惮的浪,然而浏览器们依旧保持着他那份应有的节操(国产浏览器除外)。
比如下面的代码,我们访问 192.168.10.14下的一个文件,然后用js读取其数据,显示在页面上
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ajax</title>
<script type="text/javascript">
function foo(){
var xmlhttp=new XMLHttpRequest();
xmlhttp.open("GET","http://192.168.10.14/1.txt",true);
xmlhttp.send();
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.getElementById("my").innerHTML=xmlhttp.responseText;
}
}
}
</script>
</head>
<body>
<button id="btn" οnclick="foo()">确定</button>
<p id="my">hello,word!</p>
</body>
</html>
当点击确定按钮读取 http://192.168.10.14/1.txt 的内容,并替换 hello,word! 的时候,发生了如下错误。这就是跨域请求错误!
跨域的实现
所以还是有一些方法是可以跨域的
1. 降域
源策略认为域和子域属于不同的域,如:
child1.a.com 与 a.com,
child1.a.com 与 child2.a.com,
abc.child1.a.com 与 child1.a.com
两两不同源,但是可以通过设置 document.domain=‘a.com’,浏览器就会认为它们都是同一个源。想要实现以上任意两个页面之间的通信,两个页面必须都设置documen.domain=‘a.com’。
此方式的特点:
- 只能在父域名与子域名之间使用,且将 xxx.child1.a.com域名设置为a.com后,不能再设置成child1.a.com
- 存在安全性问题,当一个站点被攻击后,另一个站点会引起安全漏洞
- 这种方法只适用于 Cookie 和 iframe 窗口
2.JSONP
JSONP 是 JSON With Padding(填充式 JSON 或参数式 JSON)的简写。
JSONP实现跨域请求的原理:简单的说,就是动态创建<script>
标签,然后利用<script>
的 src 属性不受同源策略约束来跨域获取数据。
JSONP 由两部分组成:回调函数 和 数据。回调函数是用来处理服务器端返回的数据,回调函数的名字一般是在请求中指定的。而数据就是我们需要获取的数据,也就是服务器端的数据。
JSONP在不同的语言中有不同的实现方法,以下只展示在html中的简单实现过程。
JSONP的简单实现过程:
举例:如果 a.com/abc.html 想得到 b.com/1.txt 中的数据,首先在abc.html中创建一个回调函数handleResponse,用来处理服务器端返回的数据。然后在abc.html中创建一个函数 foo,该函数的功能是动态添加
a.com/abc.html的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSONP实现跨域</title>
<script type="text/javascript">
function handleResponse(response){ //处理服务器返回的数据
console.log(response); //控制台输出
}
function foo() {
var script = document.createElement("script");
script.src = "http://192.168.31.122/1.txt"; //设置请求的链接以及处理返回数据的回调函数
document.body.insertBefore(script, document.body.firstChild);
}
</script>
</head>
<body>
<button id="btn" οnclick="foo()">确定</button>
</body>
</html>
192.168.10.14/1.txt 的代码,设置回调函数,数据以JSON格式存放
handleResponse([ { "name":"xie",
"sex" :"man",
"id" : "66" },
{ "name":"xiao",
"sex" :"woman",
"id" : "88" },
{ "name":"hong",
"sex" :"woman",
"id" : "77" }]
)
然后当我们点击了 确定按钮后,console控制台就输出了从192.168.10.14/1.js 传过来的JSON格式的数据了
3.CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。使用AJAX技术跨域获取数据
刚才的例子中,用CORS实现跨域。
在b.com里面添加响应头声明允许a.com的访问,代码
Access-Control-Allow-Origin: http://a.com
然后a.com就可以用ajax获取b.com里的数据了。
4.如果是log之类的简单单项通信,新建<img>,<script>,<link>,<iframe>
元素,通过src,href属性设置为目标url。实现跨域请求
5.内部服务器代理请求跨域url,然后返回数据
允许跨域的标签
-
<script src="..."></script>
标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。 -
<link rel="stylesheet" href="...">
标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的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)。 -
<frame> 和 <iframe>
载入的任何资源。站点可以使用X-Frame-Options
消息头来阻止这种形式的跨域交互。
参考:
https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
https://www.jianshu.com/p/4e17445d66e2
https://zhuanlan.zhihu.com/p/26585158
https://www.zhihu.com/question/25427931
https://blog.csdn.net/qq_36119192/article/details/82931250
上一篇: vue学习之全局api
下一篇: Java 面向对象编程