同源策略、跨域

同源策略

同源策略 Same-Origin-Policy(SOP)

定义

同源策略是浏览器的一个安全功能,不同源的的客户端脚本在没有明确授权的情况下,不能读取对方资源。只有在同一个源的脚本才被赋予操作dom、读写Cookie、Session,使用AJAX等操作的权限。只要有JS引擎的浏览器都使用同源策略。

现象

Web浏览器只允许在两个页面有相同的源时,第一个页面的脚本才可以访问第二个页面里的数据。

当在第一个页面发起Ajax请求第二个页面时,报错如下。

第一个页面:http://test.yueluo.club/index.html
第二个页面:http://study.yueluo.club/index.html

Access to XMLHttpRequest at "http://study.jsplusplus.com/server.php" from origin
'http://test.jspluspus.com' has been blocked by CROS policy:NO 'Access-Control-Allow-Origin'
header is present on the requested resource.

同源、不同源

源(域名):协议 + 域名 + 端口

同源:相同的协议&&相同的域名&&相同的端口
不同源:不同的协议||不同的域名||不同的端口

不受同源策略限制的项

1. 页面超链接
2. 重定向页面
3. 表单提交
4. 资源引入(script、src/link、href、src/iframe、src)

7种跨域获取数据的方法

1. 服务器中转请求

客户端浏览器通过同源服务端程序向不同源的服务端程序获取数据。

客户端浏览器 -> 同源服务端程序 -> 不同源服务端程序

同源策略是针对于浏览器的,服务端不受同源策略影响。

2. 设置基础域名 + IFRAME

前提:基础域名一致

让不同源的客户端浏览器请求数据,利用iframe获取到contentWindow。

1. 首先设置同源域名(同源地址和不同源地址都要设置)
2. 客户端浏览器进行iframe引入,利用引入页面的HTTP请求方法获取数据
案例
var iframe = document.createElement('iframe');

iframe.src = 'http://data.yueluo.com/index.html';
iframe.id = 'myIframe';
frame.style.display = 'none';

iframe.onload = function () {
  var $$ = document.getElementById('myIframe').contentWindow.$;
  $$.post('http://data.yueluo.com/getCourses', {}, 
  function () {
    console.log(data);
  });
}

document.body.appendChild(iframe);

3. WINDOW.NAME + IFRAME

window.name的特点

1. 每个浏览器都有一个全局变量window(包括iframe框架和contentWindow)
2. 每个window对象都有一个name属性(一个窗口只有一个name属性)
3. 该窗口被关闭前,所有页面共享一个name属性并且有读写的权限
4. 无论窗口在关闭前,载入什么页面,都不会改变name值
5. window.name可以存储2M的字符串(2M左右)
6. 如果父级窗口地址源和iframe的地址源不同,父级无法通过iframe.contentWindow.name获取值(iframe内部不受影响)

先让iframe的页面程序保存window.name,然后再跳转到与父级窗口同源的另一个页面,父级页面可以从当前的iframe拿到该页面的window.name。

案例
$.post('http://data.yueluo.club/getCourses', {}, function (data) {
  window.name = JSON.stringify(data);
});
var iframe = document.createElement('iframe'),
    flag = false;

var getDatas = function () {
  if (flag) {
    var data = iframe.contentWindow.name;
    console.log(JSON.parse(data));
  } else {
    flag = true;
    setTimeout(function () {
      iframe.contentWindow.location = 'index2.html'; // ?
    }, 500);
  }
}

iframe.src = 'http://data.yueluo.com/index.html'; // 不同源的地址

if (iframe.attachEvent) {
  iframe.attachEvent('onload', getDatas);
} else {
  iframe.onload = getDatas;
}

document.body.appendChild(iframe);

4. POSTMESSAGE + IFRAME

这种方式使用比较少,不常用。

1. 存在兼容性问题
2. 伪造数据端容易泄露数据
3. 容易收到XSS攻击

使用方式:

otherWindow.postMessage(message, targetOrigin);

otherWindow:接收方的引用
message:需要接收方接收的数据
targetOrigin:接收方的源,还必须要有监听的message事件

案例
$.post('http://data.yueluo.club/getCourses', {}, function (data) {
  window.parent.postMessage(JSON.stringify(data), 'http://www.yueluo.club');
});

<iframe src="http://data.yueluo.club/index.html" id="iframe" />

window.onmessage = function (e) {
  var e = e || window.event;
  console.log(JSON.parse(e.data));
}

5. HASH + IFRAME

利用url的hash值来传递数据。明文传输,可以用来传输简单的字符串。

案例
index.html

  <iframe src="http://data.yueluo.club/index.html#getCourses" />
  <button id="btn">获取HASH</button>

  var oBtn = document.getElementById('btn');
  oBtn.onclick = function () {
    console.log(JSON.parse(decodeURI(location.hash.substring(1))));
  }

index2.html

  var self = this;
  setTimeout(function () {
    window.parent.parent.location.hash = self.location.hash.substring(1);
  }, 300);
<iframe src="http://www.yueluo.club/index2.html" id="iframe" />

var hash = location.hash.substring(1),
    iframe = document.getElementById('iframe');

switch (hash) {
  case 'getCourses':
    $.post('http://data.yueluo.club/getCourses', {}, function (data) {
      var str = JSON.stringify(data);
      iframe.src = 'http://www.yueluo.club/index2.html#' + str;
    });
    break;
}

6. CORS跨域

跨域资源共享(Cross-origin resource sharing)

任意域名:

header("Access-Control-Allow-Origin:*");

单域名:

header("Access-Control-Allow-Origin:http://www.yueluo.club");

多域名:

$allowed_origins = array('http://www.yueluo.club', 'http://yueluo.club);
header("Access-Control-Allow-Origin:".$allowed_origins);

7. JSONP跨域

JSONP(JSON with Padding)

跨域请求获取JSON数据的一种非官方的使用模式。

JSON与JSONP

JSON和JSONP不是一个类型;
JSON是一种数据交换格式;
JSONP是一种跨域获取JSON数据的交互技术,是一种非正式的协议;
JSONP抓取的资源并不直接是JSON数据,而是带有JSON数据参数的函数执行;

客户端期望返回的数据是 {“name”: “杨志强”, age: “23”}
JSONP实际返回的数据是 callback({“name”: “杨志强”, age: “23”})

JSONP实现原理

JSONP实际上是借助script标签来实现跨域的。

script标签本身就是用来引入脚本文件的,文件后缀名,对于script标签并不重要,JS会直接解析文本内的内容。文件里写的是什么,就解析什么,比如后缀为.txt的文件,也可以正常解析。

案例
<script type="text/javascript">
  function test1 (data) {
    console.log(data);
  }

  function test2 () {
    console.log('test2');
  }

  function test3 () {
    console.log('test3');
  }
</script>

<script type="text/javascript" id="jsonpScript" 
	src="http://data.yueluo.com/index.js?cb=test1" ></script>
function getParams () {
  var path = document.getElementById('jsonpScript).src,
      callback = path.match(/cb=(.*)/)[1];

  switch (callback) {
    case 'test1':
      $.post('http://data.yueluo.club/getCourses', {}, function (data) {
        test1(data);
      });
      break;
    case 'test2':
      test2();
      break;
    case 'test3':
      test3();
      break;
    default:
      test1();
      break;
  }
}