跨域+如何解决
https://segmentfault.com/a/1190000015597029
浏览器的同源策略导致了跨域。
“同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。”
浏览器是从两个方面去做这个同源策略的,一是针对接口的请求,二是针对Dom的查询。
没有同源策略限制的接口请求
服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,浏览器会自动将cookie附加在HTTP请求的头字段Cookie中。
可能导致CSRF攻击。
没有同源策略限制的Dom查询
// HTML
<iframe name="yinhang" src="www.yinhang.com"></iframe>
// JS
// 由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom
const iframe = window.frames['yinhang']
const node = iframe.document.getElementById('你输入账号密码的Input')
console.log(`拿到了这个${node},我还拿不到你刚刚输入的账号密码吗`)
钓鱼网站窃取信息。
同源策略限制下接口请求的正确打开方式
1.JSONP
在HTML标签里,一些标签比如script、img这样的获取资源的标签是没有跨域限制的,只能使用GET方法。利用这一点,我们可以这样干:
后端接口:
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
static async jsonp (ctx) {
// 前端传过来的参数
const query = ctx.request.query
// 设置一个cookies
ctx.cookies.set('tokenId', '1')
// query.cb是前后端约定的方法名字,其实就是后端返回一个直接执行的方法给前端,由于前端是用script标签发起的请求,所以返回了这个方法后相当于立马执行,并且把要返回的数据放在方法的参数里。
ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}, 'success'))})`
}
}
module.exports = CrossDomain
简单版前端:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type='text/javascript'>
// 后端返回直接执行的方法,相当于执行这个方法,由于后端把返回的数据放在**方法的参数**里,所以这里能拿到res。
window.jsonpCb = function (res) {
console.log(res)
}
</script>
<script src='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb' type='text/javascript'></script>
</body>
</html>
简单封装一下前端这个套路
/**
* JSONP请求工具
* @param url 请求的地址
* @param data 请求的参数
* @returns {Promise<any>}
*/
const request = ({url, data}) => {
return new Promise((resolve, reject) => {
// 处理传参成xx=yy&aa=bb的形式
const handleData = (data) => {
const keys = Object.keys(data)
const keysLen = keys.length
return keys.reduce((pre, cur, index) => {
const value = data[cur]
const flag = index !== keysLen - 1 ? '&' : ''
return `${pre}${cur}=${value}${flag}`
}, '')
}
// 动态创建script标签
const script = document.createElement('script')
// 接口返回的数据获取
window.jsonpCb = (res) => {
document.body.removeChild(script)
delete window.jsonpCb
resolve(res)
}
script.src = `${url}?${handleData(data)}&cb=jsonpCb`
document.body.appendChild(script)
})
}
// 使用方式
request({
url: 'http://localhost:9871/api/jsonp',
data: {
// 传参
msg: 'helloJsonp'
}
}).then(res => {
console.log(res)
})
2,空iframe加form
3,CORS是一个W3C标准,全称是**“跨域资源共享**”(Cross-origin resource sharing)跨域资源共享 CORS 详解。看名字就知道这是处理跨域问题的标准做法。CORS有两种请求,简单请求和非简单请求。
只要同时满足以下两大条件,就属于简单请求。
(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
// *时cookie不会在http请求中带上
ctx.set('Access-Control-Allow-Origin', '*')
2.非简单请求
非简单请求会发出一次预检测请求,返回码是204,预检测通过才会真正发出请求,这才返回200。这里通过前端发请求的时候增加一个额外的headers来触发非简单请求。
// 如果需要http请求中带上cookie,需要前后端都设置credentials,且后端设置指定的origin
ctx.set('Access-Control-Allow-Origin', 'http://localhost:9099')
ctx.set('Access-Control-Allow-Credentials', true)
// 这种情况下除了设置origin,还需要设置Access-Control-Request-Method以及Access-Control-Request-Headers
ctx.set('Access-Control-Request-Method', 'PUT,POST,GET,DELETE,OPTIONS')
ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, t')
4.代理
想一下,如果我们请求的时候还是用前端的域名,然后有个东西帮我们把这个请求转发到真正的后端域名上,不就避免跨域了吗?这时候,Nginx出场了。
Nginx配置
同源策略限制下Dom查询的正确打开方式
1.postMessage
window.postMessage() 是HTML5的一个接口,专注实现不同窗口不同页面的跨域通讯。
为了演示方便,我们将hosts改一下:
127.0.0.1 crossDomain.com,
现在访问域名crossDomain.com就等于访问127.0.0.1。
这里是http://localhost:9099/#/crossDomain,发消息方
<template>
<div>
<button @click="postMessage">给http://crossDomain.com:9099发消息</button>
<iframe name="crossDomainIframe" src="http://crossdomain.com:9099"></iframe>
</div>
</template>
<script>
export default {
mounted () {
window.addEventListener('message', (e) => {
// 这里一定要对来源做校验
if (e.origin === 'http://crossdomain.com:9099') {
// 来自http://crossdomain.com:9099的结果回复
console.log(e.data)
}
})
},
methods: {
// 向http://crossdomain.com:9099发消息
postMessage () {
const iframe = window.frames['crossDomainIframe']
iframe.postMessage('我是[http://localhost:9099], 麻烦你查一下你那边有没有id为app的Dom', 'http://crossdomain.com:9099')
}
}
}
</script>
这里是http://crossdomain.com:9099,接收消息方。
<template>
<div>
我是http://crossdomain.com:9099
</div>
</template>
<script>
export default {
mounted () {
window.addEventListener('message', (e) => {
// 这里一定要对来源做校验
if (e.origin === 'http://localhost:9099') {
// http://localhost:9099发来的信息
console.log(e.data)
// e.source可以是回信的对象,其实就是http://localhost:9099窗口对象(window)的引用
// e.origin可以作为targetOrigin
e.source.postMessage(`我是[http://crossdomain.com:9099],我知道了兄弟,这就是你想知道的结果:${document.getElementById('app') ? '有id为app的Dom' : '没有id为app的Dom'}`, e.origin);
}
})
}
}
</script>
2.document.domain
这种方式只适合主域名相同,但子域名不同的iframe跨域。
比如主域名是http://crossdomain.com:9099,子域名是http://child.crossdomain.com:9099,这种情况下给两个页面指定一下document.domain即document.domain = crossdomain.com就可以访问各自的window对象了。
3.canvas操作图片的跨域问题
这个应该是一个比较冷门的跨域问题,张大神已经写过了我就不再班门弄斧了。
解决canvas图片getImageData,toDataURL跨域问题