第69节 跨域请求-Web前端开发之JavaScript-零点程序员-王唯

2022-10-11 21:42:17 122 0
魁首哥

跨域请求:

现代的所有的浏览器都遵守同源策略,所谓的源指的就是一个域名、一个服务器,同源是指两个脚本拥有相同的域名,不同源指的是不同的域名,同源策略指的是,一个源的脚本不能读取或操作其他源的http响应和cook IE ,也就是出于安全方面的考虑,页面中的JavaScript无法访问其他服务器上的数据,这就是所谓的“同源策略”。
同源是指协议、域名和端口都一致。

不同源限制的内容:

  • Cookie、LocalStorge、IndexedDb等存储性内容;
  • DOM节点;
  • Ajax请求;

跨域请求时,不同域的服务器是返回了数据的,只不过浏览器拦截了响应数据;同时也说明了跨域并不能完全阻止 CSRF ,因为请求毕竟是发出去了;

CORS (Cross-Origin Response Sharing)跨域资源共享:

通过XHR实现Ajax通信的主要限制,是跨域安全策略;默认情况下,只能访问同一个域中的资源,这种安全策略可以预防某些恶意行为,如:

 var xhr = new XMLHttp request ();
xhr.onload = function(){
console.log(xhr.responseText);
}
xhr.open("GET", " HTTPS ://www.zeronetwork.cn/study/index. HTML ");
xhr.send(null);  

其抛出了CORS policy异常;

XHR2规范了在通过HTTP响应中如何选择合适的CORS(Cross-Origin Response Sharing,跨域资源共享)去跨域访问资源;其定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通;CORS的基本思想是,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从面决定请求或响应是否应该成功;

比如一个简单的使用GET或POST发送的请求,默认情况下它没有自定义的头,但一般会包括一个Origin请求头,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应,如Origin头部示例:

 Origin:   

如果服务器认为这个请求可以接受,就在响应的Access-Control-Allow-Origin([əˈlaʊ])头中回发相同的源信息(如果是公共资源,可以回发”*”),例如:

 Access-Control-Allow-Origin:   

如果没有这个响应头,或者有这个响应头但与请求Origin头信息不匹配,浏览器就会驳回请求;反之,浏览器会处理请求;

实现跨域:

IE和标准浏览器已经实现了各自的跨域解决方案;

标准浏览器对CORS的实现:

在标准浏览器中,客户端在使用Ajax跨域请求时,抛出异常,不能访问;如:

 var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText);
}
}
xhr.open("GET"," JSON ");
xhr.send(null);  

被请求的服务端需要设置Access-Control-Allow-Origin响应头,以便于浏览器识别它是否为可信源。

例如,在Apache服务器中,在服务器的配置中添加如下设置:

  header  set Access-Control-Allow-Origin 'origin-list'  

对于 Nginx ,设置此http头的命令是:

 add_header 'Access-Control-Allow-Origin' 'origin-list'  

或者使用. htaccess 文件配置,如:

 
Header set Access-Control-Allow-Origin "*"

order allow,deny
allow from all  

应用:

 xhr.open("GET","#34;);  

单独为某个后端程序设置响应头,例如b.com/cors.php:

  

无论同源请求还是跨源请求都使用相同的接口,因此对地本地资源,最好使用相对URL,在访问远程资源时再使用绝对URL;这样做能消除歧义,避免出现限制访问头部或本地cookie信息等问题。

IE对CORS的实现:

微软 在IE8中引入了 XDR (XDomain Request )对象,其与XHR类似,其可直接用于发起安全的跨域请求,实现安全可靠的跨域通信;

 var xdr = new XDomainRequest();
console.log(xdr);  

IE11 和标准浏览器并不支持;

XDR对象的使用方法与XHR对象非常相似,两者拥有几乎相同的属性和方法,也是调用 open ()方法,再调用 send ()方法;但与XHR对象的open()方法不同,XDR对象的open()方法只接收两个参数:请求的类型和URL;如:

 var  xdr  = new XDomainRequest();
console.log(xdr);
// xdr.open("GET", "#34;);
xdr.open("GET", "example.php");
xdr.onload = function(){
console.log(xdr.responseText);
}
xdr.send();  

此时,不管是跨域的还是同源的都不允许访问,抛出“在 Access-Control-Allow-Origin 标头中未找到源”;

XDR对象的安全机制中部分实现了CORS,后端也需要设置 Access-Control-Allow-Origin响应头,如c.com/cors.php:

  

请求端:

 xdr.open("GET", "#34;);  

XDR对象属性和事件:

请求返回后,会触发onload事件,响应的数据也会保存在responseText属性中,响应的MIME类型保存在contentType属性中,如:

 var xdr = new XDomainRequest();
xdr.onload = function(){
console.log(xdr.contentType); // application/json
console.log(xdr.responseText);
}
xdr.open("post","#34;);
xdr.send(null);  

例如再请求一个同源的contentType.php:

 header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json");
echo '{"username":"王唯","age":18,"sex":true}';  

在接收到响应后,只能访问响应的原始文本,不能确定响应的状态代码(也就是它没有status属性);而且,只要响应有效就会触发onload事件,如果失败就会触发error事件,但除了错误本身之外,没有其他信息可以确定请求是否成功,所以唯一能够确定的就只有请求未成功;

要检测错误,如要指定error事件处理程序,如:

 xdr.onerror = function(){
console.log("出现错误");
}  

由于导致XDR请求失败的因素很多,因此,最好通过error事件处理程序来捕获该事件,否则,即使请求失败也不会有任何提示。

在请求返回前调用abort()方法可以终止请求,如:

 xdr.abort();  

与XHR对象一样,XDR也支持timout属性和ontimeout事件,如:

 xdr.timeout=1000;
xdr.ontimeout = function(){
console.log("请求超过1秒");
};  

onprogress事件:

应该始终定义 xdr.onprogress 事件,即使它是一个空函数,否则 XDomainRequest 对于重复请求,可能不会触发 onload 事件;

 xdr.onprogress = function(event){
console.log(event);
};  

XDR与XHR的不同之外:

1、必须使用 HTTP 或 HTTPS 协议访问目标 URL:
因为XDR对象依赖于一个HTTP响应头来实现访问控制,所以它要求目标URL符合HTTP或HTTPS 协议,以便于XDR对象检验响应头;
2、只支持GET和POST请求;
3、不能设置自定义请求头信息,也不能访问响应头部信息;
4、只支持text/plain作为请求头Content-Type的取值:在XDR中,不管是GET还是POST请求, Content-Type 被限制成“text/plain”,所以服务端不会把请求主体数据解析成键值对,即不能从参数中获取到POST的数据,只能读取流数据,需要其自行解析,如:

 var xdr = new XDomainRequest();
xdr.open("POST", "xdrpost.php");
xdr.onload = function(){
console.log(xdr.responseText);
};
var param = "username=wangwei&age=18";
xdr.send(param);  

xdrpost.php:

 foreach  ($arr as $value) {
$v = explode("=", $value);
echo "key:$v[0], value:$v[1] \r\n";
}  

5、身份验证和cookie不能和请求一起发送,也不会随响应返回;
6、请求的URL必须和被请求的URL采用相同的协议:两者的协议必须统一,要么是HTTP,要么是HTTPS;
7、所有XDR请求都是异步执行的,不能用它来创建同步请求;

Preflighted Requests:

CORS通过一种叫做Prelighted Requests的透明服务器验证机制支持开发人员使用自定义的头部、GET或POST之外的请求方式,以及不同类型的主体内容;
在使用下列高级选项来发送请求时,就会向服务器发送一个Preflight请求,这种请求使用OPTIONS方式,发送下列头部:

  • Origin:与简单的请求相同;
  • Access-Control-Request-Method:请求自身使用的方法;
  • Access-Control-Request-Headers:(可选)自定义的头部信息,多个头部以逗号分隔;

以下是一个带有自定义头部customHeader,并使用POST方法发送的数据,如:

  • Origin:
  • Access-Control-Request-Method: POST
  • Access-Control-Request-Headers: customHeader

发送这个请求后,服务器端可以决定是否允许这种类型的请求,其通过在响应中发送如下头部与浏览器进行沟通:

  • Access-Control-Allow-Origin:与简单的请求相同;
  • Access-Control-Allow-Methods:允许的方法,多个方法以逗号分隔;
  • Access-Control-Allow-Headers:允许的头部,多个头部以逗号分隔;
  • Access-Control-Max-Age:应该将这个Preflight请求缓存多长时间(以秒表示)

例如,允许任何源、POST请求方式、自定义头customHeader以及请求的缓存时间:

  • Access-Control-Allow-Origin:
  • Access-Control-Allow-Methods: POST
  • Access-Control-Allow-Headers: customHeader
  • Access-Control-Max-Age: 1728000

如:

 var xhr = new XMLHttpRequest();
xhr.onload = function(){
console.log(xhr.responseText);
}
xhr.open("OPTIONS","#34;,true);
xhr.setRequestHeader("customHeader", "customValue");
xhr.send(null);  

后端flighted.php:

 echo  "有Headers、Methods头信息";  

Preflight请求结束后,结果将按照响应中指定的时间缓存起来;

带凭据的请求:

默认情况下,跨域请求不提供凭据(cookie、HTTP认证及客户端 SSL 证明等);
通过将XHR对象的withCredentials属性设置为true,可以指定某个跨域请求应该发送凭据(授权信息);如:

 xhr.withCredentials = true;
xhr.send(null);  

当使用带有凭据的请求时,不能把Access-Control-Allow-Origin设为*,并且Access-Control-Allow-Origin只能设置一个域,不能是多个,否则会抛出异常;

后端c.com/credentials.php:

 header("Access-Control-Allow-Origin: #34;);
echo "c.com/example.php,已经设置了ACAO";  

如果服务端接受带凭据的请求,必须设置Access-Control-Allow-Credentials: true响应头;

如后端c.com/ credentials.php:

 header("Access-Control-Allow-Origin: #34;);
header("Access-Control-Allow-Credentials: true");
echo "设置了Origin,也设置了Credentials";
echo json_encode($_COOKIE);  

如果在同源下配置withCredentials,无论配置true还是false,效果都会相同,且会一直提供凭据信息;另外,同时还可以发送自定义请求头,如后端credentials.php:

  

服务端还可以在Preflight响应中发送这个HTTP头部,但不能把Access-Control-Allow-Headers设为*;

跨浏览器的CORS:

即使浏览器对CORS的支持程度并不一致,但所有浏览器都支持简单的请求(非Preflight和不带凭据的请求),因此有必要实现一个跨浏览器的方案:检测XHR是否支持CORS的最简单方式,就是检查是否存在withCredentials属性,再结合检测XDomainRequest对象是否存在,就可以兼顾所有浏览器了,如:

 function createCORSRequest(method, url, withCredentials){
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
xhr.open(method, url);
xhr.withCredentials = withCredentials;
}else  if (typeof XDomainRequest != "undefined"){
xhr = new XDomainRequest();
xhr.open(method, url);
}else{
xhr = null;
}
return xhr;
}
var request = createCORSRequest("GET", "#34;, true);
if(request){
request.onload = function(){
console.log(request.responseText);
};
request.send(null);
}  

示例:使用HEAD和CORS请求链接的详细信息,如:

 var supportsCORS = (new XMLHttpRequest).withCredentials != undefined;
var links = document. getElementsByTagName ("a");
for(var i=0; i站外链接 ";
if(!supportsCORS) continue;
}
if(link.addEventListener)
link.addEventListener("mouseover", mouseoverHandler, false);
else
link.attachEvent("onmouseover", mouseoverHandler);
}
function mouseoverHandler(e){
var link = e.target || e.srcElement;
var url = link.href;
var xhr = new XMLHttpRequest();
xhr.open("HEAD", url);
xhr.onreadystatechange = function(){
if(xhr.readyState !== 4) return;
if(xhr.status == 200){
var type = xhr.getResponseHeader("Content-Type");
var size = xhr.getResponseHeader("Content-Length");
var date = xhr.getResponseHeader("Last-Modified");
link.title = "类型:" + type + "\n" +
"大小:" + size + "\n" +
"时间:" + date;
}else{
if(!link.title)
link.title = "获取不到详细信息:\n" +
xhr.status + " " + xhr.statusText;
}
};
xhr.send(null);
if(link.removeEventListener)
link.removeEventListener("mouseover", mouseoverHandler, false);
else
link.detachEvent("onmouseover", mouseoverHandler);
}  

HTML:

 study
a.com
apple.com
虽然CORS技术已经无处不在,但在CORS出现之前,就已经存在一些跨域的技术了,虽然这些技术应用起来有些麻烦,但它们绝大部分不需要修改服务器端代码,所以直到现在这些技术仍然被广泛使用; 

后端代理方式:

这种方式可以解决所有跨域问题,也就是将本域的后端程序作为代理,每次对其它域的请求都转交给该代理程序,其通过模拟http请求去访问其它域,再将返回的结果返回给前端,这样做的好处是,无论访问的是文档、还是JS文件都可以实现跨域;
例如,b.com/data.php响应JSON字符串:

  

a.com/getdata.php服务端获取b.com/data.php响应:

  

a.com/data.html使用Ajax请求同源的getdata.php:

 var xhr = new XMLHttpRequest();
xhr.open("GET", "getdata.php");
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
console.log(xhr.responseText);
}
};
xhr.send(null);  

基于iframe实现跨域:

基于iframe实现的跨域要求两个域属于同一个根域,如:www.a.com和b.a.com其使用同一协议(例如都是 http)和同一端口(例如都是80),此时在两个页面中同时设置document.domain为同一个主域,就实现了同域,从而可以实现通信;如b.a.com中的iframe.html:

 

iframe

www.a.com主页面为:

 

  

使用window.name和iframe进行跨域:

window的name属性返回的是该window的名称,它的值有个特点:在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的name值(2MB),即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name,每个页面对window.name都有读写的权限;

正因为window的name属性的这个特征,所以可以使用window.name来进行跨域;例如a.html:

 

a.html

b.html:

 

b.html

跨域:例如,有一个a.com/a.html页面,需要通过js来获取位于另一个不同域上的页面,如:b.com/b.html里的数据:

   

如果b.html不跳转,其他页也可以获取数据,可以采用iframe;
如a.com/a.html:

 

a.html

使用location.hash+iframe跨域:

假设a.com/a.html要向b.com/b.html传递信息;如a.com/a.html:

 

a.html

b.com/b.html:

 

b.html

a.com/c.html:

   

图像Ping:

使用标签,也可以动态创建图像,使用它们的onload和onerror事件处理程序来确定是否接收到了响应;例如:

 var img = new Image();
img.onload = img.onerror = function(){
console.log("Done");
};
img.src = "#34;;
pingimg.php:
if($_GET['name']){
echo $_GET['name'];
}  

图像Ping有两个主要的缺点,一是只能发送GET请求,二是无法访问服务器的响应文本,因此,图像Ping只能用于浏览器与服务器间的单向通信;提交的数据是通过查询字符串形式发送的,但响应可以是任意内容,但通常是像素图或204响应;
通过图像Ping,浏览器得不到任何具体的数据,但通过侦听load和error事件,它能知道响应是什么时候接收到的,此时可以实现一些自身的逻辑;
示例:图像Ping最常用于跟踪用户点击页面或动态广告曝光次数,如:

 

基于

b.com/scripts/demo.js:

 function show(msg){
alert("收到的数据:" + msg);
}
alert("www.b.com/script/demo.js");  

JSONP:
JSONP是JSON with padding(填充式JSON或参数式JSON)的简写,是应用JSON的新方法,其利用%20
%20

%20

请求端和服务端共同约定使用自定义函数,而不是内置函数;例如b.com/jsonptest.php:

 echo 'handlerJSONP({"name":"王唯","age":18})';  

请求端实现handlerJSONP()函数,并使用 %20%20

%20

b.com/jsonptest.php:

 header('Content-type: application/json');
//获取回调函数名
$callback = htmlspecialchars($_REQUEST['callback']);
//json数据
$json_data = '["王唯","静静","娟子","大国"]';
//输出jsonp格式的数据
echo $callback . "(" . $json_data . ")";  

JSONP是通过动态

JSONP的不足:

  • 首先,JSONP是从其他域中加载代码执行,如果其他域不安全,很可能会在响应中夹带一些恶意代码;因此在使用不是你自己运维的Web服务时,一定得保证它安全可靠;
  • 其次,只能进行GET请求;
  • 最后,要确定JSONP请求是否失败比较麻烦,虽然HTML5给

    b.com/ message.html:

     

    b.com/message.html

    语法:otherWindow.postMessage(message, targetOrigin, [transfer]);

    • otherWindow为其他窗口的一个引用;
    • message参数:将要发送到其他window的数据;它将会被结构化克隆算法序列化,即可以不受限制的将数据对象安全的传送给目标窗口而无需自己序列化;
    • targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串”*”(表示无限制)或者一个URI;
    • transfer参数:可选,是一串和message同时传递的Transferable对象,这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权;

    例如:a.com/post.html:

     // ...
    win.postMessage("来自a.com/post.html的消息","*");
    b.com/message.html:
    window.addEventListener("message", function(event){
    console.log(event); // MessageEvent
    });  

    需要延迟执行postMessage()方法,延迟的方式有多种,使用setTimeout()、iframe的onload事件、发送者的onload事件或者使用按钮的click事件处理程序,如a.com/post.html:

     setTimeout(function(){
    win.postMessage("来自a.com/post.html的消息","*");
    },500);
    // 或:
    var iframe = document.getElementsByTagName("iframe")[0];
    iframe.onload = function() {
    var iframe = document.getElementById("iframe");
    var win = iframe.contentWindow;
    win.postMessage("来自a.com/post.html的消息","*");
    }
    // 或:
    var btnSend = document.getElementById("btnSend");
    btnSend.addEventListener("click", function(){
    var iframe = document.getElementsByTagName("iframe")[0];
    var win = iframe.contentWindow;
    win.postMessage("来自a.com/post.html的消息","*");
    });  

    使用postMessage()将数据发送到其他窗口时,最好指定明确的targetOrigin,而不是*,原因是恶意网站可以在用户不知情的情况下更改窗口的位置,甚至可以拦截所发送的数据;

     win.postMessage("来自a.com/post.html的消息","#34;); // 或
    win.postMessage("来自a.com/post.html的消息","#34;); // 或
    win.postMessage("来自a.com/post.html的消息","#34;);  

    在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送;这个机制用来控制消息可以发送到哪些窗口。

    MessageEvent接口:
    代表一段被目标对象接收的消息;
    属性:

    • data属性:其保存着由发送者发送的字符串数据;
    • lastEventId属性:表示事件的唯一ID;
    • origin属性:返回一个表示消息发送者来源;
    • ports属性:MessagePort对象数组,表示消息正通过特定通道(数据通道)发送的相关端口;
    • source属性:是一个MessageEventSource对象,代表消息发送者;
     window.addEventListener("message", function(event){
    console.log(event);
    console.log(event.data); // 来自a.com/post.html的消息
    console.log(event.lastEventId); // 空
    console.log(event.origin); // 
    console.log(event.ports); // []
    console.log(event.source); // window 
    });  

    如果不希望接收message,就不要实现message事件;如果希望从其他网站接收message,最好使用origin或source属性验证消息发送者的身份;如:

     window.addEventListener("message", function(event){
    if(event.origin != "#34;)
    return;
    console.log(event.data);
    });  

    虽然postMessage()是单向的通信,但可以使用source属性在具有不同origin的两个窗口之间建立双向通信;
    例如a.com/message.html:

     
    

    是否确认保存?

    https://www.zhihuclub.com/console.html:

     

    控制台

    https://www.zhihuclub.com/content.html:

     
    

    设置1:

    设置2:

    设置3:

    设置4:

    示例,修改信息:

     https://www.zhihuclub.com/content.html:
    

    用户信息

    ID:001

    姓名:王唯

    单位:零点网络

    地址:

    电话:

    editInfo.html:

     

    姓名:

    性别:

    年龄:

    editInfo.php:

     query($sql);
    $row = mysqli_fetch_array($result);
    echo json_encode($row);
    }elseif($_REQUEST['action'] == 'update'){
    $ID = intval($_POST['ID']);
    $username = $_POST['username'];
    $sex = $_POST['sex'];
    $age = $_POST['age'];
    $sql = "update users set username='$username', sex='$sex', age='$age' where ID=$ID";
    $result = $conn->query($sql);
    if($result){
    echo '{"status": 1}';
    }else{
    echo '{"status": 0}';
    }
    }  

    超链接打开的窗口也可使用postMessage()方法进行通信,但需要设置a标签的target属性为自定义值,如:

     

    零点程序员

    b.com/https://www.zhihuclub.com/content.html: console.log(window.opener); // Window or global console.log(window.name); // "mywin"

    发送消息,b.com/https://www.zhihuclub.com/content.html:

     if(window.opener){
    console.log(window.opener);
    window.opener.postMessage("我已经打开了","*");
    }  

    主页面:

     window.addEventListener("message", function(event){
    console.log(event.data);
    });  

收藏
分享
海报
0 条评论
122
上一篇:「php」php中常用的7种排序&查找&去乱码方法实现 下一篇:【Swoole源码研究】深入理解Swoole协程实现

本站已关闭游客评论,请登录或者注册后再评论吧~

忘记密码?

图形验证码