一、何为跨域
广义的跨域是指一个域下的文档和脚本试图请求另一个域下的资源。
举
- 资源跳转: A链接、重定向、表单提交
- 资源嵌入:
<link>、<script>、<img>、<frame>
等DOM标签,还有样式中background:url()、@font-face()
等文件外链 - 脚本请求: Js发起的Ajax请求、DOM和Js对象的跨域操作等
而我们常说的跨域,是指浏览器不能执行其他网站的脚本。它是由于浏览器的同源策略限制造成的。
同源策略: 浏览器对JS实施的安全限制,只要“协议、域名、端口号”有任何一个不同,都被当做是不同的域。缺少哦同源策略,浏览器很容易受到XSS、CSFR
等攻击
同源策略限制以下几种行为:
cookie、LocalStorage和IndexDB
无法读取DOM
和JS
对象无法获得AJAX
请求不能发送
关于XSS和CSRF, cc之后会专门整理关于网络安全防范的文章~
二、跨域的原理
跨域的原理就是通过各种方式,避开浏览器的安全限制。
三、跨域的解决方案
我们可以通过多种方式实现跨域,常见的如下七种,大致分为iframe
、api
跨域:
-
JSONP(JSON with Padding): 开发人员创造的一种非官方跨域数据交互协议。Ajax请求受到同源策略的影响,不允许进行跨域请求,而
script
标签src属性中的链接却可以访问跨域的js脚本,web页面调用JS文件不受浏览器同源策略的限制。利用这个特性,服务端不再返回json格式的数据,而是返回一段调用某个函数的js代码,在src中进行调用,这样就可以实现跨域。 jsonp通过动态创建script,再请求一个带参网址实现跨域通信,解决老版本浏览器跨域数据访问问题,流程如下:. 首先前端设置好回调参数,并将其作为
URL
的参数
. 服务器端收到请求之后,通过该参数获取到回调函数名,并将数据放在参数中返回
. 收到结果后因为是script
标签,所以浏览器当做脚本运行。
详细说一下前端实现JSONP:
- 原生实现
var script = document.createElement('script')
script.type = 'text/javascript'
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback'
document.head.appendChild(script)
// 回调执行函数
function handleCallback(res){
alert(JSON.stringify(res))
}
服务端返回如下(返回时执行全局函数):
handleCallback({"status": true, "user": "admin"})
- Jquery / Ajax
$.ajax({
url: 'http://www.domain2.com:8080/login',
type: 'get',
dataType: 'jsonp', // 格式方法为jsonp
jsonpCallback: "handleCallback", // 自定义回调函数名
data: {}
});
- Vue.js
this.$http.jsonp('http://ww.domain2.com:8080/login',{
params: {},
jsonp: 'handleCallback'
}).then((res) =>{
console.log(res)
})
后端Node.js代码示例:
var querystring = require('querystring')
var http = require('http')
var server = http.createServer()
server.on('request', function(req, res){
var params = qs.parse(req.url.split('?')[1])
var fn = params.callback
// jsonp返回设置
res.writeHead(200, {'Content-Type': 'text/javascript'})
res.write(fn + '(' + JSON.stringify(params) + ')')
res.end()
});
server.listen('8080')
console.log('Server is running at port 8080 ...')
常见的手写jsonp算法示例:
jsonp原理:
借助script标签不受同源策略限制的特点,把script标签的src指向请求的服务端地址。
function jsonp(url, data = {}, callback = 'callback') {
// 处理json对象,将参数拼接为url
data.callback = callback
let params = []
for(let key in data) {
params.push(key + '=' + data[key])
}
let script = document.createElement('script')
script.src = url + '?' + params.join('&')
document.body.appendChild(script)
return new Promise((reslove, reject) => {
window[callback] = (data) => {
try {
reslove(data)
} catch (e) {
reject(e)
} finally {
// 移除script元素
script.parentNode.removeChild(script)
}
}
})
}
// 使用jsonp请求数据
jsonp('http://dsbcjkxs.index', {
page: 1,
cate: 'recommend',
}, 'jsonpcallback').then(data => {
console.log(data)
})
-
CORS跨域资源共享(Cross-origin resource sharing):是一个W3C标准,允许浏览器向跨源服务器发送
XMLHTTP Request
请求,从而克服了ajax只能同源使用的限制,实现CORS的关键是服务器,只要服务器实现了CORS接口,就可以进行跨源通信。-
前端的逻辑十分简单,正常发起ajax即可,成功的关键在于服务器
Access-Control-Allow-Origin
是否包含请求页面的域名,若要携带cookie请求,前后端都需要设置。如果不包含的话,浏览器将认为这是一次失败的异步请求,将会调用xhr.onerror
中的函数。整个Cors的通信过程都是浏览器自动完成,不需要用户参与。 -
CORS使用简单,支持
POST
方式,但是存在兼容问题 -
浏览器将CORS请求分为两类:简单请求和非简单请求,浏览器对于这两种的处理不同
-
简单请求(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
-
非简单请求(not-so-simple request):
1)对浏览器有特殊要求的请求,PUT、DELETE
或者字段为Content-Type: application/json
类型的请求
2)在正式通信之前,会增加一次HTTP查询请求,成为**“预检”**请求(preflight
),请求方法是OPTIONS
Origin: 表示请求来自哪个源
Access-Control-Request-Method: 必选,列出CORS请求会用到哪些HTTP方法
Access-Control-Request-Headers: 逗号分隔,指定浏览器CORS请求会额外发送的头信息字段 -
如果服务器同意本次请求,返回的响应会多出几个头信息字段:
Access-Control-Allow-Origin: 返回Origin的字段或*
Access-Control-Allow-Credentials: 可选, bool值,表示是否发送cookie
Access-Control-Expose-Headers -
举Vue框架中的设置:
axios设置:axios.defaults.withCredentials = true
vue-resource设置:Vue.http.options.credentials = true
-
-
代理跨域:把跨域的请求操作发送给后端,让后端帮忙代理请求,包括
nginx
代理跨域、nodejs
中间件代理跨域、webSocket
协议跨域 -
document.domain + iframe跨域:两个页面都通过js强制设置
document.domain
为基础主域,就实现了同域。【此方案仅限主域相同,子域不同的跨域应用场景】 -
location.hash + iframe跨域 :a若要与b跨域相互通信,通过中间页c来实现,c与a同域,可以通过parent.parent来访问a页面的所有对象。三个页面,不同域之间利用iframe的
location.hash
传值,相同域之间直接js访问来通信。 -
window.name + iframe跨域 :通过iframe的
src
属性由外域转向本地域,跨域数据即由iframe的window.name
从外域传递到本地域,巧妙地绕过了浏览器的跨域访问限制,但是又是安全操作。window.name属性的独特之处在于,name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的name值(2MB) -
postMessage跨域:它是HTML5 XMLHttpRequest Level 2中的API,可以跨域操作的window属性之一,主要解决:
1)页面和其打开的新窗口的数据传递
2)多窗口之间的消息传递
3)页面与嵌套的iframe消息传递
4)上述三个场景的跨域数据传递
JSONP和CORS的对比:
- 使用目的相同,但是CORS比JSONP强大
- JSONP只支持
GET
请求,CORS支持所有类型的HTTP请求 - JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据
在众多跨域的解决方案中,最常见的还是前三种解决方案