一、什么是跨域,为什么会出现跨域问题。
前面两篇文章cookie了解一下?,网络攻防(xss/csrf/xsrf)了解一下介绍了在网站及网站用户的安全问题。浏览器为隔离潜在的恶意文件,限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互,所以,是浏览器的基于安全考虑的同源策略导致的跨域。
同源(协议,域名,端口号三者均相同),只要有一样不同则为跨域。
eg:IE比较特殊。
- IE浏览器不将端口划在同源限制内,即:协议域名相同,只有端口不同,IE也认为是同源的。
- 授信范围(Trust Zones):两个相互之间高度互信的域名,如公司域名(corporate domains),不遵守同源策略的限制。
二、如何跨域(允许跨源访问)
(1).JSONP
在HTML标签中,一些标签比如:script,img这样的src 是不受同源限制的,可天然跨域。我们可以利用这一点来向后端请求数据。
思想: 动态创建script标签,将入参和回调拼在url后面,利用script标签的src去访问,成功后删除script标签
后端代码
// 处理成功失败返回格式的工具
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
前端代码
/**
* JSONP请求工具
* @param url 请求的地址
* @param data 请求的参数
* @returns {Promise}
*/
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)
})
特点:
- 只能get请求:请求方式有限制,安全性不高,容易被攻击,url有长度限制,请求的入参受限,具体各家浏览器不同
- 不能对请求头进行设置
- 需要后端配合
(2). iframe + form
基于jsonp不能发送post请求。可以考虑在新的iframe中使用from来提交数据
思想:动态创建iframe标签,结合form表单提交。设置form.target = ifrme.name。则form提交时会在该名称的框架内打开链接。但是form还是要追加到主文档中。在iframe的load事件中处理返回的事件。
后端接口代码
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
static async iframePost (ctx) {
let postData = ctx.request.body
console.log(postData)
ctx.body = successBody({postData: postData}, 'success')
}
}
module.exports = CrossDomain
前端代码
const requestPost = ({url, data}) => {
// 首先创建一个用来发送数据的iframe.
const iframe = document.createElement('iframe')
iframe.name = 'iframePost'
iframe.style.display = 'none'
document.body.appendChild(iframe)
const form = document.createElement('form')
const node = document.createElement('input')
// 注册iframe的load事件处理程序,如果你需要在响应返回时执行一些操作的话.
iframe.addEventListener('load', function () {
console.log('post success')
})
form.action = url
// 在指定的iframe中执行form
form.target = iframe.name
form.method = 'post'
for (let name in data) {
node.name = name
node.value = data[name].toString()
form.appendChild(node.cloneNode())
}
// 表单元素需要添加到主文档中.
form.style.display = 'none'
document.body.appendChild(form)
form.submit()
// 表单提交后,就可以删除这个表单,不影响下次的数据发送.
document.body.removeChild(form)
}
// 使用方式
requestPost({
url: 'http://localhost:9871/api/iframePost',
data: {
msg: 'helloIframePost'
}
})
特点:
- 需要后端配合
- 通过iframe的load事件,返回结果处理不是很清晰
(3). CORS
CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)跨域资源共享 CORS 详解需要浏览器和服务器同时支持
-
简单请求
后端接口配置// 处理成功失败返回格式的工具 const {successBody} = require('../utli') class CrossDomain { static async cors (ctx) { const query = ctx.request.query // *时cookie不会在http请求中带上 ctx.set('Access-Control-Allow-Origin', '*') ctx.cookies.set('tokenId', '2') ctx.body = successBody({msg: query.msg}, 'success') } } module.exports = CrossDomain
核心就是'Access-Control-Allow-Origin设置为 * 表示允许所有的远程访问该资源,所以前端就什么也不用配置,直接请求后台接口即可。但是当设置为*的时候http头是不会携带cookie的,所以,如果要携带cookie,则前后端都要设置一下
2.非简单请求
后端接口
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
static async cors (ctx) {
const query = ctx.request.query
// 如果需要http请求中带上cookie,需要前后端都设置credentials,且后端设置指定的origin
ctx.set('Access-Control-Allow-Origin', 'http://localhost:9099')
ctx.set('Access-Control-Allow-Credentials', true)
// 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)
// 这种情况下除了设置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')
ctx.cookies.set('tokenId', '2')
ctx.body = successBody({msg: query.msg}, 'success')
}
}
module.exports = CrossDomain
前端请求代码
fetch(`http://localhost:9871/api/cors?msg=helloCors`, {
// 需要带上cookie
credentials: 'include',
// 这里添加额外的headers来触发非简单请求
headers: {
't': 'extra headers'
}
}).then(res => {
console.log(res)
})
(4). 代理
想一下,如果我们请求的时候还是用前端的域名,然后有个东西帮我们把这个请求转发到真正的后端域名上,不就避免跨域了吗。
nginx代理
server{
# 监听9099端口
listen 9099;
# 域名是localhost
server_name localhost;
#凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
location ^~ /api {
proxy_pass http://localhost:9871;
}
}
前端开发时的dev-server代理
webpackConfig.devServer = {
host: '0.0.0.0', //加上这个配置才能让别人访问你的本地服务器
contentBase: './dist', //本地服务器所加载的页面所在的目录
port: 8888,
historyApiFallback: true, //不跳转
inline: true, //实时刷新
//代理到json-server的端口,模拟后端接口
proxy: {
'/api/*': {
target: 'http://localhost:8787',
secure: false,
changeOrigin: true,
pathRewrite: {
'^/api/': '/'
},
}
}
};
(5). 设置浏览器快捷方式的 --args --disable-web-security --user-data-dir
同源策略限制DOM查询下的 跨域方式
一、window.postMessage
postMessage是HTML5的一个新特性,专门用来解决跨域的方法,但是目前es6,es7不支持。postMessage方法允许来自不同源的脚本,采用异步的方式进行通信,可以实现跨文本档,多窗口,跨域消息传递。
- 跨域除了客户端和服务端请求的接口跨域,还有:
1)多窗口之间消息传递(newWin = window.open(..));
2)页面与嵌套的iframe消息传递
postMessage跨域不是解决客户端与服务端的跨域问题,而是专注解决,客户端中跨文档消息传送通信。
2.向目标窗口传送消息
postMessage(dataStr,origin)方法接受两个参数;dataStr是要传送的消息字符串,origin是目标窗口的源(协 议,域名,端口)。
-
目标窗口接收消息
监听message事件window.addEventListener('message', function(messageEvent) { var data = messageEvent.data; // messageEvent: {source, currentTarget, data} console.info('message from child:', data); }, false);
接收消息
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); } })
e事件对象有三个属性
- source:发送消息的窗口对象
- origin:发送消息窗口的源(协议+主机+端口号)
- data:顾名思义,是传递来的message
二、document.domain
这种方式只适合主域名相同,但子域名不同的iframe跨域。
比如主域名是crossdomain.com:9099,子域名是child.crossdomain.com:9099,这种情况下给两个页面指定一下document.domain即document.domain = crossdomain.com就可以访问各自的window对象了。
三、canvas操作图片的跨域问题
这个应该是一个比较冷门的跨域问题,张大神已经写过了我就不再班门弄斧了解决canvas图片getImageData,toDataURL跨域问题
参考 https://juejin.im/entry/5b4d4721f265da0f926b78c8