同源策略
同源策略是浏览器最核心也最基本的安全功能,它指的是“协议,域名,端口”三者相同,即使两个不同的域名指向同一个IP地址,也非同源。
同源限制:ajax请求受到限制,不能获取DOM,不能获取cookie,localstorage,indexDB
跨域的方法
1 JSONP
利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。
原生实现
//原生实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
// 定义一个回调函数 处理相应数据
var flightHandler = function(data){
console.log(data)
}
// 要访问的网址 ?后面表示传递的参数以及回调函数名
var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
//创建一个标签
var script = document.createElement("script")
script.src = url;
//将标签加入到DOM里面,调用开始
document.head.appendChild(script)
</script>
</head>
jQuery实现
<script type="text/javascript">
//回调函数可以直接用?,jquery自动处理
$.getJSON('http://crossdomain.com/jsonp/services.php?callback=?', function (data) {
console.log(data)
})
</script>
ajax实现
<script type="text/javascript">
$(document).ready(function(){
$.ajax({
url: "http://flightQuery.com/jsonp/flightResult.aspx",
dataType: "jsonp",
data: "code=CA1998&callback=?", //传递的参数
type: "get", //一定是get方法 可省略
jsonp: "callback", //用来获得回调函数名的参数名,可省略
jsonpCallback: "flighthandler", //自定义传递的函数名 可省略
success: function (json) {
console.log(JSON.parse(json))
},
error: function () {
console.log("fail")
}
})
}
</script>
vue实现
this.$http.jsonp('http://flightQuery.com/jsonp/flightResult.aspx', {
param: {},
jsonp: "callback",
}).then((res) => {
console.log(res)
})
后端node.js实现
var querystring = require('querystring') //请求字符串
var http = require('http') //http请求
var sever = http.createSever()
sever.on('request', function (req, res) { //服务器对request的响应
var params = qs.parse(querystring.split("?")[1]) //取参数
var fn = params.callback; //取回调函数名
res.writeHead(200, { 'Content-Type': 'text/javascript' }) //设置响应头
res.write(fn + '(' + JSON.stringify(params) + ')') //写响应体
res.end() //响应内容发送
})
sever.listen('8080')
jsonp就是利用js脚本可以跨源访问的特点,进行一种hack,只能get请求。
2 CORS
需要浏览器和服务器同时支持cors,浏览器不需要配置,使用起来和ajax一样
两种请求
1 简单请求
同时满足下列两个条件:
(1) 使用下列方法:get,post,head
(2) 允许的首部字段;
- Accept
- Accept-Language
- Content-Language
- Content-Type (需要注意额外的限制)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
其中content-type需要是下列之一:
- text/plain (纯文本格式)
- multipart/fram-data (post提交数据的方式)
- application/x-www-form-urlencoded (post提交数据的方式)
(3) 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。 (4)请求中没有使用 ReadableStream 对象。
2 非简单请求
非简单请求时会先发送预检请求 方法为OPTIONS
前提:假如站点 foo.example 的网页应用想要访问 bar.other 的资源。
请求头和响应头相关字段:
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
实际请求和响应
POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache
<?xml version="1.0"?><person><name>Arun</name></person>
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some GZIP'd payload]
响应首部
Access-Control-Allow-Origin //定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。当不为* 时,vary中必须包含Origin
Access-Control-Expose-Headers //响应头白名单,客户端可以获取
Access-Control-Max-Age //预检结果被缓存的时间
Access-Control-Allow-Credentials //允许携带cookie
Access-Control-Allow-Methods //预检 允许的跨域请求方法
Access-Control-Allow-Headers // 预检 允许请求中设置的首部字段
附带身份验证的请求
var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain(){
if(invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true;
invocation.onreadystatechange = handler;
invocation.send();
}
}
默认情况下不带cookie,即跨域的cookie,在ajax中这是withCridentails为true,就会附带cookie,对于简单请求,cookie就被发送过去了,但是如果响应中不包含Access-Control-Allow-Credentials为true,浏览器就不会把响应的内容展示出来。另外对于附带身份凭证的请求,Access-Control-Allow-Origin不能设置为*,否则请求将会失败。
3 document.domain + iframe
对于ifram标签,跨域时显示内容没问题,但是操作数据不行
情景:两个页面的主域要相同
4 location.hash + iframe
5 window.name + iframe
原理:页面发生跳转,但是window.name依然存在。在iframe页面设置一个window.name,父页面能取到。
<style>
#ifra {
height: 500px;
width: 500px;
}
</style>
</head>
<body>
<iframe id="ifra" src="http://jasonkid.github.io/fezone/" frameborder="0"></iframe>
<script>
let iframe = document.getElementById('ifra')
iframe.onload = function () {
iframe.onload = function () { //第二次加载同源页面的时候,就可以取到ifram页面设置的window.name了
let data = iframe.contentWindow.name
console.log(data)
}
iframe.src = 'about:blank' //设置为和父页面同源。页面发生跳转,但是window.name依然存在
}
</script>
</body>
6 navigation
7 postMessage
<body>
<iframe id="iframe" width="480px" height="270px" src="http://jasonkid.github.io/fezone/"></iframe>
<script>
var iframe = document.getElementById('iframe');
window.addEventListener('message', function(event) {
console.log(event);
event.source.postMessage('Message from parent page.', '*');
//e.source是指发送这个消息的window对象,来自iframe里面的网页
//e.origin = http://jasonkid.github.io/fezone/
});
</script>
</body>
安全讨论
在HTML5之前,JSONP已经成为跨域的事实标准了,jQuery都给出了支持。 值得注意的是它只是Hack,并没有产生额外的安全问题。 因为JSONP要成功获取数据,需要跨域资源所在服务器的配合,比如资源所在服务器需要自愿地回调一个合适的函数,所以服务器仍然有能力控制资源的跨域访问。
跨域的正道还是要使用HTML5提供的CORS头字段以及window.postMessage, 可以支持POST, PUT等HTTP方法,从机制上解决跨域问题。 值得注意的是Access-Control-Allow-Origin头字段是资源所在服务器设置的, 访问控制的责任仍然是在提供资源的服务器一方,这和JSONP是一样的。