一、问题描述:
在某域名下使用Ajax向另一个域名下的页面请求数据,会遇到跨域问题。“已拦截跨源请求:同源策略禁止读取位于 xxxx 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin')。” 浏览器的console中会给出这样的错误提示信息。这源自于浏览器的同源策略。
同源策略是浏览器的一项最为基本同时也是必须遵守的安全策略,毫不夸张地说,浏览器的整个安全体系均建立在此之上。同源策略的存在,限制了“源”自A的脚本只能操作“同源”页面的DOM,“跨源”操作来源于B的页面将会被拒绝。所谓的“同源”,必须要求相应的URI在如下3个方面均是相同的。术语“源(Origin)”在中文表达中显得有点突兀,所以在接下来的内容中,我们更多地会采用“站点(Site)”或者“域(Domain)”这样的说法,在未作特别说明的情况下均与“源”表达相同的意思。
- 主机名称(域名/子域名或者IP地址)
- 端口号
- 网络协议(Scheme,分别采用“http”和“https”协议的两个URI被视为不同源)
1: <script src="http://www.artech.com/scripts/common.js"></script>
2: <script src="http://www.jinnan.me/scripts/utility.js"></script>
除了<script>标签,HTML还具有其它一些具有src属性的标签(比如<img>、<iframe>和<link>等),它们均具有跨域加载资源的能力,所以同源策略对它们不做限制。对于这些具有src属性的HTML标签来说,标签的每次加载都意味着针对目标地址的一次HTTP-GET请求。
同源策略以及跨域资源共享在大部分情况下针对的是Ajax请求。同源策略主要限制了通过XMLHttpRequest实现的Ajax请求,如果请求的是一个“异源”地址,浏览器将不允许读取返回的内容,我们可以通过一个简单的实例来演示这一点。
二、解决方案
最常见的两种方案:1. jsonp跨域请求. 2. CORS(Cross-Origin Resource Sharing)方案。
1. JSONP跨域请求方案:
1)请求type改成了get,JSONP只支持get请求,这个参数在JSONP场景下其实是可以忽略的,即使改成post,也会依然按get模式;
2) dataType改成了jsonp,这个参数标明要采用JSONP方式进行调用;
3)jsonp: “x5callback”,这个参数其实是一个约定的参数名,用于后端按照这个参数名获取一个回调函数名;
4)jsonpCallback:这个参数用来指定上面那个参数对应的回调函数名,如果不指定,jQuery会自动生成一个随机的函数名。
jsonp是一种非官方的解决跨域的方案。
2. CORS方案
这个方案实现起来非常简单,只需要在服务端返回的头部信息中增加:
response.setHeader("Access-Control-Allow-Origin","http://b.test.com")
三、跨域传递cookie
通过修改请求头是可以传递cookie等信息的。但是w3c的标准写的很清楚,cookie,connection和content-length等是不安全的字段,容易导致多种的request smuggling攻击,不允许编程设置。这些字段浏览器会自动帮你设置,如果设置就会报出错误:“Refused to set unsafe header "Content-Length"。
既然ajax跨域中直接设置请求头是不允许的,那么我们就必须在ajax请求发出之前就应该设置cookie,然后ajax请求时会自动去填充请求头header内容(其中cookie内容会自动从硬盘中读取)。同时服务器端也要做些返回头的修改:
response.setHeader("Access-Control-Allow-Credentials","true");
对于JQuery,前端ajax请求可以设置如下:document.cookie=“pin=test;domain=test.com;”;
$.ajax({ url:_url, type:"get", data:"", dataType:"json", xhrFields: { withCredentials: true }, crossDomain: true, 对于Angular,可以设置如下:$http({ method: 'get', url: getBookList, withCredentials: true, data: {s: $scope.smartTablePageSize, i: 1}, }).then(function successCallback(response) { console.log(response); }, function errorCallback(response) { console.log(response); }); 但是我们可能还会遇到另外一个问题,浏览器对每一个跨域请求,会在正式请求之前加上一个OPTIONS请求, 因为这个请求是浏览器自己加上的,所以是不会带上cookies的,我们也无法控制。而如果服务器端不做任何 处理,对于需要回话保持的请求,这个请求会报错(no session)。三、OPTIONS请求
OPTIONS方法是用于请求获得由Request-URI标识的资源在请求/响应的通信过程中可以使用的功能选项。通过这个方法,客户端可以在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务器的性能。
该请求方法的响应不能缓存。
如果这个OPTIONS请求包含一个正文(有Content-Length或Transfer-Encoding存在),则必须有Content-Type来指定媒体类型。虽然规范里没有定义这种正文的用法,但是HTTP将来的扩展可能会用它来查询服务器上更详细的信息。不支持该扩展的服务器可以忽略该请求正文。
如果该URI是一个星号(“*”),OPTIONS请求将试图应用于服务器,而不是某个指定资源。由于服务器的通信选项通常依赖于资源,所以此“*”请求只能作为“ping”或者“no-op”方法;或者用来测试服务器的性能。例如,用来测试HTTP/1.1代理。
如果该URI不是星号,则只能用来获取该资源通信中可用的选项。
得到的200响应应该包含一个头域,指明服务器实现的和适用于该资源的可选特征(如:Allow),可能还包括该规范尚未定义的扩展。如果有响应正文,则应包含关于通信选项的信息。本规范没有定义该正文格式,但可能在HTTO将来的扩展中定义。可以利用内容协商来选择合适的响应格式。如果没有响应正文,响应必须包含Content-Length,并且值为“0”。
请求头的Max-Forwards用来请求特定代理。当代理收到一个允许URI转发的OPTIONS请求,则检查Max-Forwards。如果Max-Forwards值为0,则不能转发该消息;相反,代理会将自己的通信选项去响应。如果Max-Forwards是正整数,代理转发请求的时候会将该值减1。如果请求中没有Max-Forwards,转发的请求也不会有。
OPTIONS请求方法的主要用途有两个:
1、获取服务器支持的HTTP请求方法;也是黑客经常使用的方法。
2、用来检查服务器的性能。例如:AJAX进行跨域请求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全。
所以,服务器端要针对跨域的OPTIOS做特殊的处理,放过对他的session校验,这样它后面的真正的请求才能被发送。