在实际工作中,遇到前后端进行数据交互,经常会碰到请求跨域,今天就来探讨一下什么是跨域以及跨域的几种实现方式!
什么是跨域
同源策略(Same origin Policy
)
浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。
本域指的是?
- 同协议:如都是http或者https
- 同域名:如都是http://jirengu.com/a 和http://jirengu.com/b
- 同端口:如都是80端口
如:
http://jirengu.com/a/b.js 和 http://jirengu.com/index.php (同源)
不同源的例子(跨域):
http://jirengu.com/main.js 和 https://jirengu.com/a.php (协议不同)
http://jirengu.com/main.js 和 http://bbs.jirengu.com/a.php (域名不同,域名必须完全相同才可以)
http://jiengu.com/main.js 和 http://jirengu.com:8080/a.php (端口不同,第一个是80)
需要注意的是: 对于当前页面来说页面存放的 JS 文件的域不重要,重要的是加载该 JS 页面所在什么域
请求跨域了,那么请求到底发出去没有?
看一下代码就会明白
HTML
HTML
<h1>饥人谷</h1>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET','http://127.0.0.1:8080/getWeather', true)
xhr.send()
xhr.onload = function(){
console.log(xhr.responseText)
}
</script>
js部分
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true)
switch (pathObj.pathname) {
case '/getWeather':
res.end(JSON.stringify({beijing: 'sunny'}))
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}).listen(8080)
当前域名
用
AJAX
请求数据 是请求结果
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax
就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax
可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF
,因为请求毕竟是发出去了。
跨域解决方案
jsonp
JSONP
是通过script
标签加载数据的方式去获取数据当做 JS
代码来执行 提前在页面上声明一个函数,函数名通过接口传参的方式传给后台,后台解析到函数名后在原始数据上「包裹」这个函数名,发送给前端。换句话说,JSONP
需要对应接口的后端的配合才能实现。
JSONP
和AJAX
对比
JSONP
和AJAX
相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX
属于同源策略,JSONP
属于非同源策略(跨域请求)
JSONP优缺点
JSONP
优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get
方法具有局限性,不安全可能会遭受XSS
攻击。
实现方式
HTML
中 script
标签可以加载其他域下的js
,比如我们经常引入一个其他域下线上cdn
的jQuery
。那如何利用这个特性实现从其他域下获取数据呢?
可以先这样试试:
<script src="http://api.jirengu.com/weather.php"></script>
这时候会向天气接口发送请求获取数据,获取数据后做为 js 来执行。 但这里有个问题, 数据是 JSON 格式的数据,直接作为 JS 运行的话我如何去得到这个数据来操作呢?
这样试试:
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
这个请求到达后端后,后端会去解析callback
这个参数获取到字符串showData
,在发送数据做如下处理:
之前后端返回数据:{"city": "hangzhou", "weather": "晴天"}
现在后端返回数据showData({"city": "hangzhou", "weather": "晴天"})
前端script
标签在加载数据后会把 「showData({“city”: “hangzhou”, “weather”: “晴天”})」
做为js
来执行,这实际上就是调用showData
这个函数,同时参数是 {“city”: “hangzhou”, “weather”: “晴天”}。
用户只需要在加载提前在页面定义好showData
这个全局函数,在函数内部处理参数即可。
<script>
function showData(ret){
console.log(ret);
}
</script>
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
具体流程如下
声明一个回调函数,其函数名(如
showData
)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data
)。创建一个
<script>
标签,把那个跨域的API
数据接口地址,赋值给script
的src
,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=showData
)。服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是
showData
,它准备好的数据是show({"city": "hangzhou", "weather": "晴天"})
。最后服务器把准备的数据通过
HTTP
协议返回给客户端,客户端再调用执行之前声明的回调函数(showData)
,对返回的数据进行操作。
以上就是JSONP(JSON with padding)
。
CORS
CORS
全称是跨域资源共享(Cross-Origin Resource Sharing)
,是一种 ajax
跨域请求资源的方式,支持现代浏览器,IE支持10以上。 实现方式很简单,当你使用XMLHttpRequest
发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin
,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin
(访问控制允许来源); 浏览器判断该相应头中是否包含 Origin
的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。所以 CORS
的表象是让你觉得它与同源的ajax
请求没啥区别,代码完全一样。
其实实现原理很简单
就是加上一句代码指定允许来源
res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')
示例:
html部分
<!DOCTYPE html>
<html>
<body>
<div class="container">
<ul class="news">
</ul>
<button class="show">show news</button>
</div>
<script>
$('.show').addEventListener('click', function(){
var xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:8080/getNews', true)
xhr.send()
xhr.onload = function(){
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
}
function $(selector){
return document.querySelector(selector)
}
</script>
</html>
js部分:
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true)
switch (pathObj.pathname) {
case '/getNews':
var news = [
"第11日前瞻:中国冲击4金 博尔特再战200米羽球",
"正直播柴飚/洪炜出战 男双力争会师决赛",
"女排将死磕巴西!郎平安排男陪练模仿对方核心"
]
//指定
res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')
//res.setHeader('Access-Control-Allow-Origin','*')
res.end(JSON.stringify(news))
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}).listen(8080)
浏览器会进行比较,如果一致就允许访问,否则就拒绝!
CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。
浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
res.setHeader('Access-Control-Allow-Origin','*')
(所有网站都可以访问资源)
降域
原理就是 设置另个域名之间相同的后缀 实现降域
<html>
<style>
.ct{
width: 910px;
margin: auto;
}
.main{
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input{
margin: 20px;
width: 200px;
}
.iframe{
float: right;
}
iframe{
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
<div class="ct">
<h1>使用降域实现跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.jrg.com:8080/a.html">
</div>
<iframe src="http://b.jrg.com:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
//URL: http://a.jrg.com:8080/a.html
document.querySelector('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "jrg.com"
</script>
</html>
postMessage
postMessage
是HTML5
XMLHttpRequest Level 2
中的API
,且是为数不多可以跨域操作的window
属性之一,它可用于解决以下方面的问题:
页面和其打开的新窗口的数据传递
多窗口之间消息传递
页面与嵌套的
iframe
消息传递
上面三个场景的跨域数据传递
postMessage()
方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
message: 将要发送到其他 window的数据。
targetOrigin
:通过窗口的origin
属性来指定哪些窗口能接收到消息事件,其值可以是字符串 *
(表示无限制)或者一个URI
。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin
提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
transfer(可选)
:是一串和message
同时传递的 Transferable
对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
// a.html
<iframe src="http://localhost:4000/b.html" frameborder="0"id="frame"onload="load()"></iframe> //等它加载完触发一个事件
//内嵌在http://localhost:3000/a.html
<script>
function load(){
let frame =document.getElementById('frame')
frame.contentWindow.postMessage('我爱你','http://localhost:4000')
//发送数据
window.onmessage = function(e){
//接受返回数据
console.log(e.data)
//我不爱你
}
}
</script>