当前位置: 首页 > Web前端 > HTML5

跨域请求处理方法全解

时间:2023-04-05 02:17:37 HTML5

为什么会出现跨域问题我们想象一下以下几种情况:我们开了一个天猫,登录了自己的账号,然后我们又开了一个天猫商品,我们不需要重新登录直接购买商品,因为两个网页同源,可以共享登录相关的cookie或localStorage数据;如果您在使用支付宝或网银的同时打开了一个未知的网页,如果该网页可以访问您的支付宝或网银页面的信息,将会造成严重的安全问题。如果未知网站是黑客的工具,他可以利用它发起CSRF攻击。显然浏览器不允许这样的事情发生;想必你也同时登录了几个qq账号,如果你同时打开各自的qq空间浏览器,会有一个小模式,就是会打开另一个窗口打开第二个的空间qq账号。为了解决不同域名相互访问数据带来的不安全问题,Netscape提出了一个众所周知的安全策略——同源策略,即来自同一“源”的数据可以自由访问,但来自不同域名的数据不同的来源是相互排斥的。无法访问。同源政策是显而易见的。在上面的第一个和第三个例子中,不同的天猫店铺和qq空间同源,可以共享登录信息。为了区分不同qq的登录信息,qq重新开了一个窗口,因为浏览器的不同窗口无法共享信息。在第二个例子中,支付宝、网上银行和未知网站不是同源的,因此它们不能相互访问信息。如果硬要请求数据,会提示异常:No'Access-Control-Allow-Origin'headerispresentontherequestedresource。因此不允许访问Origin'null'。那么什么是同源请求呢?同源请求要求请求的资源页面和请求的页面满足三个相同点:相同的协议,相同的主机,相同的端口,简单理解:/*下面两个数据不是同源的,因为协议不同*/http://www.abc123。com.cn/item/a.jshttps://www.abc123.com.cn/item/a.js/*以下两个数据不是同源的,因为域名不同*/http://www.abc123.com。cn/item/a.jshttp://www.abc123.com/item/a.js/*以下两个数据不是同一来源,因为主机名不同*/http://www.abc123.com.cn/item/a.jshttp://item.abc123.com.cn/item/a.js/*以下两个数据不是同一来源,因为协议不同*/http://www.abc123.com.cn/item/a.jshttp://www.abc123.com.cn:8080/item/a.js/*以下两个数据非同源,域名和ip视为异源*这里要注意ip和域名替换不是同源的*假设www.abc123.com.cn解析的ip是195.155.200.134*/http://www.abc123.com.cn/http://195.155.200.134//*以下两个数据同源*//*这是同源*/http://www.abc123.com.cn/source/a.htmlhttp://www.abc123.com.cn/item/b.jsHTTP简单请求与非简单请求http请求满足以下条件时称为简单请求,否则为非简单请求:请求方法为HEAD之一,GET,和POST,并且HTTP头信息不超过以下字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-TypeContent-Type取值仅限于application/x-www-form-urlencoded、multipart/form-data、text/plain非简单请求前会发送一个OPTION预请求发送,如果跨域操作返回405(MethodNotAllowed)错误,服务器需要允许OPTION请求HTTP跨域访问处理方法及适用条件JSOP适用条件:请求的GET接口需要支持jsonp使用权。这里需要强调的是,jsonp并不是Ajax的一部分,它只是将url放到script标签中实现数据传输,并不受同源策略的限制。由于通用库也会将其与Ajax封装在一起,由于与Ajax的根不一样,这里不再赘述。下面是jsonp的例子:window.jsonpCallback=console.log;varJSONP=document.createElement("script");JSONP.src="http://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=13122222222&t="+Math.random()+"&callback=jsonpCallback";;document.body.appendChild(JSONP);后端支持jsonp模式(Nodejs)varquerystring=require('querystring');varhttp=require('http');varserver=http.createServer();server.on('request',function(req,res){varparams=qs.parse(req.url.split('?')[1]);varfn=params.callback;//jsonp返回设置res.writeHead(200,{'Content-Type':'text/javascript'});res.write(fn+'('+JSON.stringify(params)+')');res.end();});server.listen('8080');console.log('Serverisrunningatport8080...');document.domain适用条件:host仅在不同服务器的情况下,域名本身应该相同。www.dom.com和w1.dom.com需要从同一来源访问。可以将document.domain设置为dom.com来解决这个问题。document.domain='dom.com';比如我要开发一个浏览器插件,发现腾讯视频页面有iframe本身是跨域的,无法获取到iframe的DOM对象。但是域名部分相同,可以用这种方法解决。注意:如果要设置成完全不同的域名,肯定会报同源错误,注意使用范围!内嵌iframe的适用条件:在host中只有server不同的情况下,域名本身要相同。有了上面的例子,就不难理解这个方法了。严格来说,这并不是一种新的方法,而是对之前方法的扩展。通过设置document.domain,同一个域名下不同服务器名的页面可以访问数据,但是值得注意的是这个数据访问不是相互的,外部页面可以访问iframe内部的数据,但是iframe不能访问外部数据。location.hash适用条件:iframe与其宿主页面通信。#和完成的url中的以下部分是哈希。这部分可以修改,完成iframe和host之间的直接数据传递。下面演示iframe页面(B.html)向宿主(A.html)传输数据,反之亦然://A.htmldata=['book','map','shelf','knife'];setTimeout(()=>{location.hash=window.encodeURIComponent(data.join('/'));},1000);//B.htmlwindow.parent.onhashchange=function(e){vardata=window.decodeURIComponent(e.newURL.split('#')[1]).split('/');控制台日志(数据);//["book","map","shelf","knife"]}*注意反向传输数据时要使用window。parent.location.hashwindow.name适用条件:宿主页面和iframe之间的通信窗口对象有一个name属性,它有一个特点:即在窗口的生命周期内,所有页面(iframe)加载的window都是共享一个window.name,每个页面都有对window.name的读写权限,window.name在一个window加载的所有页面中都是持久化的,不会在加载新页面的地方重新加载。这样就可以在iframe中获取window中编辑window.name,但是这个过程缺少监控,宿主页面(A.html)和iframe页面(B.html)不知道对方什么时候修改value://A.htmlsetTimeout(()=>{window.parent.name="what!";},2000);//B.htmlsetTimeout(()=>{console.log(window.name);//什么!},2500);postMessage适用条件:postMessage是H5提出的一种消息交换机制,解决了iframe之间不能互通的问题,也可以跨窗口互通。语法如下://在www.siteA.com中发送消息//@message{any}要发送的数据(注意:旧浏览器只支持字符串类型)//@targetOrigin{string}指定要发送的域接收数据,只有它指定的域可以接收消息,如果是"*"则没有域限制//transfer{any}与消息一起发送并转移所有权window.postMessage(message,targetOrigin,[transfer]);//在另一个页面接受参数window.onmessage=console.log;这里不说第三个参数,因为你这辈子可能用不到它。而targetOrigin最好不要使用“*”,除非你想让所有的页面都收到你的消息。您将使用的场景(iframe):这个只是在没有iframe的情况下,当你在同一个浏览器窗口同时打开www.siteA.com和www.siteB.com两个标签时,可以也可以使用<脚本>functionsendMessage(){window.postMessage({title:'向siteA打招呼!'},'http://www.siteA.com');}setTimeout(sendMessage,2000);反向代理服务端页面需要访问一些跨域接口。由于代理的存在,请求在服务端看来是不跨域的,所以在使用各种请求的时候,需要注意http到https的兼容性问题。比如我在一些网络平台开发网站时,得到一个页面www.site-A.com,这个页面需要去请求自己数据服务器data.site-B.com上的数据,这样也会造成跨域问题。但是www.site-A.com这个页面挂在了第三方服务器上。为了解决这个问题,可以使用代理服务器:varexpress=require('express');varrequest=require('request');varapp=express();app.use('/api',function(req,res){varurl='http://data.site-B.com/api2'+req.url;req.pipe(request(url)).pipe(res);});app.use('/',function(req,res){varurl='http://data.site-C.com';req.pipe(request(url)).pipe(res);});当然你还需要配置一个host:127.0.0.1local.www.site-B.com然后访问local.www.site-B.com就OK了。CORS应用条件:CORS需要服务器支持,存在一定的兼容性问题(现在可以无视,但必要时不要忘记这个'bug')。它通过添加http头关键字实现跨域访问,包括如下头内容:#www.siteA.com/api对应的响应需要有如下http头字段Access-Control-Allow-Origin:'http://www.siteB.com'#指定域可以请求,通配符'*'(必填)Access-Control-Allow-Methods:'GET,PUT,POST,DELETE'#指定允许的跨域请求方式(必填)Access-Control-Allow-Headers:'Content-Type'#请求中必须包含的HTTP头字段Access-Control-Allow-Credentials:true#配合请求中的withCredentials头进行请求验证也很简单通过express实现,在注册路由之前添加:varcors=require('cors');//通过npm安装app.use(cors());当然你也可以自定义一个中间件://custommiddlewarevarcors=function(req,res,next){//自定义跨域设置需要的响应头。res.header('Access-Control-Allow-Origin','http://www.siteB.com');res.header('Access-Control-Allow-Methods','GET,PUT,POST,DELETE');next();};app.use(cors);//使用跨域中间件WebSocket协议。跨域ws协议是H5中的一种web全双工通信解决方案。常规的http属于对应请求的过程。如果客户端没有请求,服务器就不能主动向客户端推送数据。ws协议解决了这个问题,但是出于安全考虑,它也有同源策略的限制。*本文不讨论通过长连接、服务端挂起请求等方式推送数据。本文只讨论跨域。这是一个示例(取决于socket.io.js)://前端socket.on('connect',function(){//监听服务器消息socket.on('message',function(msg){console.log('datafromserver:'+msg);});//监听服务器关闭socket.on('disconnect',function(){console.log('Serversockethasclosed.');});});document.getElementById('input').onkeyup=function(e){if(!e.shiftKey&&!e.ctrlKey&&!e.altKey&&e.keyCode===13)socket.send(this.value);};//后端部分(node.js)varhttp=require('http');varsocket=require('socket.io');//启动http服务varserver=http.createServer(function(req,res){res.writeHead(200,{'Content-type':'text/html'});res.end();});server.listen('8080');console.log('服务器isrunningatport8080...');//监听socket连接socket.listen(server).on('connection',function(client){//监听客户端信息client.on('message',function(msg){client.send('hello:'+msg);console.log('datafromclient:'+msg);});//监听客户端断开client.on('disconnect',function(){console.log('客户端套接字已关闭。');});});HTML标签中的crossorigin属性HTML中的