三种跨域方案(jsonp、cors、form+iframe)

您所在的位置:网站首页 解释什么是iframe 三种跨域方案(jsonp、cors、form+iframe)

三种跨域方案(jsonp、cors、form+iframe)

2024-01-28 09:04| 来源: 网络整理| 查看: 265

必备知识 同源策略(Same origin policy),浏览器的一个安全策略,现代浏览器大部分都实现了该策略。 同源指:域名、协议、端口相同。禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。 http协议的Content-Type字段:如果该字段在http request header(请求头)里,指“前端向后台传递的数据的类型”,后台框架可能会根据该字段,自动地对前端发来的内容进行解析;如果该字段在htttp response header(响应头)里,指“后台向前端传递的数据的类型”。

如果要跨域就要解决两个关键问题,一个是浏览器不让你发请求(实质上是浏览器根据预检请求得到的结果,不让你发正式的请求),另一个是浏览器不让你收请求。

如何绕开同源策略(如何跨域)? 1. jsonp跨域

JSONP(JSON with Padding,带填充的json),是由Bob Ippolito在2005年提出的一种跨域手段。jsonp利用script标签从前端向后台请求数据。前端获得响应后,会在该script标签内执行一个回调函数。所以,jsonp跨域的两个关键点,一个是标签,另一个是回调函数。

为什么是标签呢?因为一些HTML标签是不受同源策略限制的,如script、img,而且这些标签会在浏览器渲染时,向指定的URL发送请求。(这里要注意哦,别有用心的人会利用这个特性发起CSRF攻击,详见:「每日一题」CSRF 是什么?)

为什么不用img标签呢?因为实际场景,前端不仅要发、收请求,还要对请求的结果进行下一步的处理。script标签内的内容可以作为js代码被浏览器执行,因此script非常符合我们的需求。

jsonp的另一个关键点就是回调函数了。利用发起请求,返回内容是由后台生成的,因此在script标签内执行的代码只能是后台响应的数据。 这样一来…是不是有点尴尬,“后台大哥,我前端只能执行你发的数据,要不我等会代码写好了拷贝一份发你吧!”,后台大哥:“滚!”… 我们当然不能这样做,也不一定可行(作用域问题)。我们应该好好利用一个东西:函数!函数能把我们的业务代码囊括进去,一行代码便能执行数十行代码,还能利用闭包解决作用域的问题,简直太棒了!所以我们仅需要把回调函数告诉后台,让后台以函数的调用形式作为请求的响应内容返回就好了!至此,利用jsonp跨域的理论基础便建立起来了。

所以,利用jsonp发起一个跨域请求的具体步骤大致如下:

前端创建一个script标签,并设置该标签的src属性为我们期望请求api的url,设置type属性为"text/javascript"。将该script标签添加至dom树。该标签加入dom树后,便会自动向src指定的url发送请求。后台向前端响应的内容,应该是一段调用回调函数的代码,如:前端回调函数名(参数)。在前端声明一个回调函数,该函数在请求成功响应后执行。(注意,大部分情况都是要将该函数挂到全局对象上哦,因为script标签内的作用域是全局作用域)

jsonp跨域的一些缺点:

仅支持get请求。 1.1 jsonp跨域的原生实现: /* vue前端 */ let script = document.createElement("script"), // 创建一个script标签 jsonpCallbackName = "jsonpCallback", // 消除魔法字符串,统一后续的jsonp回调函数名称。 context = this; // 记录当前的上下文。博主的这段代码实际是在vue中编写的,具体代码可见文末的demo。这条赋值语句可以忽略。 script.type = "text/javascript"; // 使该标签请求获得的数据可以被浏览器执行。 script.src = `http://localhost:3000/api/testGet?anything=${this.form.anything}&callback=${jsonpCallbackName}`; // 建议传一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数。 document.head.appendChild(script); // 将该标签添加至dom树,加入dom树后该标签会触发向src的请求。 // script标签请求成功后的回调函数。 window[jsonpCallbackName] = function (res) { document.head.removeChild(script); delete window[jsonpCallbackName]; context.returnMsg = res; }; /* node后台 */ router.get('/testGet', (ctx) => { console.log(`后台testGet接口被触发`); console.log(`接收到数据:${ctx.request.query.anything}`); ctx.response.status = 200; // 后台返回的内容必须为模拟执行回调函数的代码,即:回调函数名(参数) ctx.body = `${ctx.request.query.callback}({"status":"success"})`; })

对于实现过程中的一些细节的解释:

为什么一定要给script标签设置type属性?

type属性定义script元素包含或src引用的脚本语言。属性的值为MIME类型; 支持的MIME类型包括text/javascript, text/ecmascript, application/javascript, 和application/ecmascript。如果没有定义这个属性,脚本会被视作JavaScript。如果MIME类型不是JavaScript类型(上述支持的类型),则该元素所包含的内容会被当作数据块而不会被浏览器执行。 (摘自mdn)

为什么要将回调函数挂载到window上? 因为script标签内的代码的执行环境是全局作用域,如果不将回调函数挂载到window上,script标签内的代码访问不到该回调函数。 为什么要在回调函数里删除该方法? 为了避免内存泄漏。function也是会占用一定内存的。

可能会遇到的报错:

Cross-Origin Read Blocking (CORB) blocked cross-origin response http://localhost:3000/api/testGet?name=test&callback=handleCallback with MIME type application/json. See https://www.chromestatus.com/feature/5629709824032768 for more details.

出现原因:因为CORB策略(详见参考资料1)。后台返回的内容可能满足了CORB的保护规则,触发了CORB,因此前端无法获取到响应信息。解决方案:博主是初学时不懂后台要返回一个方法的调用(如:前端回调函数名(参数)),而是返回了一段json数据才导致的这个错误。 1.2 jquery版本的jsonp跨域 let jsonpCallbackName = "jsonpCallback", context = this; window[jsonpCallbackName] = function (res) { delete window[jsonpCallbackName]; context.returnMsg = res; }; $.ajax({ url: "http://localhost:3000/api/testGet", type: "get", dataType: "jsonp", // 预期服务器返回的数据类型。当值为"jsonp"时,会在url中自动添加"callback=?",其中?会被自动替换为jsonpCallback字段设置的函数名。 jsonpCallback: jsonpCallbackName, // 回调函数的函数名。 data: { anything: this.form.anything, }, }); 2. iframe+form跨域

由于form表单在提交时不会出现跨域问题,因此可以利用form表单进行跨域。 ifame标签,主要用来避免页面刷新的问题,收到响应数据后直接将表单的数据显示到iframe标签中。 但是该方法的局限性比较大,后台传递回来的数据只在iframe中渲染,在iframe的父页面中无法获取。

/* 前端 */ let iframe = document.createElement("iframe"), // 首先创建一个用来发送数据的iframe. form = document.createElement("form"), node = document.createElement("input"), context = this, data = { anything: this.form.anything, }; // 设置并添加iframe至dom树 iframe.name = "iframePost"; iframe.style.display = "none"; iframe.src = "http://localhost:8080"; iframe.addEventListener("load", function (res) { context.returnMsg = res; console.log(res); }); document.body.appendChild(iframe); // 设置并添加form至iframe form.action = "http://localhost:3000/api/testPost"; form.target = iframe.name; // 在提交表单之后,在指定的iframe中显示响应信息 form.method = "post"; for (let prop in data) { node.name = prop; node.value = data[prop].toString(); form.appendChild(node.cloneNode()); } form.style.display = "none"; document.body.appendChild(form); form.submit(); // 发送form document.body.removeChild(form); // 表单提交后,就可以删除这个表单,不影响下次的数据发送. }; /* 后台 */ router.post('/testPost', (ctx) => { ctx.response.status = 200; ctx.body = { status: "success" } }) 3. CORS(Cross-origin resource sharing,跨域资源共享)

cors是一种规范,这个规范规定了一些能够进行跨域的情况。 如果我们需要根据cors规范跨域,绝大多数情况下仅需要在前后端设置一下http请求的header。

关于cors规范,有些内容你必须了解 1. 简单请求和非简单请求

cors规范将请求分为了简单请求和非简单请求。为什么要划分呢?因为有些http请求可能会对服务器数据产生副作用,划分请求便是为了阻止这些副作用。

这两个请求的最大区别,便是简单请求只需发送一个http请求,而非简单请求一共需要发送两个http请求。非简单请求首先发起一个预检请求,获知后台是否允许跨域请求,确认允许后才可以发起实际的HTTP请求。简单请求直接发送实际的HTTP请求。

满足以下所有条件即简单请求(***摘自mdn***)

使用下列方法之一 GETHEADPOST 除了被用户代理自动设置的首部字段(例如 Connection ,User-Agent)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为: AcceptAccept-LanguageContent-LanguageContent-Type (需要注意额外的限制)DPRDownlinkSave-DataViewport-WidthWidth Content-Type 的值仅限于下列三者之一:(牢记,容易出问题,我们常用的application/json并不包括在里面) text/plainmultipart/form-dataapplication/x-www-form-urlencoded 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。请求中没有使用 ReadableStream 对象。 cors跨域该如何设置?

前端:

如果需要携带认证信息(包括cookie),需要设置xhr.withCredentials = true。根据自己传送的数据类型,设置一下contentType,如xhr.setRequestHeader("Content-Type", "application/json");

后台:

Access-Control-Allow-Origin:设置允许访问资源的源,经常被设置为"*",注意如果携带了认证信息,该字段值不能设置为"*",需要设置为相应的uri。如果携带认证信息(包括cookie),设置Access-Control-Allow-Credentials的值为true。非简单请求一般情况下也将该字段设置为true。Access-Control-Allow-Headers:设置请求头中除一些标准的字段,额外允许携带的字段。比如Content-Type。多个字段用逗号隔开,如:'Content-Type, Content-Length, Authorization, Accept'Access-Control-Allow-Methods:设置前端可以使用哪些方法进行请求。可以设置为:'PUT, POST, GET, DELETE, OPTIONS' 设置了这几个字段,基本就能实现请求的跨域了,其他字段可以根据需要另行设置。 与cors跨域相关的字段(仅做总结) 请求头的相关字段: origin:发送请求的源的URI。Access-Control-Request-Method:仅用于预检请求。将实际请求所使用的 HTTP 方法告诉服务器。Access-Control-Request-Headers:仅用于预检请求。将实际请求所携带的首部字段告诉服务器。 响应头的相关字段: Access-Control-Allow-Origin,指定可以访问该资源的URI。“*”指任何人都可以访问;也可以是具体的uri,比如:https://developer.mozilla.org。 对于附带身份凭证的请求,或需要传递cookie的请求,该值不能使用"*" Access-Control-Allow-Credentials,表示是否可以将对请求的响应暴露给页面。 如果前端要把cookie传递至后台,则后台必须将响应头中该字段的值设为true Access-Control-Allow-Methods,表示前端可以使用哪些方法进行请求。Access-Control-Allow-Headers,表示请求头中除一些标准的字段,额外允许携带的字段。Access-Control-Expose-Headers,扩展前端使用XMLHttpRequest对象的getResponseHeader()方法所能获取到的响应头信息。getResponseHeader()默认只能获取最基本的响应头:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、PragmaAccess-Control-Max-Age,指定预检请求可以被缓存多少秒。 基于上述内容做的一个跨域模拟器

项目基于nodejs koa2和vue3.0实现(基本没用什么新特性…),这个模拟器可以用来:

观察跨域时的http报文…直接看源码,了解上述跨域方法是如何使用的。

嗯嗯…就这些了…这也是我第一次用这个koa框架和vue3.0…喜欢的话帮忙点个Star呗~ 欢迎pr~ 项目地址:https://github.com/Michael-Zhang-Xian-Sen/cross-domain-simulation

参考资料 30 分钟理解 CORB 是什么Jsonp 维基百科前端常见跨域解决方案(全)Remote JSON - JSONP不要再问我跨域的问题了 延伸阅读 w3c cors标准


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3