AJAX跨域问题

本文深入解析了跨域问题的本质及解决方案,包括同源策略的概念、JSONP与CORS两种主要解决方法及其应用场景。

最近工作有遇到跨域问题,正值周末,好好梳理一下跨域相关知识。

还是老样子,提问去学习是习惯的方法:

什么是跨域?

为什么有跨域限制?

怎么解决跨域限制?


什么是跨域?

什么叫同域-协议,域名,端口相同的就叫同域,否则都叫跨域协议。例如下面

http://www.lanco.com ,其中http是协议,www.lanco.com是域名。一般后面都默认80端口,只是没有写出来而已。


为什么有跨域限制?

主要是浏览器的同源策略问题,之所以会有同源策略问题其实很简单,就拿COOKIE来说,如果没有同源策略限制,我在A网站登录之后,去浏览B网站,B网站可以拿到我的COOKIE,这样毫无安全性可言,而因为同源受到限制的大概就是以下三种:

1、禁止对不同源页面DOM进行操作,主要场景是iframe跨域情况

2、禁止使用XHR对象向不同源的服务器地址发起HTTP请求。

3、Cookie,LocalStorage和IndexDB无法读取。


如何解决跨域问题?

1、JSONP解决跨域:

JSONP是JSON使用的一种补充方式,并不是官方协议,JSONP是一种解决跨域问题的协议之一。

他的基本实现原理也很简单,同源策略是不允许跨域请求,而我们的script标签src属性的链接却是可以访问任意网站脚本的


我们可以很简单就模拟出跨域请求,在这里简单演示一下:

有一个test.html文件:

<!DOCTYPE html>
<html>
<head>
    <title>跨域</title>
</head>
<body>
<p>测试</p>
<script type="text/javascript">

    function doalert(data){
        alert("firstname:" + data.firstname +'=='+"lastname:" + data.lastname);
    }
</script>
<script type="text/javascript" src="jquery-1.11.3.min.js">
</script>
<script type="text/javascript">
     $(document).ready(function(){
        $.ajax({
            type : "get",
           url : "http://localhost/testforjsonp.php",
            type: "json",
            success : function(data) {
		var data = eval('('+data+')');
                doalert(data);
            }
			

        });
    });
</script>
</body>
</html>

还有一个简单的php脚本文件:
<?php
echo json_encode(["firstname"=>"lanco","lastname"=>"liu"]);

我们在浏览器中使用http://127.0.0.1/test.html方式去访问html文件,而文件中请求php脚本是localhost的,虽然localhost和1270.0.1都是指向本机,但是他们也属于跨域了。于是在控制台就会出现如下错误,说明发生跨域错误:


现在尝试使用JSONP去解决跨域问题:

首先我们当然是修改html文件,如下,我们去掉了ajax

<!DOCTYPE html>
<html>
<head>
    <title>跨域</title>
</head>
<body>
<p>测试</p>
<script type="text/javascript">

    function doalert(data){
        alert("firstname:" + data.firstname +'=='+"lastname:" + data.lastname);
    }
</script>
<script type="text/javascript" src="jquery-1.11.3.min.js">
</script>
<script type="text/javascript" src="http://localhost/testforjsonp.php?callback=doalert"></script>
</body>
</html>

php脚本文件:

<?php

$callback = $_GET['callback'];
echo $callback."(".json_encode(["firstname"=>"lanco","lastname"=>"liu"]).")";

如下便解决了跨域问题,这里应该也很清楚,他实际就是通过script的src属性来解决的跨域问题,所以也只能使用get方式。



JQuery的dataType也提供了JSONP方便我们使用,所以我们还可以使用:

主要有如下三个参数:

1、dataType:'jsonp' 。JSONP 格式。使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数。

2、jsonp:'string' 。在一个jsonp中重写回调函数名字,这个值会代替上述中的callback字段,例如jsonp:'testjsonp'传给php脚本的参数名称将不会是callback而是testjsonp

3、jsonCallback:'字符串'。 为jsonp请求指定一个回调函数名,这个值将会用户取代JQery自动生成的随机函数名。

修改后的html文件如下:

<!DOCTYPE html>
<html>
<head>
    <title>跨域</title>
</head>
<body>
<p>测试</p>
<script type="text/javascript">

    function doalert(data){
        alert("firstname:" + data.firstname +'=='+"lastname:" + data.lastname);
    }
	
	function doalertbefore()
	{
		alert('success');
	}
</script>
<script type="text/javascript" src="jquery-1.11.3.min.js">
</script>
<script type="text/javascript">
   $.ajax({
            type : "get",
            url : "http://localhost/testforjsonp.php?id=1",
            dataType: "jsonp",
            jsonp:"testjsonp", //请求的参数名,前面自动带&
            jsonpCallback: "doalertbefore",//要执行的回调函数
			success:function(data){
                doalert(data);
			}

        });
</script>
</body>
</html>


2、CORS解决跨域问题:

上面提到jsonp只能发送get请求,这里第二种方法CORS允许任何类型的请求,CORS是跨资源分享(Cross-Origin Resource Sharing)缩写,全称是"跨域资源共享",实现的关键主要是服务器端,只要服务器实现了CORS接口或者约定,就可以跨源通信。

浏览器把CORS请求分为:简单请求和非简单请求,只要满足以下条件,就属于简单请求:

1、请求方法是HEAD/GET/POST三种方式之一;

2、HTTP的头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type -只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

只要是不同时满足以上两点,就属于非简单请求


简单请求:

对于简单请求,浏览器是直接发出CORS请求,具体来说,就是在头信息中,增加一个Origin字段,它包括请求页面源信息(协议,域名,端口号)以此服务器来决定是否响应。但是细心会发现,在标准浏览器中,不管是不是跨域,都将会携带该请求头,但是可以保证在跨域时候一定会携带它

示例代码如下:

<!DOCTYPE html>
<html>
<head>
    <title>跨域</title>
</head>
<body>
<p>测试</p>
<script type="text/javascript">

    function doalert(data){
        alert("firstname:" + data.firstname +'=='+"lastname:" + data.lastname);
    }
</script>
<script type="text/javascript" src="jquery-1.11.3.min.js">
</script>
<script type="text/javascript">
   $(document).ready(function(){
        $.ajax({
            type : "post",      
			url : "http://localhost/testforjsonp.php",
			data:'',
            success : function(data) {
			//var data = eval('('+data+')');
                doalert(data);
            }
        });
    });
</script>
</body>
</html>


在php脚本文件中:

<?php
header('content-type:application:json;charset=utf8');  
header('Access-Control-Allow-Origin:*');  
// 响应类型  
header('Access-Control-Allow-Methods:POST');  


echo json_encode(["firstname"=>"lanco","lastname"=>"liu"]);

如此便可以实现跨域,上述脚本的三个请求头字段表示的意思分别是:

1、以json格式返回给请求的客户端

2、表示接收任何域名的请求

3、服务端接受的跨域请求方法


注意,CORS请求默认是不发送Cookie和HTTP认证信息的,如果要把Cookie发送到服务端,一方面必须要服务器同意,使用Access-Control-Allow-Credentials请求头并设置为true;

另一方面,必须在客户端请求中打开withCredentials属性。

示例代码如下:

<!DOCTYPE html>
<html>
<head>
    <title>跨域</title>
</head>
<body>
<p>测试</p>
<script type="text/javascript">

    function doalert(data){
        alert("firstname:" + data.firstname +'=='+"lastname:" + data.lastname);
    }
</script>
<script type="text/javascript" src="jquery-1.11.3.min.js">
</script>
<script type="text/javascript">
   $(document).ready(function(){
        $.ajax({
            type : "post",      
			url : "http://localhost/testforjsonp.php",
			data:'',
			xhrFields: {
				withCredentials: true // 携带跨域cookie
				},
            success : function(data) {
			//var data = eval('('+data+')');
                doalert(data);
            }
        });
    });
</script>
</body>
</html>

php脚本代码如下:

<?php
header('content-type:application:json;charset=utf8');  
header('Access-Control-Allow-Credentials:true');  
header('Access-Control-Allow-Origin:http://127.0.0.1');  
header('Access-Control-Allow-Methods:POST');  
echo json_encode(["firstname"=>"lanco","lastname"=>"liu"]);

当然还有注意事项是当我们需要传递Cookie时候,服务端的Access-Control-Allow-Origin不能指定为*,必须指定明确的域名,此时的Cookie依然遵循同源策略,只有服务器设置的域名服务器才会接收,两者缺一不可。


接下来CORS学习非简单请求:

非简单CORS请求会在正式通信之前,增加一次HTTP查询请求,这称为预检请求。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

如下是HTML文件代码:

<!DOCTYPE html>
<html>
<head>
    <title>跨域</title>
</head>
<body>
<p>测试</p>
<script type="text/javascript">

    function doalert(data){
        alert("firstname:" + data.firstname +'=='+"lastname:" + data.lastname);
    }
</script>
<script type="text/javascript" src="jquery-1.11.3.min.js">
</script>
<script type="text/javascript">
   $(document).ready(function(){
        $.ajax({
            type : "post",      
			url : "http://localhost/testforjsonp.php",
			data:"",
			contentType:"application/json;charset=utf-8",
            success : function(data) {
                doalert(data);
            }
        });
    });
</script>
</body>
</html>

在这里改变了contentType为application/json,所以浏览器会认为他是非简单请求,自动向服务器发送一个预检请求



在这个预检请求中请求方法是OPTIONS,表示这个请求用来询问,预检头信息还包括以下两个比较重要的请求头:

Access-Control-Request-Method必须字段,用来列出浏览器的CORS请求会用到哪一些方法;

Access-Control-Request-Headers,指定浏览器CORS请求额外发送的头信息字段


php脚本代码如下:

<?php
header('content-type:application:json;charset=utf8');   
header('Access-Control-Allow-Origin:http://127.0.0.1');  
header('Access-Control-Allow-Methods:POST,GET');  
header('Access-Control-Allow-Headers:content-type');  
echo json_encode(["firstname"=>"lanco","lastname"=>"liu"]);

服务器接收到这个预检请求之后,检查相关Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段,确认允许跨源请求,就会做出如下响应:


相反,如果是拒绝预检请求,也会返回一个正常的HTTP Response,但是不一定没有CORS相关头信息,例如我在服务器端指定可以访问的域名不是当前正在访问的,依旧会响应Access-Control-Allow-Origin请求头



正常允许预检之后的结果是如下:



下面是服务器端可能需要使用的相关字段:

Access-Control-Allow-Origin:必须,它的值要么是浏览器端传过来的Origin字段值,要么是*,表示接收任意域名请求;

Access-Control-Allow-Methods:必须,它的值是逗号分割的字符串或者*,表明服务器端支持的跨域请求方法;

Access-Control-Allow-Headers:如果浏览器请求包含了Access-Control-Request-Headers,则这个字段必须,它也是逗号分隔字符串,表示服务器端支持的所有头信息字段;

Access-Control-Max-Age:可选,用来指定本次预检(非简单请求)有效期,单位为秒,在有效期时,不可以发出另外一条预检请求;

Access-Control-Allow-Credentials:可选,值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie是不包括在CORS请求之中;

Access-Control-Expose-Headers:可选,CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。


如上是我个人学习感悟,如有不同意见,恳请指教,共同学习进步^^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值