9种跨域方式实现原理

2023-05-27 0 730

全文:当协定、子搜索引擎、主搜索引擎、freenode中任一两个不全然相同时,都算是全然相同域。全然相同域间互相允诺天然资源,即使是“布吕马”。

责任编辑撷取自宏碁云街道社区《八种布吕马形式同时实现基本上原理咋回事》,译者:小弟博纳县

一、甚么是布吕马?

1.甚么是相混思路或其管制文本?

相混思路是一种签订合同,它是应用程序最核心理念也最基本上的安全可靠机能,假如缺乏了相混思路,应用程序很难受XSS、CSRF等反击。简而言之相混是指”协定+搜索引擎+路由器”二者全然相同,即使两个全然相同的搜索引擎对准同两个ip门牌号,也非相混。

相混思路管制文本有:

Cookie、LocalStorage、IndexedDB 等储存性文本DOM 结点AJAX 允诺推送后,结论被应用程序截击了

但是有四个条码是容许布吕马加载天然资源:

<img src=XXX><link href=XXX><script src=XXX>

2.常用布吕马情景

当协定、子搜索引擎、主搜索引擎、freenode中任一两个不全然相同时,都算是全然相同域。全然相同域间互相允诺天然资源,即使是“布吕马”。

特别表明三点:

第三:假如是协定和路由器导致的布吕马难题“后台”是束手无策的。

第三:在布吕马难题上,实际上是通过“URL的第三部”来辨识而不会根据搜索引擎相关联的IP门牌号与否全然相同来推论。“URL的第三部”能认知为“协定, 搜索引擎和路由器必须相匹配”。

这里你也许有位疑点:允诺布吕马了,那么允诺究竟接到去没?

布吕马并不是允诺发不进来,允诺能接到去,服务器端能接到允诺并恒定回到结论,只是结论被应用程序截击了。你可能会取捷伊文本,所以能发动布吕马允诺。同时也表明了布吕马并不能全然制止 CSRF,因为允诺即便是接到去了。

二、布吕马软件系统

1.jsonp

1) JSONP基本上原理

借助 <script> 条码没布吕马管制的安全可靠漏洞,网

2) JSONP和AJAX对比

的形式。但AJAX属于相混思路,JSONP属于非相混思路(布吕马允诺)

3) JSONP优缺点

JSONP优点是简单兼容性好,可用于解决主流应用程序的布吕马数据访问的难题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS反击。

4) JSONP的同时实现流程

pt>条码,把那个布吕马的API数据接口门牌号,赋值给script的src,还要在这个门牌号中向服务器传递该函数名(能通过问号传参:?callback=show)。服务器接接到允诺后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成两个字符串,例如:传递进去的函数名是show,它准备好的数据是show(我不爱你)。最后服务器把准备的数据通过HTTP协定回到给客户端,客户端再调用执行之前声明的回调函数(show),对回到的数据进行操作。

在开发中可能会遇到多个 JSONP 允诺的回调函数名是全然相同的,这时候就需要自己封装两个 JSONP函数。

// index.html function jsonp({ url, params, callback }) { return new Promise((resolve, reject) => { let script = document.createElement(script) window[callback] = function(data) { resolve(data) document.body.removeChild(script) } params = { …params, callback } // wd=b&callback=show let arrs = [] for (let key in params) { arrs.push(`${key}=${params[key]}`) } script.src = `${url}?${arrs.join(&)}` document.body.appendChild(script) }) } jsonp({ url: http://localhost:3000/say, params: { wd: Iloveyou }, callback: show }).then(data => { console.log(data) })

上面这段代码相当于向http://localhost:3000/say?wd=Iloveyou&callback=show这个门牌号允诺数据,然后后台回到show(我不爱你),最后会运行show()这个函数,打印出我不爱你

// server.js let express = require(express) let app = express() app.get(/say, function(req, res) { let { wd, callback } = req.query console.log(wd) // Iloveyou console.log(callback) // show res.end(`${callback}(我不爱你)`) }) app.listen(3000)

5) jQuery的jsonp形式

JSONP都是GET和异步允诺的,不存在其他的允诺形式和同步允诺,且jQuery默认就会给JSONP的允诺清除缓存。

$.ajax({ url:”http://crossdomain.com/jsonServerResponse”, dataType:”jsonp”, type:”get”,//能省略 jsonpCallback:”show”,//->自定义传递给服务器的函数名,而不是使用jQuery自动生成的,可省略 jsonp:”callback”,//->把传递函数名的那个形参callback,可省略 success:function (data){ console.log(data);} });

2.cors

CORS 需要应用程序和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来同时实现。

应用程序会自动进行 CORS 通信,同时实现 CORS 通信的关键是后端。只要后端同时实现了 CORS,就同时实现了布吕马。

服务器端设置 Access-Control-Allow-Origin 就能开启 CORS。 该属性表示哪些搜索引擎能访问天然资源,假如设置通配符则表示所有网站都能访问天然资源。

虽然设置 CORS 和前端没甚么关系,但是通过这种形式解决布吕马难题的话,会在推送允诺时出现两种情况,分别为简单允诺和复杂允诺。

1) 简单允诺

只要同时满足以下两大条件,就属于简单允诺

条件1:使用下列方法之一:

GETHEADPOST

条件2:Content-Type 的值仅限于下列二者之一:

text/plainmultipart/form-dataapplication/x-www-form-urlencoded

允诺中的任一 XMLHttpRequestUpload 对象均没注册任何事件监听器; XMLHttpRequestUpload 对象能使用 XMLHttpRequest.upload 属性访问。

2) 复杂允诺

不符合以上条件的允诺就肯定是复杂允诺了。

复杂允诺的CORS允诺,会在正式通信之前,增加一次HTTP查询允诺,称为”预检”允诺,该允诺是 option 方法的,通过该允诺来知道服务器端与否容许布吕马允诺。

我们用PUT向后台允诺时,属于复杂允诺,后台需做如下配置:

// 容许哪个方法访问我 res.setHeader(Access-Control-Allow-Methods, PUT) // 预检的存活时间 res.setHeader(Access-Control-Max-Age, 6) // OPTIONS允诺不做任何处理 if (req.method === OPTIONS) { res.end() } // 定义后台回到的文本 app.put(/getData, function(req, res) { console.log(req.headers) res.end(我不爱你) })

接下来我们看下两个完整复杂允诺的例子,并且介绍下CORS允诺相关的字段

// index.html let xhr = new XMLHttpRequest() document.cookie = name=xiamen // cookie不能布吕马 xhr.withCredentials = true // 前端设置与否带cookie xhr.open(PUT, http://localhost:4000/getData, true) xhr.setRequestHeader(name, xiamen) xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { console.log(xhr.response) //得到响应头,后台需设置Access-Control-Expose-Headers console.log(xhr.getResponseHeader(name)) } } } xhr.send() //server1.js let express = require(express); let app = express(); app.use(express.static(__dirname)); app.listen(3000); //server2.js let express = require(express) let app = express() let whitList = [http://localhost:3000] //设置白名单 app.use(function(req, res, next) { let origin = req.headers.origin if (whitList.includes(origin)) { // 设置哪个源能访问我 res.setHeader(Access-Control-Allow-Origin, origin) // 容许携带哪个头访问我 res.setHeader(Access-Control-Allow-Headers, name) // 容许哪个方法访问我 res.setHeader(Access-Control-Allow-Methods, PUT) // 容许携带cookie res.setHeader(Access-Control-Allow-Credentials, true) // 预检的存活时间 res.setHeader(Access-Control-Max-Age, 6) // 容许回到的头 res.setHeader(Access-Control-Expose-Headers, name) if (req.method === OPTIONS) { res.end() // OPTIONS允诺不做任何处理 } } next() }) app.put(/getData, function(req, res) { console.log(req.headers) res.setHeader(name, jw) //回到两个响应头,后台需设置 res.end(我不爱你) }) app.get(/getData, function(req, res) { console.log(req.headers) res.end(我不爱你) }) app.use(express.static(__dirname)) app.listen(4000)

上述代码由http://localhost:3000/index.html向http://localhost:4000/布吕马允诺,正如我们上面所说的,后端是同时实现 CORS 通信的关键。

3.postMessage

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多能布吕马操作的window属性之一,它可用于解决以下方面的难题:

页面和其打开的新窗口的数据传递多窗口间消息传递页面与嵌套的iframe消息传递上面四个情景的布吕马数据传递

postMessage()方法容许来自不相混的脚本采用异步形式进行有限的通信,能同时实现跨文本档、多窗口、布吕马消息传递。

otherWindow.postMessage(message, targetOrigin, [transfer]);message: 将要推送到其他 window的数据。targetOrigin:通过窗口的origin属性来指定哪些窗口能接接到消息事件,其值能是字符串”*”(表示无管制)或者两个URI。在推送消息的时候,假如目标窗口的协定、主机门牌号或路由器这二者的任一一项不相匹配targetOrigin提供的值,那么消息就不会被推送;只有二者全然相匹配,消息才会被推送。transfer(可选):是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而推送一方将不再保有所有权。

接下来我们看个例子: http://localhost:3000/a.html页面向http://localhost:4000/b.html传递“我爱你”,然后后者传回”我不爱你”。

// a.html <iframe src=”http://localhost:4000/b.html” frameborder=”0″ id=”frame” onload=”load()”></iframe> //等它读取完触发两个事件 //内嵌在http://localhost:3000/a.html <script> function load() { let frame = document.getElementById(frame) frame.contentWindow.postMessage(我爱你, http://localhost:4000) //推送数据 window.onmessage = function(e) { //接受回到数据 console.log(e.data) //我不爱你 } } </script> // b.html window.onmessage = function(e) { console.log(e.data) //我爱你 e.source.postMessage(我不爱你, e.origin) }

4.websocket

Websocket是HTML5的两个持久化的协定,它同时实现了应用程序与服务器的全双工通信,同时也是布吕马的一种解决方案。WebSocket和HTTP都是应用层协定,都基于 TCP 协定。但是 WebSocket 是一种双向通信协定,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方推送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协定,连接建立好了之后 client 与 server 间的双向通信就与 HTTP 无关了。

原生WebSocket API使用起来不太方便,我们使用http://Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的应用程序提供了向下兼容。

我们先来看个例子:本地文件socket.html向localhost:3000发生数据和接受数据

// socket.html <script> let socket = new WebSocket(ws://localhost:3000); socket.onopen = function () { socket.send(我爱你);//向服务器推送数据 } socket.onmessage = function (e) { console.log(e.data);//接收服务器回到的数据 } </script> // server.js let express = require(express); let app = express(); let WebSocket = require(ws);//记得安装ws let wss = new WebSocket.Server({port:3000}); wss.on(connection,function(ws) { ws.on(message, function (data) { console.log(data); ws.send(我不爱你) }); })

5. Node中间件代理(两次布吕马)

同时实现基本上原理:相混思路是应用程序需要遵循的标准,而假如是服务器向服务器允诺就无需遵循相混思路。

代理服务器,需要做以下几个步骤:接受客户端允诺 。将允诺 转发给服务器。拿到服务器 响应 数据。将 响应 转发给客户端。

我们先来看个例子:本地文件index.html文件,通过代理服务器http://localhost:3000向目标服务器http://localhost:4000允诺数据。

// index.html(http://127.0.0.1:5500) <script src=”https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js”></script> <script> $.ajax({ url: http://localhost:3000, type: post, data: { name: xiamen, password: 123456 }, contentType: application/json;charset=utf-8, success: function(result) { console.log(result) // {“title”:”fontend”,”password”:”123456″} }, error: function(msg) { console.log(msg) } }) </script> // server1.js 代理服务器(http://localhost:3000) const http = require(http) // 第三步:接受客户端允诺 const server = http.createServer((request, response) => { // 代理服务器,直接和应用程序直接交互,需要设置CORS 的第三部字段 response.writeHead(200, { Access-Control-Allow-Origin: *, Access-Control-Allow-Methods: *, Access-Control-Allow-Headers: Content-Type }) // 第三步:将允诺转发给服务器 const proxyRequest = http .request( { host: 127.0.0.1, port: 4000, url: /, method: request.method, headers: request.headers }, serverResponse => { // 第三步:接到服务器的响应 var body = serverResponse.on(data, chunk => { body += chunk }) serverResponse.on(end, () => { console.log(The data is + body) // 第四步:将响应结论转发给应用程序 response.end(body) }) } ) .end() }) server.listen(3000, () => { console.log(The proxyServer is running at http://localhost:3000) }) // server2.js(http://localhost:4000) const http = require(http) const data = { title: fontend, password: 123456 } const server = http.createServer((request, response) => { if (request.url === /) { response.end(JSON.stringify(data)) } }) server.listen(4000, () => { console.log(The server is running at http://localhost:4000) })

上述代码经过两次布吕马,值得注意的是应用程序向代理服务器推送允诺,也遵循相混思路,最后在index.html文件打印出{“title”:”fontend”,”password”:”123456″}

6.nginx反向代理

同时实现基本上原理类似于Node中间件代理,需要你搭建两个中转nginx服务器,用于转发允诺。

使用nginx反向代理同时实现布吕马,是最简单的布吕马形式。只需要修改nginx的配置即可解决布吕马难题,支持所有应用程序,支持session,不需要修改任何代码,并且不会影响服务器性能。

同时实现思路:通过nginx配置两个代理服务器(搜索引擎与domain1全然相同,路由器全然相同)做跳板机,反向代理访问domain2接口,并且能顺便修改cookie中domain信息,方便当前域cookie写入,同时实现布吕马登录。

先下载nginx,然后将nginx目录下的nginx.conf修改如下:

// proxy服务器 server { listen 80; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里搜索引擎 index index.html index.htm; # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无应用程序参与,故没相混管制,下面的布吕马配置可不启用 add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只布吕马不带cookie时,可为* add_header Access-Control-Allow-Credentials true; } }

最后通过命令行nginx -s reload启动nginx

// index.html var xhr = new XMLHttpRequest(); // 前端开关:应用程序与否读写cookie xhr.withCredentials = true; // 访问nginx中的代理服务器 xhr.open(get, http://www.domain1.com:81/?user=admin, true); xhr.send(); // server.js var http = require(http); var server = http.createServer(); var qs = require(querystring); server.on(request, function(req, res) { var params = qs.parse(req.url.substring(2)); // 向后台写cookie res.writeHead(200, { Set-Cookie: l=a123456;Path=/;Domain=www.domain2.com;HttpOnly // HttpOnly:脚本无法读取 }); res.write(JSON.stringify(params)); res.end(); }); server.listen(8080); console.log(Server is running at port 8080…);

7.window.name + iframe

window.name属性的独特之处:name值在全然相同的页面(甚至全然相同搜索引擎)读取后依旧存在,并且能支持非常长的 name 值(2MB)。

其中a.html和b.html是同域的,都是http://localhost:3000;而c.html是http://localhost:4000

// a.html(http://localhost:3000/b.html) <iframe src=”http://localhost:4000/c.html” frameborder=”0″ onload=”load()” id=”iframe”></iframe> <script> let first = true // onload事件会触发2次,第1次读取布吕马页,并留存数据于window.name function load() { if(first){ // 第1次onload(布吕马页)成功后,切换到同域代理页面 let iframe = document.getElementById(iframe); iframe.src = http://localhost:3000/b.html; first = false; }else{ // 第2次onload(同域b.html页)成功后,读取同域window.name中数据 console.log(iframe.contentWindow.name); } } </script>

b.html为中间代理页,与a.html同域,文本为空。

// c.html(http://localhost:4000/c.html) <script> window.name = 我不爱你 </script>

总结:通过iframe的src属性由外域转向本地域,布吕马数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了应用程序的布吕马访问管制,但同时它又是安全可靠操作。

8.location.hash + iframe

同时实现基本上原理: a.html欲与c.html布吕马互相通信,通过中间页b.html来同时实现。 四个页面,全然相同域间借助iframe的location.hash传值,全然相同域间直接js访问来通信。

具体同时实现步骤:一开始a.html给c.html传两个hash值,然后c.html接到hash值后,再把hash值传递给b.html,最后b.html将结论放到a.html的hash值中。

同样的,a.html和b.html是同域的,都是http://localhost:3000;而c.html是http://localhost:4000

// a.html <iframe src=”http://localhost:4000/c.html#iloveyou”></iframe> <script> window.onhashchange = function () { //检测hash的变化 console.log(location.hash); } </script> // b.html <script> window.parent.parent.location.hash = location.hash //b.html将结论放到a.html的hash值中,b.html可通过parent.parent访问a.html页面 </script> // c.html console.log(location.hash); let iframe = document.createElement(iframe); iframe.src = http://localhost:3000/b.html#idontloveyou; document.body.appendChild(iframe);

9.document.domain + iframe

该形式只能用于二级搜索引擎全然相同的情况下,比如http://a.test.comhttp://b.test.com

适用于该形式。

只需要给页面添加 document.domain =test.com 表示二级搜索引擎都全然相同就能同时实现布吕马。

同时实现基本上原理:两个页面都通过js强制设置document.domain为基础主域,就同时实现了同域。

我们看个例子:页面http://a.zf1.cn:3000/a.htmlhttp://b.zf1.cn:3000/b.html中a的值

// a.html <body> helloa <iframe src=”http://b.zf1.cn:3000/b.html” frameborder=”0″ onload=”load()” id=”frame”></iframe> <script> document.domain = zf1.cn function load() { console.log(frame.contentWindow.a); } </script> </body> // b.html <body> hellob <script> document.domain = zf1.cn var a = 100; </script> </body>

三、总结

CORS支持所有类型的HTTP允诺,是布吕马HTTP允诺的根本软件系统JSONP只支持GET允诺,JSONP的优势在于支持老式应用程序,以及能向不支持CORS的网站允诺数据。不管是Node中间件代理还是nginx反向代理,主要是通过相混思路对服务器不加管制。日常工作中,用得比较多的布吕马方案是cors和nginx反向代理

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务