为什么会出现跨域的问题
在浏览器中有一个同源策略, 也是浏览器的安全策略, 保障资源间的安全访问,我们访问地址的URL: 协议://域名:端口/资源路径?查询字符串#hash,也就是只有当协议,域名,端口完全一致的时候 才能访问,只要有一个不同,就会出现跨域;
解决跨域的方法:
- (一)CORS跨域:
原理: 通过新版XMLHttpRequest(ajax20.)的特性
实现方式:
在服务器端设置"Access-Control-Allow-Origin"的值,解除跨域限制
CORS非常有用,可以共享许多内容,不过这里存在风险。因为它完全是一个盲目的协议,只是通过HTTP头来控制。
它CORS的风险包括:
1、HTTP头只能说明请求来自一个特定的域,但是并不能保证这个事实。因为HTTP头可以被伪造。
所以未经身份验证的跨域请求应该永远不会被信任。如果一些重要的功能需要暴露或者返回敏感信息,应该需要验证Session ID。
2、第三方有可能被入侵
举一个场景,FriendFeed通过跨域请求访问Twitter,FriendFeed请求tweets、提交tweets并且执行一些用户操作,Twitter提供响应。两者都互相相信对方,所以FriendFeed并不验证获取数据的有效性,Twitter也针对Twitter开放了大部分的功能。
但是当如果Twitter被入侵后:
FriendFeed总是从Twitter获取数据,没有经过编码或者验证就在页面上显示这些信息。但是Twitter被入侵后,这些数据就可能是有害的。
或者FriendFeed被入侵时:
Twitter响应FriendFeed的请求,例如发表Tweets、更换用户名甚至删除账户。当FriendFeed被入侵后,攻击者可以利用这些请求来篡改用户数据。
所以对于请求方来说验证接收的数据有效性和服务方仅暴露最少最必须的功能是非常重要的。
3、恶意跨域请求
即便页面只允许来自某个信任网站的请求,但是它也会收到大量来自其他域的跨域请求。.这些请求有时可能会被用于执行应用层面的DDOS攻击,并不应该被应用来处理。
例如,考虑一个搜索页面。当通过’%'参数请求时搜索服务器会返回所有的记录,这可能是一个计算繁重的要求。要击垮这个网站,攻击者可以利用XSS漏洞将Javascript脚本注入某个公共论坛中,当用户访问这个论坛时,使用它的浏览器重复执行这个到服务器的搜索请求。或者即使不采用跨域请求,使用一个目标地址包含请求参数的图像元素也可以达到同样的目的。如果可能的话,攻击者甚至可以创建一个WebWorker执行这种攻击。这会消耗服务器大量的资源。
有效的解决办法是通过多种条件屏蔽掉非法的请求,例如HTTP头、参数等。
4、内部信息泄漏
假定一个内部站点开启了CORS,如果内部网络的用户访问了恶意网站,恶意网站可以通过COR(跨域请求)来获取到内部站点的内容。
5、针对用户的攻击 - (二)JSONP跨域::
实现原理: 主要通过script标签的src属性访问资源不受浏览器同源策略的限制;
实现方式:
服务器端: 将需要跨域返回的数据包装作为参数传递给一个能够在JS脚本中执行的函数
前端:
1、创建script标签
2、设置src属性为服务器请求的地址
3、将script标签添加到body
4、执行回调函数 – 创建全局回调函数
5、删除script标签
el.onClick = function () {
var val = '参数'
var st = document.createElement("script"); // 创建script标签
st.src = 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd='+ val + '&cb=call' // call就是回调函数名
document.body.appendChild(st); // 将script标签添加到body中,就会执行全局的回调函数,所以直接在下面删除就可以了
// 删除script
st.parentNode.removeChild(st)
}
function call(data) { // 创建全局回调函数
console.log(data) // data就是返回的数据
}
在vue中使用JSONP跨域访问数据,并且使用promise封装:
- 安装jsonp, npm install -S jsonp
- 在公共资源目录下(assets)创建一个js文件夹,在js文件夹中创建jsonp.js文件:
- jsonp.js
import jsonp from 'jsonp'
const parseParam = (data) => {
// 这个函数要把一个对象换成这样的形式 'key=1&key1=2'的这种形式
let params = []
Object.keys(data).forEach((item) => {
params.push([item, encodeURIComponent(data[item])])
})
return params.map(item => item.join('=')).join('&')
}
export default (url, urldata, options) => {
/**
* 这里的url: 就是服务器地址
* data: 就是请求地址的时候携带的参数
* options: 是jsonp这个包的第二个参数,是一个对象,取值看下面
* */
url += (url.indexOf('?') < 0 ? '?' : '&') + parseParam(urldata)
return new Promise((resolve, reject) => {
/***
* url是服务器地址,也包括参数
* options则是jsonp这个包当中的第二个参数
* param (String) 用于指定回调的查询字符串参数的名称 (默认 'callback')
* timeout (Number) 发出超时错误后多长时间 (默认 : 60000)
* prefix (String) 处理JSONP响应的全局回调函数的前缀 (默认 '__jp')
* name (String) 处理JSONP响应的全局回调函数的名称 (默认: prefix + incremented counter)
* jsonp的第三个参数是一个回调函数,
* 这个回调函数中有两个参数(err, data), 当是err的时候,将取消正在进行的jsonp请求, data这是请求成功时候的数据
*
* */
jsonp(url, options, (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
然后在请求数据的地方引入下面的代码,我的文件目录是在 api/xxx.js
import axios from 'axios' // 可以直接在axios文件下面引入,因为这里可能还有通过axios请求的方法
import jsonp from '@/assets/js/jsonp.js'
export const getBestData = (page = 1, psize = 20) => {
const url = '服务器地址'
const params = { // 这个是上传服务器的参数
name: '参数'
}
return jsonp(url, params, {
param: 'callback', // 制定回调函数名
}).then(res => {
if (res.code === '200') {
return res
}
throw new Error('没有成功获取到数据!') // 注意在这里如果状态码不等于200, 则手动抛出错误
}).catch(err => { // 那在catch中可以捕获到所有的错误, 包括手动抛出的,如果出现错误,就报错
if (err) {
console.log(err)
}
}).then(res => { // 这个then呢就是在上面的then成功, res就是上面return的数据,所以只要走到这个then的时候,那肯定就会有数据的
return new Promise(resolve => { // 当有数据的时候,就返回一个promise对象这样写的主要原因是因为setTimeout是一个异步函数
resolve(res)
})
// return res // 如果不要loading效果,直接写res也可以
})
}
(三)postMessage
实现思路: 主要通过ifarme标签加载跨域资源不受浏览器同源策略限制,下面新建两个html文件,a.html 、b.html
a.html代码
<body>
<h1>这是a.html</h1>
<!-- 在a页面中嵌套b页面,就可以通过iframe -->
<iframe src="http://localhost:4000/b.html" frameborder="0" id="frame"></iframe>
<script>
let frame = document.getElementById('frame');
frame.onload = function () {
// 获取frame标签中的window, 在a页面给b页面发送消息
frame.contentWindow.postMessage('在a页面给b页面发送消息', 'http://localhost:4000');
// 获取b页面的消息
window.onmessage = function (e) {
console.log(e)
console.log(e.data)
}
}
</script>
</body>
b.html代码
<body>
<h1>这是b.html</h1>
<script>
// 在b页面接收a页面发送的消息
window.onmessage = function (e) {
// e.data就是a页面发送过来的消息
console.log(e.data)
// 如果b页面也要给a页面返回消息,那就要获取到a页面的window这个对象
e.source.postMessage('b页面返回给页面的消息', e.origin)
}
</script>
</body>
(四)window.name
- 业务要求:
- a页面和b页面实在同域: http://localhost:3000
- c页面和a、b不同域: http://localhost:4000
- 要在a页面获取c页面的数据
- 实现方式: a先引用c,c把数据放到window.name上, 然后在把a引用的地址改到b,现在新建三个html文件,分别是a.html, b.html, c.html
a.html 代码:
<body>
<iframe src="http://localhost:4000/c.html" frameborder="0" id="frame"></iframe>
<script>
let frame = document.getElementById('frame');
let first = true;
frame.onload = function () {
if(first) { // 当frame的地址改变的时候,那iframe标签就会从新出发onload事件
frame.src = 'http://localhost:3000/b.html';
first = false
} else { // 所以当第二次加载的时候,就走else, 主要利用了window.name,当域名发生变化的时候,window.name还是存在的
console.log(frame.contentWindow.name)
}
}
</script>
</body>
c.html代码:
<body>
<script>
window.name = '你好,我是c页面的数据'
</script>
</body>
那b.html就是空的html文件,b就相当于是个跑龙套的,但是必须保证b.html和a.html是同域的