解决跨域请求问题

发布:elantion 日期:2018-07-11 阅读:2397 评论:1

为了更快地把静态资源(html, js, css, png...)传送给用户,大多数公司都会采CDN方式,但CDN一般采用与API接口不一致的域名,于是就会产生跨域的问题。那什么是跨域?为什么会有这问题?有什么影响?怎么解决?

什么是跨域?

只要当前用户访问的地址与资源请求的域名、端口、协议不一致都会产生跨域请求。

例子:

如下图,访问的是domain-a.com页面,但图片和字体是来自domain-b.com,它们的资源请求就会受跨域策略控制:

跨域的限制

像图片<img>, js<script>, css<link>,即便是跨域也是可以访问的(这点很重要,jsonp原理的依据)。但XMLHttpRequest和Fetch就必须受同源策略(Same-origin policy)的控制。

为什么会有跨域问题?

跨域问题只出现在浏览器访问的页面,因为这是浏览器为了保户用户安全而制造的策略。假如没有这层保护,网站就很容易受到跨站伪造请求(CSRF)的攻击。
示例:

/*
     -> 1.浏览 A 银行  -> 2.登录账户 -> 3.A银行在cookie标记用户
用户
     -> 1.浏览 B 恶意网站  -> 2.诱导用户点击  -> 3.获取A网站cookie -> 4.发送“转账”请求给A(含cookie) -> 5.A执行转账
*/

由于浏览器同源策略的限制,一般在第4步就失败了,但有些比较厉害的黑客就有办法绕过该保护,至于怎么防,日后有时间再说。

怎么解决跨域限制?

跨域的XMLHttpRequest(ajax)和Fetch是不能直接请求的,因此我们要采用以下两种办法来“解除限制”:JSONP和CORS,这两方法都有各自的优缺点,也都不是最安全的,最安全的就是屏蔽所有跨域请求,但不太现实。目前来说,业界比较推崇CORS方式,相对较安全。

JSONP

之前说过,通过<script src="http://domain-b.com/xx.js" ></script>方式来加载的脚本是不受限制的,并且加载之后浏览器也能立即执行,这就给我们创造了一个绕过同源策略的“漏洞”。
我们看看同源的请求是怎样的:

// 用户A -> 1、A网站(www.a.com) -> 2、ajax.get -> 3、A服务器(www.a.com)  -> 4、json -> 5、展示用户结果

过程很简单,浏览A服务器上的网站,发送ajax.get的请求给A服务器,A服务器返回json结果给浏览器,最后展示给用户结束。
看看跨域的请求:

// 用户B -> 1、A网站(www.a.com) -> 2、ajax.get -> 3、B服务器(www.b.com)  -> 4、json -> 5、屏蔽,不展示结果

但跨域就没那么顺畅了,到第5步的时候就给浏览器“杀掉”了。接下来我们用JSONP方式就是把第2和第4步进行改造,从而绕过跨域请求。
先把第2步由ajax.get改成<script src="/user/info?callback=jsonpCallback"></script>方式请求,之前我们说过script标签是允许跨域请求的,所以这个请求服务器是能成功返回给浏览器。同时,我们还需要预先定义一个回调函数。

window.jsonpCallback = function(response){
    document.getElementById('userName').innerText = response.name;
};

第4步原本是服务器返回json格式的数据,例如{name: James},使用JSONP后就需要改成jsonpCallback({name: James}),并且content-type改成script/text,当浏览器接受到这个js之后(不再是json了),js就会立即执行第2步写的回调,整个JSONP请求就完成了。
JONP请求方式比较简单,不需要服务器配置很多东西,只要服务器校验机制比较完善,也是很安全的一种方式,而且可以兼容比较低版本的浏览器。
我写了个JSONP的前端包,有兴趣同学可以再深入研究。

/* https://github.com/lazycoffee/lc-jsonp */

CORS

上面用户B访问跨域资源到第5步被浏览器拒绝了,但如果我们告诉浏览器,这些资源(来自B服务器)是允许A网站的请求,那么浏览器就会乖乖放行了,前端只需像平常一样发送请求即可。怎么告诉浏览器呢?我们只要在http的头部增加一些“放行条”就可以了,而这种方式我们就称之为CORS,Cross-resource HTTP request,跨域资源共享。这里我们就将B服务器的资源共享给A网站。相对于JSONP,业界比较推崇CORS方式,但只兼容IE8及以上浏览器,而且IE8需要特殊“照顾”。

支持的浏览器

  1. Chrome 3+
  2. Firefox 3.5+
  3. Opera 12+
  4. Safari 4+
  5. Internet Explorer 8+

请求流程


增加CORS头部信息可以在JAVA代码中加,也可以在Nginx配置文件里加,相对来说,在Nginx里加会比较简单,如下(B服务器):

server{
    listen 80;
    server_name www.b.com;
    location / {
            add_header "Access-Control-Allow-Origin" "http://www.a.com";
            add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS";
            add_header "Access-Control-Allow-Headers" "X-Requested-With, access_token";
            add_header "Access-Control-Allow-Credentials" "true";
            add_header "Access-Control-Expose-Headers" "Date";
            if ($request_method = 'OPTIONS') {
                add_header "Access-Control-Allow-Origin" "http://www.a.com";
                add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS";
                add_header "Access-Control-Allow-Headers" "X-Requested-With, acces_token";
                add_header "Content-Type" "text/plain charset=UTF-8";
                add_header "Content-Length" 0;
                add_header "Access-Control-Max-Age" 1728000;
                return 204;
            }
         proxy_pass http://localhost:8080/;
        }
}

Access-Control-Allow-Origin

允许那个域名使用当前域名资源,例如:http://www.a.com就表示B网站www.b.com的资源(API)允许A网站访问。如果使用了*号,那就表示允许所有网站使用www.b.com的资源。这里只能一次写一个域名,不能使通配符。例如你只能写http://www.a.com, http://src.a.com,而不允许写http://*.a.com。甚到不能省略协议,如果用到https还得另外写,例如http://www.a.com, https://www.a.com

Access-Control-Allow-Methods

允许调用的方法,顾名思意,就是允许何种请求方式访问B服务器的资源了。

Access-Control-Allow-Headers

允许请求时附带的头部信息,例如你发送请求时需要发送access_token头部信息,用于获取用户信息,那么你就得必须加上这个语句。

Access-Control-Allow-Credentials

是否允许请求附带cookie信息,如果你发送的请求必须附带cookie,那么就要加上这个头,否则服务器收不到cookie。

Access-Control-Expose-Headers

允许暴露给WEB页面的头部信息,例如JS有时需要获取服务器返回的Date头,用于校对当前页面的时间,那么就需要加上Date

prefight

当发送ajax请求之前,浏览器会预先发送一个options的请求,用于询问是否允许接下来的ajax请求,如果允许则继续发送,否则就告诉浏览器截止ajax请求,这种行为我们称之为prefight。prefight的cors头与其它get、post的是一样,只是返回时会加大缓存时间,减少不必要的请求,并且直接返回空内容Content-Length:0Status-Code:204

参考文档

部分信息来自stackoverflow,或许不太准确,本文任何地方欢迎修正补充,谢谢!

/**
https://www.html5rocks.com/en/tutorials/cors/
http://enable-cors.org/server_nginx.html
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
*/