一、 同源策略
在前端开发中,所有支持Javascript的浏览器都会使用“同源策略”这个安全策略。
同源策略(Same origin policy),它是由Netscape提出的一个著名的安全策略,它是一种约定,是浏览器最核心也最基本的安全功能。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指“协议+域名+端口”三者相同。
同源策略是为了安全,确保一个应用中的资源只能被本应用的资源访问。
而解决这种同源策略的方法称之为跨域,跨域的方法有很多种,本文简单介绍一下最经典的jsonp跨域。
二、JSON和JSONP
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
jsonp可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 mydomain1.com 的网页无法与不是 mydomain1.com的服务器沟通,而 HTML 的script 元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。
三、模拟Ajax跨域
仅看定义有点不太直接,我们通过简单的代码来进行模拟跨域(后台使用thinkphp框架)。本机添加两个域名mydomain1.com和mydomain2.com,指向同一个服务。新建一个index.html网页,代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="refresh" content="86400"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>JSONP</title> <script type="text/javascript" src="/static/plugins/jquery/jquery.min.js"></script> <body> <div> <button οnclick="handleAjax()">Ajax跨域请求</button> </div> <script> // jquery模拟ajax跨域 function handleAjax() { $.ajax({ url: "http://mydomain2.com/index.php/Ext/Jsonp/ajaxapi/name/zhangsan", dataType: "json", success: function (d) { alert(d['res']); } }); } </script> </body> </html>
PHP后台接口(通过mydomain2.com提供服务):
public function ajaxapi() { // 请求参数 $name = isset($_REQUEST ['name']) ? $_REQUEST ['name'] : 'neil'; // 返回结果 $d['res'] = 'hello ' . $name; $this->ajaxReturn($d); }
如果ajax请求中的域名为mydomain1.com,点击按钮“Ajax跨域请求”,页面将成功弹出提示框,同域请求不再过多说明。
而如果将请求域名更改为mydomain2.com,通过mydomain2.com访问index.html,点击按钮,控制台会提示跨域请求错误,截图如下:
意思就是无法请求到 mydomain2.com 的资源,原因就是禁止了跨域请求资源。(当然,如果PHP后台添加header("Access-Control-Allow-Origin: *");,可以解决跨域问题,不过这样做不安全)。
JSONP的原理:在页面js中创建一个回调函数,然后在远程服务上调用这个函数并且将服务端结果数据以JSON形式作为参数传递,完成回调。
四、js实现跨域
上面提到过,<script>标签的src是支持跨域请求的。最常见的就是CDN服务的应用。因此,根据JSONP的实现原理,在页面通过<script>标签的src属性,直接访问跨域接口。
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="refresh" content="86400"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>JSONP</title> <body> <div> <button οnclick="addScriptTag()">js跨域请求</button> </div> <script> function callback(d) { alert(d['res']); } // 添加<script>标签,实现跨域 function addScriptTag(src) { var script = document.createElement('script'); script.setAttribute("type", "text/javascript"); script.src = "http://mydomain2.com/index.php/Ext/Jsonp/jsonpapi.html?name=zhangsan&callback=callback"; document.body.appendChild(script); } </script> </body> </html>
PHP后台接口修改如下:
public function jsonpapi() { // 请求参数 $name = isset($_REQUEST ['name']) ? $_REQUEST ['name'] : 'neil'; // 返回结果 $d['res'] = 'hello ' . $name; $this->ajaxReturn($d, 'jsonp'); // 或使用以下代码返回 // if (!isset($_REQUEST ['callback'])) { // $_REQUEST ['callback'] = 'function_callbak'; // } // $jsoncallback = htmlspecialchars($_REQUEST ['callback']); // echo $jsoncallback . "(" . json_encode($d) . ")"; }
刷新页面,点击按钮“js跨域请求”,跨域看到成功执行了跨域请求并执行了回调。
以上代码很简单,就是传递一个回调函数的参数名,后台业务处理完成后,将返回数据传入回调函数里面,调用页面的函数得到了该接口返回的方法数据,使用Javascript解释器执行回调代码,也就是类似于ajax中succsss:function(data)。
五、JQuery使用JSONP解决跨域问题
jQuery 提供了方便使用 JSONP 的方式,它的底层实现也是拼了一个script,然后指定src这种方式,跟上面js的实现是一样,只是jquery封装了一下,显得更加优雅。虽然jQuery的JSONP使用类似 ajax 的请求,但是本质是不一样的,且只能使用 get 请求。
将上面的index.html页面修改如下(添加ajax的JSONP请求,注意请求的配置参数):
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="refresh" content="86400"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>JSONP</title> <script type="text/javascript" src="/static/plugins/jquery/jquery.min.js"></script> <body> <div> <button οnclick="handleAjax()">Ajax跨域请求</button> <button οnclick="handleJsonp()">JSONP跨域请求</button> </div> <script> // jquery模拟ajax跨域(后台可以添加header("Access-Control-Allow-Origin: *");,解决跨域问题) function handleAjax() { $.ajax({ url: "http://mydomain2.com/index.php/Ext/Jsonp/ajaxapi/name/zhangsan", dataType: "json", success: function (d) { alert(d['res']); } }); } // jquery实现JSONP跨域请求 function handleJsonp() { $.ajax({ url: "http://mydomain2.com/index.php/Ext/Jsonp/jsonpapi/name/zhangsan", dataType: "jsonp", jsonpCallback: "callback", success: function (d) { alert("success执行:" + d['res']); } }); } // JSONP回调方法 function callback(d) { alert("回调执行:" + d['res']); } </script> </body> </html>
PHP后台接口修改如下:
public function ajaxapi() { // 请求参数 $name = isset($_REQUEST ['name']) ? $_REQUEST ['name'] : 'neil'; // 返回结果 $d['res'] = 'hello ' . $name; $this->ajaxReturn($d); } public function jsonpapi() { // 请求参数 $name = isset($_REQUEST ['name']) ? $_REQUEST ['name'] : 'neil'; // 返回结果 $d['res'] = 'hello ' . $name; $this->ajaxReturn($d, 'jsonp'); // 或使用以下代码返回 // if (!isset($_REQUEST ['callback'])) { // $_REQUEST ['callback'] = 'function_callbak'; // } // $jsoncallback = htmlspecialchars($_REQUEST ['callback']); // echo $jsoncallback . "(" . json_encode($d) . ")"; }
刷新页面,点击按钮“Ajax跨域请求”,页面仍会提示跨域。而点击按钮“JSONP跨域请求”,则会发现请求成功。
不过我们可能注意到,页面弹出了2次提示框,分别是执行了callback和success方法。这是因为回调函数设置为callback,那么返回后会先调用callback函数中的代码,再调用success函数中的代码。一般情况下,不用定义callback函数,同样jsonpCallback也不需要设置,此时就只执行success中的代码,也就跟平时的ajax处理方式相同。