2017.12.11-学习笔记:聊聊同源跨域 Jsonp、CORS

96
演员小新
2017.12.11 16:28* 字数 1494

同源策略

  • 指的是浏览器对不同源的脚本或者文本的访问方式进行的限制。比如源a的js不能读取或者设置引入的源b的元素属性

  • 同源的三要素:

    • 相同的协议
    • 相同的域名
    • 相同的端口号
  • 同源策略限制的不同之间的交互主要针对的js中的XMLHttpRequest等请求,下面这些情况是完全不受同源策略限制的

    • 页面中的链接,重定向以及表单提交是不会受到同源策略限制的。
      链接就不用说了,导航网站上的链接都是链接到其他站点的。而你在域名www.a.com下面提交一个表单到www.b.com是完全可以的。
    • 跨域资源嵌入是允许的,当然,浏览器限制了JavaScript不能读写加载的内容。
      如前面提到的嵌入的<script src="..."></script>,<img>,<link>,<iframe>。
  • 存在的意义:

    • 为了保证使用者信息的安全,防止恶意网站篡改用户数据
      ajax同源策略主要用来防止CSRF攻击。如果没有ajax同源策略,相当危险,我们发起的每一次HTTP请求都会带上请求地址对应的cookie,那么可以做如下攻击:
      • 1.用户登录了自己的银行页面 mybank.com , mybank.com 向用户的 cookie 中添加用户标识。
      • 2.用户浏览了恶意页面 evil.com 执行了页面中的恶意ajax请求代码
      • 3.evil.com 向 mybank.com 发起ajax HTTP请求,请求会默认把mybank.com 对应cookie 也同时发送过去
      • 4.银行页面从发送的cookie 中提取用户标识,验证用户无误,response中返回请求数据。此时数据就泄露了
      • 5.而且用于ajax在后台执行,用户无法感知这一过程
  • 限制范围(非同源的网站之间)

    • 无法共享cookie,localstorage,indexDB
    • 无法操作彼此的dom元素
    • 无法发送ajax请求
    • 无法通过flash发送http请求

跨域

  • 指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript事件的安全限制
  • 域名,协议,端口有一个不同都会造成跨域。
    localhost和127.0.0.1虽然都指向本机,但也属于跨域。
    浏览器执行JavaScript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行。

跨域解决办法:Jsonp、CORS、Nginx反向代理

script src 属性 进行 get 请求 js 文件, 不受同源策略限制
script 标签 只是在请求数据, js文本, php响应的也是js文本,所以, src 也可以引入 php 文件
浏览器会将获取过来的文本当成 js 来执行

  • 1、JSONP:
    jsonp其实算一种hack形式的请求
    jsonp的本质其实是请求你一段js代码,是对静态文件资源的请求,所以并不遵循同源策略
    因为是对静态文件资源的请求,所以jsonp只支持get请求,不支持POST请求
    ajax的核心是通过XMLHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本。
    jsonp的诞生
    1.首先,因为ajax无法跨域,然后开发者就有所思考
    2.其次,开发者发现,<script>标签的src属性是可以跨域的,把跨域服务器写成 调用本地的函数,回调数据回来不久好了?
    3.json刚好被js支持(object)
    4.调用跨域服务器上动态生成的js格式文件(不管是什么类型的地址,最终生成的返回值都是一段js代码)
    5.这种获取远程数据的方式看起来非常像ajax,但其实并不一样,便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作jsonp
    6.传递一个callback参数给跨域服务端,然后跨域服务端返回数据时会将这个callback参数作为函数名来包裹住json数据即可。

    • jsonp应用

1.服务端JSONP格式数据
如客户想访问 : http://www.jsonp.com/try/ajax/jsonp.php?jsonp=callbackFunction
假设客户期望返回JSON数据:["customername1","customername2"]。
真正返回到客户端的数据显示为: callbackFunction(["customername1","customername2"])。
服务端文件jsonp.php代码为:

<?php
header('Content-type: application/json');
//获取回调函数名
$callback = $_GET['callback'];
//json数据
$json_data = '["customername1","customername2"]';
//输出jsonp格式的数据
echo $callback . "(" . $json_data . ")";
?>

2.客户端实现 callbackFunction 函数

<script type="text/javascript">
  function callbackFunction(result, methodName) {
    var html = '<ul>';
    for (var i = 0; i < result.length; i++) {
      html += '<li>' + result[i] + '</li>';
    }
    html += '</ul>';
    document.getElementById('divCustomers').innerHTML = html;
  }
</script>

3.客户端页面完整代码

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>JSONP 实例</title>
</head>

<body>
  <div id="divCustomers"></div>
  <script type="text/javascript">
    function callbackFunction(result, methodName) {
      var html = '<ul>';
      for (var i = 0; i < result.length; i++) {
        html += '<li>' + result[i] + '</li>';
      }
      html += '</ul>';
      document.getElementById('divCustomers').innerHTML = html;
    }
  </script>
  <script type="text/javascript" src="http://www.jsonp.com/try/ajax/jsonp.php?callback=callbackFunction"></script>

<!-- script标签可以是动态生成,放到事件中可以触发事件才获取数据
    // 动态创建 script 标签, 请求数据
    var script = document.createElement("script");
    // 设置src请求数据
    script.src = "http://www.jsonp.com/try/ajax/jsonp.php?callback=callbackFunction";
    // 将 script 添加到 头部中去
    document.head.appendChild( script );-->
</body>

</html>

4.jQuery 使用 JSONP

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>JSONP 实例</title>
  <script src="http://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>
</head>

<body>
  <div id="divCustomers"></div>
  <script>
    $.getJSON("http://www.jsonp.com/try/ajax/jsonp.php?callback=?", function(data) {

      var html = '<ul>';
      for (var i = 0; i < data.length; i++) {
        html += '<li>' + data[i] + '</li>';
      }
      html += '</ul>';

      $('#divCustomers').html(html);
    });
  </script>
</body>

</html>
  • 2、跨域资源共享(CORS):
    普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。
    1.前端设置
// 原生ajax

var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4 && xhr.status == 200) {
    alert(xhr.responseText);
  }
}

-------------------------

// jQuery

$.ajax({
  ...
  xhrFields: {
    withCredentials: true // 前端设置是否带cookie
  },
  crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
  ...
});

2.后台设置

  /*
   * 导入包:import javax.servlet.http.HttpServletResponse;
   * 接口参数中定义:HttpServletResponse response
   */
  response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); // 若有端口需写全(协议+域名+端口)
  response.setHeader("Access-Control-Allow-Credentials", "true");
  • 3、nginx反向代理接口跨域
    跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
    实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
    nginx具体配置:
#proxy服务器
server {
  listen 81;
  server_name www.domain1.com;
  location / {
    proxy_pass http: //www.domain2.com:8080; #反向代理
    proxy_cookie_domain www.domain2.com www.domain1.com;#修改cookie里域名
    index index.html index.htm;#当用webpack - dev - server等中间件代理接口访问nignx时, 此时无浏览器参与, 故没有同源限制, 下面的跨域配置可不启用
    add_header Access - Control - Allow - Origin http: //www.domain1.com; #当前端只跨域不带cookie时,可为*
    add_header Access - Control - Allow - Credentials true;
  }
}

1.前端设置:

var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();

2.nodejs后台:

var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
  var params = qs.parse(req.url.substring(2));
  // 向前台写cookie
  res.writeHead(200, {
    'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本无法读取
  });
  res.write(JSON.stringify(params));
  res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
  • 4、webpack-dev-server配置proxy
    webpack 内置了 http-proxy-middleware 可以解决 请求的 URL 代理的问题
    配置:
module.exports = {
  devtool: 'cheap-module-source-map',
  entry: './app/js/index.js'
  output: {
    path: path.resolve(__dirname, 'dev'),
    // 所有输出文件的目标路径
    filename: 'js/bundle.js',
    publicPath: '/',
    chunkFilename: '[name].chunk.js'
  },
  devServer: {
    contentBase: path.resolve(__dirname, 'dev'),
    publicPath: '/',
    historyApiFallback: true,
    proxy: {
      // 请求到 '/device' 下 的请求都会被代理到 target: http://debug.xxx.com 中
      '/device/*': {
        target: 'http://debug.xxx.com',
        secure: false, // 接受 运行在 https 上的服务
        changeOrigin: true
      }
    }
  }
}




Knowledge changes the mind

大前端
Web note ad 1