WEB知识: 同源策略介绍以及规避方法

所谓同源策略(Same origin policy),其实就是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

何谓同源策略:

同源即是指域名、协议与端口相同,不同源的客户端脚本(JavaScript、ActionScript)在没明确授权的情况下,不能读写对方的资源。详细情况见下图:

同源策略的目的:

它的目的是为了保证用户信息的安全,防止恶意的网站窃取数据。

试想一下,如果你登录了某个人空间网站,该网站保存了你登录状态的cookie,而一个恶意网站可以随意读取该网站的cookie,那么事情就大条了,如果你没有退出登录状态,调皮一点的黑客可能会冒充你去发一张抠脚大汉卖萌的图片,吓坏你的朋友,而别有用心的黑客可能就会窃取你的个人信息去做一些违法乱纪的事情,Web世界将毫无安全可言!

同源策略具体限制那些行为:

总体来看。对于非同源情况,目前有如下严格限制:

a.Cookie、LocalStorage 和 IndexDB 无法读取

b.DOM 无法获得

c.AJAX 请求不能发送

虽然同源策略是为了用户安全考虑,但是用户的某些合理的请求却也遭到了限制,带来了一些不方便之处,所以,怎么样才能在合理情况下规避同源策略呢?

规避方法:

跨域资源共享CORS

CORS含义:

CORS是一个W3C标准,它允许浏览器跨越服务器,发出AJAX请求,从而克服AJAX的同源限制。

CORS与用户无关,浏览器会自动检测AJAX请求,若该请求跨域,则自动附加一些必要的请求头,无需用户多加操作,它实现的关键点在于服务器,只要服务器实现了CORS接口,那么就可以实现CORS跨源通信。

CORS请求分类:

对于一个CORS的HTTP请求来说,根据请求方法和HTTP请求头的不同,浏览器将其分为“简单请求”和“非简单请求”两种方式。

简单请求:满足以下两个条件的即为简单请求

a、请求方法为HEAD、GET、POST中的一种;

b、HTTP请求头不超过这些字段Accept、Accept-Language、Content-LanguageLast-Event-ID、Content-Type(只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)

非简单请求:不同时满足上面两个条件的请求即为非简单请求

CORS简单请求实现方法:

a、用户发起跨源AJAX请求

b、浏览器检测该请求。若该请求为简单请求,则在请求头信息内添加如下字段并发送:

Origin: 协议+域名+端口
Origin字段:指明本次请求来自哪里

c、服务器接收到信息,检测该Origin字段是否在许可范围内

d、若该字段在许可范围内,则在响应头信息内添加如下字段后返回:

Access-Control-Allow-Origin: 协议+域名+端口
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers:字段名称
Access-Control-Allow-Origin:(必须)该字段值有两种情况,与请求头Origin字段相同,表示同意该域名跨域访问,或者为一个星号“*”,表示接收任意域名访问。

Access-Control-Allow-Credentials:(可选)该字段值为一个布尔值,默认为true,表示服务器允许浏览器将cookie与请求一并发送给服务器。该字段有且仅有这一个值,若服务器不同意发送Cookie给自己,则删除该字段即可。另外,该字段必须搭配“withCredentials”属性使用。“withCredentials”属性默认为false,表示浏览器发送请求时不发送cookie凭据。若要发送,需设置为true,且“Access-Control-Allow-Origin”字段的值不可以为“*”,必须指定明确的、与请求网页一致的域名。总而言之,“withCredentials”属性告诉AJAX是否发送cookie凭据,“Access-Control-Allow-Credentials”告诉服务器是否接受cookie凭据,发送时,二者缺一不可。

Access-Control-Expose-Headers:(可选)该字段值为响应头的字段名称,默认情况下,XMLHttpRequest对象的getResponseHeader()方法只能获得6个基本字段。该值可以指定getResponseHeader()方法可以获得的其他字段的名称。

浏览器检测响应头有包含“Access-Control-Allow-Origin”字段,证明跨域成功,获得信息。

e、若该字段不在许可范围内,则不添加任何信息,正常返回HTTP响应

浏览器检测响应头没有包含“Access-Control-Allow-Origin”字段,证明跨域失败,抛出错误,该错误可被AJAX的onerror回调函数捕获。

CORS非简单请求实现方法:

a、用户发起跨源AJAX请求

b、浏览器检测该请求,若该请求为非简单请求,则浏览器在正式通信前发起一次“预检”请求

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
预检请求使用“options”方法,表示该请求为询问请求
一个关键字段“origin”:表示正式通信请求来自哪里

“Access-Control-Request-Method”:表示正式通信请求需要用到的请求方法

“Access-Control-Request-Headers”:表示正式通信请求需要使用的额外的头信息

c、服务器检测该预检请求提供的三个字段

d、若同意跨源通信,则在响应头信息内添加如下字段后返回:

Access-Control-Allow-Origin:协议+域名+端口
Access-Control-Allow-Methods:请求方式
Access-Control-Allow-Headers: 额外的请求头
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 预检请求有效期
Access-Control-Allow-Origin:(必须)该字段值有两种情况,与请求头Origin字段相同,表示同意该域名跨域访问,或者为一个星号“*”,表示接收任意域名访问。

Access-Control-Allow-Methods:(必须)该字段值为服务器允许的所有跨域方法,不限于预检中请求的字段,避免了请求方法不同而造成的多次预检。

Access-Control-Allow-Headers:(分情况)若请求头包含“Access-Control-Request-Headers”字段,则为必须,若请求头未包含“Access-Control-Request-Headers”字段则不需要。该字段指明服务器支持的所有头信息字段。

Access-Control-Allow-Credentials:(可选)与简单请求含义相同,具体见上文。

Access-Control-Max-Age:(可选)该字段表明了预检的有效期,在有效期内相同请求不必再次预检。

浏览器检测响应头有包含“Access-Control-Allow-Origin”字段,证明服务器同意该跨域通信,执行f步骤。

e、若不同意跨源通信,则不添加任何信息,正常返回HTTP响应

浏览器检测响应头没有包含“Access-Control-Allow-Origin”字段,证明服务器不同意该跨域通信,抛出错误,该错误可被AJAX的onerror回调函数捕获。

f、d步骤完成,证明同意跨源通信,则浏览器开始发送正式请求,具体方法与简单请求相同。


JSONP

JSONP含义:

JSONP(JSON with Padding)是JSON的一种“使用模式”,它是一个简单高效的跨域方式,HTML中的script标签可以加载并执行其他域的javascript,于是我们可以通过script标记来动态加载其他域的资源

JSONP实现原理:

用我觉得最通俗易懂的方式来概括就是:本地有一个函数,它拥有一个参数,这个参数就是我们要跨域获得的信息。我们通过<script>标签加载跨域的JS文件,加载时传递两个参数,告诉它第一个是我们需要什么数据,第二个是它需要执行的函数的名字,现在远程的JS文件就会将我们需要获得的信息以JSON的形式作为参数传递到我们告诉的函数里面,并执行这个函数。即远程JS传参数给了本地函数,这样本地函数就获得了我们想要的数据。

专业概括为:首先本地定义回调函数,然后网页动态插入<script>元素,由它向跨源网址发出请求,跨源网址src内包含两个参数(callback、info),callback值为回调函数名字,info值为所要查询的信息。跨域服务器收到请求后,将所请求的信息数据以JSON的形式放于回调函数的参数位置返回。回调函数以调用对象的方式进行使用参数。

具体请看如下代码:

本地:

//动态创建<script>标签的函数
function addScriptTag(src){
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}
//页面加载完毕时执行addScriptTag()函数
window.onload = function () {
  addScriptTag('协议+域名+端口?info=needInfo?callback=callbackName');
}
//回调函数
function callbackName(data) {
  console.log('远程返回的JSON数据为:'+data);
}
跨域服务器:
callbackName({"infoOne":"one","infoTwo":"two",……});
具体看到这里,相信大家对基本的实现原理已经清楚了,至于服务器端怎么实现根据参数来调用回调函数的方法就不在这里进行演示了,无非就是根据判断拼接字符串罢了。
JSONP的优缺:

a、它的优点非常显著,主要表现在它简单适用,老式浏览器全部支持,服务器改造非常小

b、它的缺点相信大家也看出来了,由于是通过<script>标签来实现,所以它只能发送GET请求

c、还有一点就是判断它请求是否成功不容易,目前大部分框架都是通过结合超时的时间来判定的

好了,JSONP大概就介绍到这里了。啥啥啥?你用的是JQuery?好吧,再给你们说一下JQuery的实现方法吧

JSONP的JQuery实现方法:

jQuery(document).ready(function(){
    $.ajax({
        //请求方式
        type: "GET",
        //默认为true,表示异步请求,false表示同步请求
        async: true,
        //跨域请求的地址
        url: "协议+域名+端口?info=needInfo",
        //返回的数据类型
        dataType: "jsonp",
        //传递给请求处理程序或页面,用以获得jsonp回调函数名的参数名(一般默认为:callback)
        jsonp: "info",
        //传递给请求处理程序或页面,用以获得jsonp回调函数名的参数名的值
        data:"needInfo",
        //自定义jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据
        jsonpCallback:"callbackName",
        //成功调用函数
        success: function(data){
            alert('远程返回的JSON数据为:'+data);
        },
        //异常处理函数
        error: function(){
            alert('Fail!');
        }
    });
});
这就是JQuery执行JSONP时所用到的一些基本步骤方法。其中注意一点,我们虽然给予了回调函数名称,但是并没有写回调函数,这是因为JQuery自动帮你生成了回调函数,并且将参数提取出来供success方法来使用。哇,老铁,意不意外,惊不惊喜?

document.domain

还有一种比较简单的方法,它是通过修改document的domain属性,让我们可以在域和子域或者不同的子域之间通信。

同域策略认为域和子域隶属于不同的域,比如www1.xxx.com和 www2.xxx.com是不同的域,这时,我们无法在www1.xxx.com下的页面中调用www2.xxx.com中定义的JavaScript方法。但是当我们把它们document的domain属性都修改为xxx.com,浏览器就会认为它们处于同一个域下,那么我们就可以互相调用对方的method来通信了。并且二者可以共享cookie!

好了,规避方法暂时就总结这比较常用的三种,还有一些比较实用的有HTML5新引进的跨文档API(window.postMessage)或者WebSocket等之后再进行总结