浏览器同源策略及跨域解决方案 yinshibanxian

什么是浏览器的同源策略

同源指的是”协议+域名+端口”三者相同,即使两个不同的域名指向同一个ip地址,也非

同源。同源策略是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,浏览器很容易受到

XSS,csrf攻击。

同源策略限制以下几种行为:

  • Cookie、LocalStorage和IndexDB无法读取
  • Dom和JS对象无法获得
  • AJAX请求不能发送

常见的跨域场景:

URL 说明 是否允许通信
http://www.domain.com/a.js  
http://www.domain.com/b.js  
http://www.domain.com/lab/c.js 同一域名,不同文件或路径 允许
http://www.domain.com:8000/a.js    
http://www.domain.com/b.js 同一域名,不同端口 不允许
http://www.domain.com/a.js    
https://www.domain.com/b.js 同一域名,不同协议 不允许
http://www.domain.com/a.js    
http://192.168.4.12/b.js 域名和域名对应相同ip 不允许
http://www.domain.com/a.js    
http://x.domain.com/b.js  
http://domain.com/c.js 主域相同,子域不同 不允许
http://www.domain1.com/a.js    
http://www.domain2.com/b.js 不同域名 不允许

主域名和子域名:

主域名: abc.com

wwww.abc.com // www是子域名

bbs.abc.com // bbs是主域名

beijing.bbs.abc.com //beijing.bbs是子域名

haidian.beijing.bbs.abc.com //haidian.beijing.bbs是子域名

主域名是不带www的域名,例如a.com,主域名前面带前缀的通常都为二级域名或多级域名,例如www.a.com其实是二级域名。

跨域解决方案

  1. 通过jsonp实现跨域
  2. 跨域资源共享(CORS)
  3. document.domain+ iframe跨域
  4. location.hash + iframe
  5. window.name +iframe
  6. postMessage跨域
  7. nginx代理跨域
  8. nodexjs中间件代理跨域
  9. websocket协议跨域
一、jsonp实现跨域
  1. 原理

    虽然浏览器默认禁止了跨域访问,但是并不禁止在页面中的script标签引用其他域下的JS文件。

    根据这一点,可以方便地通过动态创建script节点的方法来实现完全跨域的通信。

  2. 代码实现

  • 原生
     <script>
      var script = document.createElement('script');
      script.type = 'text/javascript';
    
      // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
      script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
      document.head.appendChild(script);
    
      // 回调执行函数
      function handleCallback(res) {
          alert(JSON.stringify(res));
      }
     </script>
    
  • vue.js
    this.$http.jsonp('http://www.domain2.com:8080/login', {
        params: {},
        jsonp: 'handleCallback'
    }).then((res) => {
        console.log(res); 
    })
  • 后端node.js实现
    var querystring = require('querystring');
    var http = require('http');
    var server = http.createServer();

    server.on('request', function(req, res) {
        var params = qs.parse(req.url.split('?')[1]);
        var fn = params.callback;

        // jsonp返回设置
        res.writeHead(200, { 'Content-Type': 'text/javascript' });
        res.write(fn + '(' + JSON.stringify(params) + ')');

        res.end();
    });

    server.listen('8080');
    console.log('Server is running at port 8080...');
  1. 缺点

只能实现get一种请求

跨域资源共享(CORS)

普通跨域请求只需服务端设置Access-Control-Allow-Origin即可,前端无需设置,若要

带cookie请求,则前后端要要设置cookie相关配置。由于同源策略的限制,所读取的cookie为

跨域请求接口所在的cookie,而非当前页。若想实现当前页cookie的写入,可以参考下面的方法:

nginx反向代理中设置proxy_cookie_domain和NodeJs中间件代理中cookieDomainRewrite参数的设置。

目前,所有的浏览器都支持该功能,cors已经成为主流的跨域解决方案。

  1. 前端设置
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);
    }
};
  1. 后端设置
var http = require('http');
var server = http.createServer();
var qs = require('querystring');

server.on('request', function(req, res) {
    var postData = '';

    // 数据块接收中
    req.addListener('data', function(chunk) {
        postData += chunk;
    });

    // 数据接收完毕
    req.addListener('end', function() {
        postData = qs.parse(postData);

        // 跨域后台设置
        res.writeHead(200, {
            'Access-Control-Allow-Credentials': 'true',     // 后端允许发送Cookie
            'Access-Control-Allow-Origin': 'http://www.domain1.com',    // 允许访问的域(协议+域名+端口)
            /* 
             * 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现),
             * 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问
             */
            'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'  // HttpOnly的作用是让js无法读取cookie
        });

        res.write(JSON.stringify(postData));
        res.end();
    });
});

server.listen('8080');
console.log('Server is running at port 8080...');

document.domain + iframe跨域

此方案仅限主域相同,而子域不同的跨域应用场景.

实现原理: 两个页面都通过js强制设置document.domain为基础主域,就实现了同域.

  1. 父窗口: (http://www.domain.com/a.html)
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
    document.domain = 'domain.com';
    var user = 'admin';
</script>
  1. 子窗口: (http://www.child.domain.com/b.html)
    <script>
     document.domain = 'domain.com';
     // 获取父窗口中变量
     alert('get js data from parent ---> ' + window.parent.user);
    </script>
    

location.hash + iframe 跨域

  1. 实现原理

a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信.

同时,改变hash值并不会导致页面刷新。

  1. 代码实现

a.html(http://www.domain1.com/a.html)

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 向b.html传hash值
    setTimeout(function() {
        iframe.src = iframe.src + '#user=admin';
    }, 1000);
    
    // 开放给同域c.html的回调方法
    function onCallback(res) {
        alert('data from c.html ---> ' + res);
    }
</script>

b.html:(http://www.domain2.com/b.html)

<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 监听a.html传来的hash值,再传给c.html
    window.onhashchange = function () {
        // 这里的hash可以更改为要向a.html传递的数据
        iframe.src = iframe.src + location.hash;
    };
</script>

c.html:(http://www.domain1.com/c.html)

<script>
    // 监听b.html传来的hash值
    window.onhashchange = function () {
        // 再通过操作同域a.html的js回调,将结果传回
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
    };
</script>
  1. 缺点
  • 数据直接暴露在url中,不安全
  • url有长度的限制,因此传递的数据有限

参考文章

  1. https://segmentfault.com/a/1190000015764619
  2. https://segmentfault.com/a/1190000011145364