同源策略:
三同:协议同,域名同,端口号同
下表给出了相对http://a.xyz.com/dir/page.html同源检测的示例:
URL | 结果 | 原因 |
---|---|---|
http://a.xyz.com/dir2/other.html | 成功 | |
http://a.xyz.com/dir/inner/another.html | 成功 | |
https://a.xyz.com/secure.html | 失败 | 不同协议 ( https和http ) |
http://a.xyz.com:81/dir/etc.html | 失败 | 不同端口 ( 81和80) |
http://a.opq.com/dir/other.html | 失败 | 不同域名 ( xyz和opq) |
受浏览器同源策略的限制,非同源的脚本不能操作其他域的页面对象(如DOM等)
同源策略,它是由[Netscape]提出的一个著名的[安全策略]现在所有支持JavaScript 的浏览器都会使用这个策略。
所谓同源是指,域名,协议,端口相同。
当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面
当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,
即检查是否同源,只有和百度同源的脚本才会被执行。
如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。所以xyz.com下的js脚本采用ajax读取abc.com里面的文件数据是会被拒绝的。
不受同源策略限制的
1. 页面中的链接,重定向以及表单提交是不会受到同源策略限制的。
2. 跨域资源的引入是可以的。但是js不能读写加载的内容。如嵌入到页面中的<script src="..."></script>,<img>,<link>,<iframe>等
什么是跨域?跨域有几种实现的形式?
跨域顾名思义就是突破同源策略的限制,去不同的域下访问数据。 主要有如下几种实现形式:
- jsonp
- CORS:跨域资源共享(Cross-Origin Resource Sharing)
- 降域
- postMessage()
原理:就是利用<script>标签没有跨域限制来达到与第三方通讯的目的。当需要通讯时,本站脚本创建一个<script>元素,地址指向第三方的API网址,形如:
<script src="http://www.example.net/api?param1=1¶m2=2"></script>
并提供一个回调函数来接收数据(函数名可约定,或通过地址参数传递)。
第三方产生的响应为json数据的包装(故称之为jsonp,即json padding),形如:
callback({"name":"hax","gender":"Male"})
这样浏览器会调用callback函数,并传递解析后json对象作为参数。本站脚本可在callback函数里处理所传入的数据。
前端发请求时将函数名,例如callback传给后台,后台再返回这个函数,callback(info)。这样浏览器接受到数据后,会马上执行callback函数,就像回调函数一样。
前端代码:
function callback(info){
alert(info);
}
function getUser(){
let dom = document.createElement('script');
dom.src = "http://127.0.0.1:3000/user?callback=callback"; //url上指明回调函数的名字
document.body.appendChild(dom);
}
后台代码:
const express = require('express');
const app = express();
app.get('/user',(req,res)=>{
let func = req.query.callback;
res.send(func+'(' + '"success"'+')');
});
app.listen(3000,()=>{
console.log('start');
});
再次执行getUser();成功弹出了success。这就是jsonP的原理。后台返回一个JS函数,带上参数,调用前端准备好的函数。即实现了发请求和回调函数的效果。
cors
这个就更简单了。上面ajax跨域的时候报了这个错误 No 'Access-Control-Allow-Origin' header is present on the requested resource。
那服务器返回的时候带上‘'Access-Control-Allow-Origin' 就好了。他的意思是允许哪些源的请求。你虽然不是我家的人,但这次我信任你,给你个钥匙。
改装下后台代码:
const express = require('express');
const app = express();
app.get('/user',(req,res)=>{
res.header("Access-Control-Allow-Origin", "*"); //设置返回数据的头,代表接受任意源的请求
res.send('success');
});
app.listen(3000,()=>{
console.log('start');
});
再在控制台调用上文的getUserAjax()。成功。前端都不用改代码。不过要注意的是cors不是所有的浏览器都会支持。
CORS简介:
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
CORS的两种请求:
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
只要同时满足以下两大条件,就属于简单请求。
(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
凡是不同时满足上面两个条件,就属于非简单请求。
浏览器对这两种请求的处理,是不一样的。
- 简单请求:对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。
Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段
(1)Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
(2)Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
(3)Access-Control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。如:getResponseHeader('FooBar')可以返回FooBar字段的值。
上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。
Access-Control-Allow-Credentials: true
另一方面,开发者必须在AJAX请求中打开withCredentials属性。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。
但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials。
xhr.withCredentials = false;
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie
非简单请求:非简单请求是那种对服务器有特殊要求的请求,
比如请求方法是PUT或DELETE,
或者Content-Type字段的类型是application/json。
前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>CORS</title>
<style>
.container{
width:900px;
margin:0 auto;
}
</style>
</head>
<body>
<div class="container">
<ul class="news">
<li>头条新闻1</li>
<li>头条新闻2</li>
<li>头条新闻3</li>
<li>头条新闻4</li>
</ul>
<button class="change">换一组</button>
</div>
<script>
function $(id){
return document.querySelector(id);
}
$('.change').addEventListener('click',function(){
var xhr = new XMLHttpRequest();
xhr.open('get','http://a.zyn.com:8080/getNews',true);
//html通过zyn.com打开而通过ajax获取a.zyn.com上的资源,所以为跨域。
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState ===4 && xhr.status ===200){
appendHtml(JSON.parse(xhr.responseText))
}
}
})
function appendHtml(news){
var html ='';
for(var i=0;i<news.length;i++){
html +='<li>'+news[i]+'</li>'
}
$('.news').innerHTML = html;
}
</script>
</body>
</html>
后台代码:
app.get('/getNews',function(req,res){
var news = [
"头条新闻5",
"头条新闻6",
"头条新闻7",
"头条新闻8",
"头条新闻9",
"头条新闻10",
"头条新闻11",
"头条新闻12"
]
var data =[];
for(var i=0;i<4;i++){
var index = parseInt(Math.random()*news.length);
data.push(news[index]);
news.splice(index,1);
}
res.header("Access-Control-Allow-Origin","http://zyn.com:8080")//表示这个后台接受来自zyn.com的请求
//res.header("Access-Control-Allow-Origin","*")//表示这个后台接受来自任意*的请求
res.send(data)
})
jsonP和cors比较
cors与jsonP的使用目的相同,但是比jsonP更强大。
jsonP只支持GET请求,cors支持所有类型的HTTP请求。jsonP的优势在于支持老式浏览器,以及可以向不支持cors的网站请求数据。
降域
利用域名为同一个基础域名("http://a.zyn.com/a.html"和'"http://b.zyn.com/b.html")
使用document.domain="zyn.com"进行跨域;
这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,
否则无法利用document.domain进行跨域
比如,有两个二级域名:a.yang.com 和 b.yang.com,
可通过设定 document.domain 的值为主域名:yang.com 的方式,
突破浏览器的同源策略限制,来获取和操作对方的元素
-
降域的使用
-
A 页面域为:a.yang.com
-
B 页面域为:b.yang.com
-
A 和 B 两页面都需加入该行代码:
document.domain = 'yang.com';
,‘yang.com 是 a.yang.com 和 b.yang.com 的主域名
postMessage
-
postMessage 是 HTML5 中新增方法,可实现跨域通信;
-
postMessage 并不是向服务器读写资源,只是向外发送消息而已;可以把它当做使用手机发送短信消息,仅此而已。
-
也就是:A 页面向 B 页面发送了一条消息,B 页面会接受到该消息,如果 B 页面需要该消息,则监听 message;否则无需关心该消息
-
postMessage 的使用 发送方:为目标元素添加事件处理程序,监听事件类型 接收方:为 window 添加事件处理程序,事件类型为 message
我们拿跨域中的iframe做例子:
<script type="text/javascript"> window.parent.postMessage('hello world','*'); //在被嵌套的iframe的页面中写入这样一段代码 </script>
注意:postMessage的写法,postMessage之前写的是你要通信的window对象(也就是你要像谁通信),此时的window.parent的权限仅限于此,不能在像同域似的,进行获取父级的DOM元素,否则浏览器会报错,提示你不能进行跨域访问,我们再来看postMessage中所接收的参数,第一个参数就是你要像另外一个窗口传递的数据(只能传字符串类型),第二个参数表示目标窗口的源,协议+主机+端口号,是为了安全考虑,如果设置为“*”,则表示可以传递给任意窗口。
那么另外一个窗口是如何接收数据的呢
<script type="text/javascript"> window.addEventListener('message',function(e){ console.log(e.data); //hello world console.log(e.origin); //http://127.0.0.1:8020 所传来数据的域 }) </script>
可以看到我们已经拿到了数据,那么拿到数据我们可以做那些操作呢
<script type="text/javascript"> window.addEventListener('message',function(e){ console.log(e.data); //hello world if(e.data=="hello world"){ document.body.style.background = 'red'; } }) </script>