我知道的跨域与安全

关于跨域,有两个误区javascript

1. ✕ 动态请求就会有跨域的问题html

✔ 跨域只存在于浏览器端,不存在于安卓/ios/Node.js/python/ java等其它环境java

2. ✕ 跨域就是请求发不出去了python

✔ 跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了jquery

之因此会跨域,是由于受到了同源策略的限制,同源策略要求源相同才能正常进行通讯,即协议、域名、端口号都彻底一致。ios

以下图所示:nginx

这三个源分别因为域名、协议和端口号不一致,致使会受到同源策略的限制。web

同源策略具体限制些什么呢?ajax

1. 不能向工做在不一样源的的服务请求数据(client to server)canvas

这里有个问题以前也困扰了我好久,就是为何home.com加载的cdn.home.com/index.js能够向home.com发请求而不会跨域呢?其实home.com加载的JS是工做在home.com的,它的源不是提供JS的cdn,因此这个时候是没有跨域的问题的,而且script标签可以加载非同源的资源,不受同源策略的影响。

2. 没法获取不一样源的document/cookie等BOM和DOM,能够说任何有关另一个源的信息都没法获得 (client to client)


为何会有同源策略呢?

1. 为何要限制不一样源发请求?

假设用户登录了bank.com,同时打开了evil.com,若是没有任何限制,evil.com能够向bank.com请求到任何信息,进而就能够在evil.com向bank.com发转帐请求等。

若是这样,为何不直接限制写,只限制读?

由于若是连请求都发不出去了,那就不能作跨域资源共享了,没法读取返回结果,evil.com就没法继续下一步的操做,如获取转帐请求的一些必要的验证信息。

2. 为何限制跨域的DOM读取?

若是不限制的话,那么很容易就能够假装其它的网站,如套一个iframe或者经过window.open的方法,从而获得用户的操做和输入,如帐户、密码。

另外,添加这个http头能够限制别人把你的网站套成它的iframe:

X-Frame-Options: SAMEORIGIN


同源策略提供了安全的同时也形成了不方便,由于有时候咱们须要跨域请求,如获取第三方提供的服务信息,因为第三方的源和本网站的源不同,因此这个时候就受到跨域的限制。

跨域最经常使用的方法,应当属CORS,以下图所示:

只要浏览器检测到响应头带上了CORS,而且容许的源包括了本网站,那么就不会拦截请求响应。

CORS把请求分为两种,一种是简单请求,另外一种是须要触发预检请求,这二者是相对的,怎样才算“不简单”?只要属于下面的其中一种就不是简单请求:

(1)使用了除GET/POST/HEAD以外的请求方式,如PUT/DELETE

(2)使用了除Content-Type/Accept等几个经常使用的http头

这个时候就认为须要先发个预检请求,预检请求使用OPTIONS方式去检查当前请求是否安全,以下图所示:

代码里面只发了一个请求,但在控制台看到了两个请求,第一个是OPTIONS,服务端返回:

返回头里面包含了容许的请求头、请求方式、源,以及预检请求的有效期,上图是设置了20天,在这个有效期内就不用再发一个options的请求,实际上浏览器有一个最长时间,如Chrome是5分钟。若是在预检请求检测到当前请求不符合服务端设定的要求,则不会发出去了直接抛异常,这个时候就不用去发“复杂”的请求了。

如本源不在容许的源范围内,则会抛异常,没法获取返回结果:

为了支持CORS,nginx能够这么配:

location / {
     if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204;
     }
     add_header 'Access-Control-Allow-Origin' '*';
     add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
     add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
     add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
}复制代码


第二种经常使用的跨域的方法是JSONP,JSONP是利用了script标签可以跨域,以下代码所示:

function updateList (data) {
    console.log(data);
}

$body.append(‘<script src=“http://otherdomain.com/request?callback=updateList"></script>');复制代码

代码先定义一个全局函数,而后把这个函数名经过callback参数添加到script标签的src,script的src就是须要跨域的请求,而后这个请求返回可执行的JS文本:

// script响应返回的js内容为
updateList([{
    name: 'hello'
}]);复制代码

因为它是一个js,而且已经定义了upldateList函数,因此能正常执行,而且跨域的数据经过传参获得。这就是JSONP的原理。


因此因为script/iframe/img等标签的请求默认是能带上cookie(cookie里面带上了登录验证的票token),用这些标签发请求是可以绕过同源策略的,所以就能够利用这些标签作跨站请求伪造(CSRF),以下面代码所示:

// 转帐请求 <iframe src="http://Abank.com/app/transferFunds?amount=1500&destinationAccount=..."></iframe> // 配置路由器添加代理 <img src="http://192.168.1.1/admin/config/outsideInterface?nexthop=123.45.67.89" style="display:none">复制代码

若是相应的网站支持GET请求,或者没有作进一步的防御措施,那么若是用户在另一个页面登录过了,再打开一个“有毒”的网站就中招了。

而动态ajax请求默认是不带cookie的,若是你要带cookie,能够设置ajax的一个属性withCredentials,以下代码所示:

// 原生请求
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open("GET", "http://otherdomain.com/list");
xhr.send();

// jquery请求
$.ajax({
   url: "http://otherdomain.com/list",
   xhrFields: {
      withCredentials: true
   }
});

复制代码

这个时候就和img/script标签同样,能带上cookie,而且还支持除GET以外的其它方式。因此这种方式也是能实现CSRF的,以下图所示:

因此若是转帐请求只是不支持GET,没作其它的防御措施,仍然有CSRF攻击的风险。那怎么办呢?

方法一是每次请求都要在参数里面显示地带上token即登录的票,虽然跨域请求能带上cookie,可是经过document.cookie仍然是获取不到其它源的cookie的,因此攻击者没法在代码里面拿到cookie里面的token,因此就没办法了。方法一的缺点是会暴露token,因此须要带token的最好不能是GET,由于GET会把参数拼在url里面,用户可能会无心把连接发给别人,但不知道这个连接带上了本身的登录信息。

方法二是每次转帐请求前都先请求一个随机串,这个串只能用一次转帐或者支付请求,用完就废弃,只有这个串对得上才能请求成功,攻击者是没法拿到这个串的,由于若是跨域请求带cookie,浏览器要求Access-Control-Allow-Origin不能为通配符,只能为指定的源,如:

Access-Control-Allow-Origin: http://renren.com
因为攻击者所在的域名不在这个源里面,因此它是没法获得请求结果,因此请求不到随机串。所以这种方式也是能够避免CSRF攻击。
假设Allow-Origin为*,ajax设置withCredentials为true时,浏览器会抛异常,没法获得返回结果:
另外服务还须要指定Allow-Credentials的头部,以下代码所示:
add_header "Access-Control-Allow-Origin" "http://fedren.com";
add_header "Access-Control-Allow-Credentials" "true";复制代码

关于cookie还有两个地方值得注意,以下图所示:


讨论完了client to server,咱们再讨论client to client,即 如何和一个frame通讯,包括iframe或者使用window.open打开的页面。

iframe访问父页面可经过window.parent获得父窗口的window对象,经过open打开的能够用window.opener,进而获得父窗口的任何东西;父窗口若是和iframe同源的,那么可经过iframe.contentWindow获得iframe的window对象,若是和iframe不一样源,则存在跨域的问题,这个时候可经过postMessage进行通信。

使用postMessage的基本原理以下图所示:

// main frame
let iframeWin = document.querySelector("#my-iframe")
                .contentWindow;
iframeWin.postMessage({age: 18}, "http://parent.com");
iframeWin.onmessage = function(event) {
    console.log("recv from iframe ", event.data);
};

// iframe
window.onmessage = function(event) {
    // test event.origin
    if (event.origin !== expectOrigin) {
        return;
    }
    console.log("recv from main frame ", event.data);
};

window.parent.postMessage("hello, this is from iframe ", "http://child.com");
复制代码

以页面嵌入youtobe视频为例,经过如下代码能够在页面嵌入一个youtobe视频,嵌入的是一个跨域的iframe,因此就涉及到如何和iframe进行通讯的问题。如怎么知道iframe的状态,触发父页面定义的事件onPlayerReady,这个是iframe通知父页面,而父页面能够调player.stopVideo控制iframe的行为,这个是父页面通知iframe。

iframe通知父页面是经过window.parent.postMessage,同时监听message事件:

经检查上面代码4304行的c就是window.parent,这个embed-player.js是iframe的js,iframe的js经过postMessage发送了一个消息,如上图右边的窗口所示,而后在父窗口的widgetapi.js就收到了这个消息。

一样地,父窗口的JS也是使用postMessage向iframe发送消息,以下图所示:

固然postMessage不限于跨域,同域的也可使用,只是同域的话能够经过window对象互相操做,你可能须要额外定义一些全局变量或者函数供其它frame使用,或者是定义一套事件机制(能够借助原生事件/jQuery/Vue事件等)。

这里有一个特例,就是子域如mail.hello.com要跨hello.com的时候,能够显式地设置子域的document.domain值为父域的domain:

document.domain = "hello.com";复制代码
就不会有跨域的问题了。

补充一点,若是须要和同源的不一样标签页进行通讯可使用localStorage,即一个页面设置localStorage,其它页面就会触发storage事件:
window.addEventListener('storage', function(e) {
    e.key;
    e.oldValue;
    e.newValue;
    e.url;
    e.storageArea;
});
复制代码

这个我没试过,读者能够试一下。

再补充一点,websocket是不受同源策略限制的,没有跨域的问题。CSS的字体文件是会有跨域问题,指定CORS就能加载其它源的字体文件(一般是放在cdn上的)。而canvas动态加载的外部image,也是须要指定CORS头才能进行图片处理,不然只能画不能读取。


最后,跨域分为两种,一种是跨域请求,另外一种访问跨域的页面,跨域请求能够经过CORS/JSONP等方法进行访问,跨域的页面主要经过postMesssage的方式。因为跨域请求不但能发出去还能带上cookie,因此要规避跨站请求伪造攻击的风险,特别是涉及到钱的那种请求。

相关文章
相关标签/搜索