有做lol直播网站,家具东莞网站建设,笨笨网站建设专家,在线文字logo设计前些天发现了一个巨牛的人工智能学习网站#xff0c;通俗易懂#xff0c;风趣幽默#xff0c;忍不住分享一下给大家。点击跳转到教程。
跨域这两个字就像一块狗皮膏药一样黏在每一个前端开发者身上#xff0c;无论你在工作上或者面试中无可避免会遇到这个问题。为了应付面…前些天发现了一个巨牛的人工智能学习网站通俗易懂风趣幽默忍不住分享一下给大家。点击跳转到教程。
跨域这两个字就像一块狗皮膏药一样黏在每一个前端开发者身上无论你在工作上或者面试中无可避免会遇到这个问题。为了应付面试我每次都随便背几个方案也不知道为什么要这样干反正面完就可以扔了我想工作上也不会用到那么多乱七八糟的方案。到了真正工作开发环境有webpack-dev-server搞定上线了服务端的大佬们也会配好配了什么我不管反正不会跨域就是了。日子也就这么混过去了终于有一天我觉得不能再继续这样混下去了我一定要彻底搞懂这个东西于是就有了这篇文章。
要掌握跨域首先要知道为什么会有跨域这个问题出现
确实我们这种搬砖工人就是为了混口饭吃嘛好好的调个接口告诉我跨域了这种阻碍我们轻松搬砖的事情真恶心为什么会跨域是谁在搞事情为了找到这个问题的始作俑者请点击浏览器的同源策略。 这么官方的东西真难懂没关系至少你知道了因为浏览器的同源策略导致了跨域就是浏览器在搞事情。 所以浏览器为什么要搞事情就是不想给好日子我们过对于这样的质问浏览器甩锅道“同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。” 这么官方的话术真难懂没关系至少你知道了似乎这是个安全机制。 所以究竟为什么需要这样的安全机制这样的安全机制解决了什么问题别急让我们继续研究下去。
没有同源策略限制的两大危险场景
据我了解浏览器是从两个方面去做这个同源策略的一是针对接口的请求二是针对Dom的查询。试想一下没有这样的限制上述两种动作有什么危险。
没有同源策略限制的接口请求
有一个小小的东西叫cookie大家应该知道一般用来处理登录等场景目的是让服务端知道谁发出的这次请求。如果你请求了接口进行登录服务端验证通过后会在响应头加入Set-Cookie字段然后下次再发请求的时候浏览器会自动将cookie附加在HTTP请求的头字段Cookie中服务端就能知道这个用户已经登录过了。知道这个之后我们来看场景 1.你准备去清空你的购物车于是打开了买买买网站www.maimaimai.com然后登录成功一看购物车东西这么少不行还得买多点。 2.你在看有什么东西买的过程中你的好基友发给你一个链接www.nidongde.com一脸yin笑地跟你说“你懂的”你毫不犹豫打开了。 3.你饶有兴致地浏览着www.nidongde.com谁知这个网站暗地里做了些不可描述的事情由于没有同源策略的限制它向www.maimaimai.com发起了请求聪明的你一定想到上面的话“服务端验证通过后会在响应头加入Set-Cookie字段然后下次再发请求的时候浏览器会自动将cookie附加在HTTP请求的头字段Cookie中”这样一来这个不法网站就相当于登录了你的账号可以为所欲为了如果这不是一个买买买账号而是你的银行账号那…… 这就是传说中的CSRF攻击浅谈CSRF攻击方式。 看了这波CSRF攻击我在想即使有了同源策略限制但cookie是明文的还不是一样能拿下来。于是我看了一些cookie相关的文章聊一聊 cookie、Cookie/Session的机制与安全知道了服务端可以设置httpOnly使得前端无法操作cookie如果没有这样的设置像XSS攻击就可以去获取到cookieWeb安全测试之XSS设置secure则保证在https的加密通信中传输以防截获。
没有同源策略限制的Dom查询
1.有一天你刚睡醒收到一封邮件说是你的银行账号有风险赶紧点进www.yinghang.com改密码。你吓尿了赶紧点进去还是熟悉的银行登录界面你果断输入你的账号密码登录进去看看钱有没有少了。 2.睡眼朦胧的你没看清楚平时访问的银行网站是www.yinhang.com而现在访问的是www.yinghang.com这个钓鱼网站做了什么呢
// HTML
iframe nameyinhang srcwww.yinhang.com/iframe
// JS
// 由于没有同源策略的限制钓鱼网站可以直接拿到别的网站的Dom
const iframe window.frames[yinhang]
const node iframe.document.getElementById(你输入账号密码的Input)
console.log(拿到了这个${node}我还拿不到你刚刚输入的账号密码吗)
由此我们知道同源策略确实能规避一些危险不是说有了同源策略就安全只是说同源策略是一种浏览器最基本的安全机制毕竟能提高一点攻击的成本。其实没有刺不穿的盾只是攻击的成本和攻击成功后获得的利益成不成正比。
跨域正确的打开方式
经过对同源策略的了解我们应该要消除对浏览器的误解同源策略是浏览器做的一件好事是用来防御来自邪门歪道的攻击但总不能为了不让坏人进门而把全部人都拒之门外吧。没错我们这种正人君子只要打开方式正确就应该可以跨域。 下面将一个个演示正确打开方式但在此之前有些准备工作要做。为了本地演示跨域我们需要 1.随便跑起一份前端代码以下前端是随便跑起来的vue地址是http://localhost:9099。 2.随便跑起一份后端代码以下后端是随便跑起来的node koa2地址是http://localhost:9971。
同源策略限制下接口请求的正确打开方式
1.JSONP 在HTML标签里一些标签比如script、img这样的获取资源的标签是没有跨域限制的利用这一点我们可以这样干
后端写个小接口
// 处理成功失败返回格式的工具
const {successBody} require(../utli)
class CrossDomain {static async jsonp (ctx) {// 前端传过来的参数const query ctx.request.query// 设置一个cookiesctx.cookies.set(tokenId, 1)// query.cb是前后端约定的方法名字其实就是后端返回一个直接执行的方法给前端由于前端是用script标签发起的请求所以返回了这个方法后相当于立马执行并且把要返回的数据放在方法的参数里。ctx.body ${query.cb}(${JSON.stringify(successBody({msg: query.msg}, success))})}
}
module.exports CrossDomain简单版前端
!DOCTYPE html
htmlheadmeta charsetutf-8/headbodyscript typetext/javascript// 后端返回直接执行的方法相当于执行这个方法由于后端把返回的数据放在方法的参数里所以这里能拿到res。window.jsonpCb function (res) {console.log(res)}/scriptscript srchttp://localhost:9871/api/jsonp?msghelloJsonpcbjsonpCb typetext/javascript/script/body
/html简单封装一下前端这个套路
/*** JSONP请求工具* param url 请求的地址* param data 请求的参数* returns {Promiseany}*/
const request ({url, data}) {return new Promise((resolve, reject) {// 处理传参成xxyyaabb的形式const handleData (data) {const keys Object.keys(data)const keysLen keys.lengthreturn 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.jsonpCbresolve(res)}script.src ${url}?${handleData(data)}cbjsonpCbdocument.body.appendChild(script)})
}
// 使用方式
request({url: http://localhost:9871/api/jsonp,data: {// 传参msg: helloJsonp}
}).then(res {console.log(res)
})
2.空iframe加form 细心的朋友可能发现JSONP只能发GET请求因为本质上script加载资源就是GET那么如果要发POST请求怎么办呢
后端写个小接口
// 处理成功失败返回格式的工具
const {successBody} require(../utli)
class CrossDomain {static async iframePost (ctx) {let postData ctx.request.bodyconsole.log(postData)ctx.body successBody({postData: postData}, success)}
}
module.exports CrossDomain
前端
const requestPost ({url, data}) {// 首先创建一个用来发送数据的iframe.const iframe document.createElement(iframe)iframe.name iframePostiframe.style.display nonedocument.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中执行formform.target iframe.nameform.method postfor (let name in data) {node.name namenode.value data[name].toString()form.appendChild(node.cloneNode())}// 表单元素需要添加到主文档中.form.style.display nonedocument.body.appendChild(form)form.submit()// 表单提交后,就可以删除这个表单,不影响下次的数据发送.document.body.removeChild(form)
}
// 使用方式
requestPost({url: http://localhost:9871/api/iframePost,data: {msg: helloIframePost}
})
3.CORS
CORS是一个W3C标准全称是跨域资源共享Cross-origin resource sharing跨域资源共享 CORS 详解。看名字就知道这是处理跨域问题的标准做法。CORS有两种请求简单请求和非简单请求。
这里引用上面链接阮一峰老师的文章说明一下简单请求和非简单请求。 浏览器将CORS请求分成两类简单请求simple request和非简单请求not-so-simple request。只要同时满足以下两大条件就属于简单请求。 1) 请求方法是以下三种方法之一
HEADGETPOST
2HTTP的头信息不超出以下几种字段
AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
1.简单请求 后端
// 处理成功失败返回格式的工具
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前端什么也不用干就是正常发请求就可以如果需要带cookie的话前后端都要设置一下下面那个非简单请求例子会看到。
fetch(http://localhost:9871/api/cors?msghelloCors).then(res {console.log(res)
})
2.非简单请求 非简单请求会发出一次预检测请求返回码是204预检测通过才会真正发出请求这才返回200。这里通过前端发请求的时候增加一个额外的headers来触发非简单请求。 后端
// 处理成功失败返回格式的工具
const {successBody} require(../utli)
class CrossDomain {static async cors (ctx) {const query ctx.request.query// 如果需要http请求中带上cookie需要前后端都设置credentials且后端设置指定的originctx.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-Headersctx.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
一个接口就要写这么多代码如果想所有接口都统一处理有什么更优雅的方式呢见下面的koa2-cors。
const path require(path)
const Koa require(koa)
const koaStatic require(koa-static)
const bodyParser require(koa-bodyparser)
const router require(./router)
const cors require(koa2-cors)
const app new Koa()
const port 9871
app.use(bodyParser())
// 处理静态资源 这里是前端build好之后的目录
app.use(koaStatic(path.resolve(__dirname, ../dist)
))
// 处理cors
app.use(cors({origin: function (ctx) {return http://localhost:9099},credentials: true,allowMethods: [GET, POST, DELETE],allowHeaders: [t, Content-Type]
}))
// 路由
app.use(router.routes()).use(router.allowedMethods())
// 监听端口
app.listen(9871)
console.log([demo] start-quick is starting at port ${port})前端
fetch(http://localhost:9871/api/cors?msghelloCors, {// 需要带上cookiecredentials: include,// 这里添加额外的headers来触发非简单请求headers: {t: extra headers}
}).then(res {console.log(res)
})
4.代理 想一下如果我们请求的时候还是用前端的域名然后有个东西帮我们把这个请求转发到真正的后端域名上不就避免跨域了吗这时候Nginx出场了。 Nginx配置
server{# 监听9099端口listen 9099;# 域名是localhostserver_name localhost;#凡是localhost:9099/api这个样子的都转发到真正的服务端地址http://localhost:9871 location ^~ /api {proxy_pass http://localhost:9871;}
}
前端就不用干什么事情了除了写接口也没后端什么事情了
// 请求的时候直接用回前端这边的域名http://localhost:9099这就不会跨域然后Nginx监听到凡是localhost:9099/api这个样子的都转发到真正的服务端地址http://localhost:9871
fetch(http://localhost:9099/api/iframePost, {method: POST,headers: {Accept: application/json,Content-Type: application/json},body: JSON.stringify({msg: helloIframePost})
})
Nginx转发的方式似乎很方便但这种使用也是看场景的如果后端接口是一个公共的API比如一些公共服务获取天气什么的前端调用的时候总不能让运维去配置一下Nginx如果兼容性没问题IE 10或者以上CROS才是更通用的做法吧。
同源策略限制下Dom查询的正确打开方式
1.postMessage window.postMessage() 是HTML5的一个接口专注实现不同窗口不同页面的跨域通讯。 为了演示方便我们将hosts改一下127.0.0.1 crossDomain.com现在访问域名crossDomain.com就等于访问127.0.0.1。
这里是http://localhost:9099/#/crossDomain发消息方
templatedivbutton clickpostMessage给http://crossDomain.com:9099发消息/buttoniframe namecrossDomainIframe srchttp://crossdomain.com:9099/iframe/div
/templatescript
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接收消息方
templatediv我是http://crossdomain.com:9099/div
/templatescript
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可以作为targetOrigine.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跨域问题
最后
希望看完这篇文章之后再有人问跨域的问题你可以嘴角微微上扬冷笑一声“不要再问我跨域的问题了。” 扬长而去。 转自https://segmentfault.com/a/1190000015597029