解决跨域的两种方案JSONP和CORS


讲跨域之前,我们先来讲同源策略(SOP),同源策略是网景公司提出的一个著名安全策略。所谓同源就是域名、协议、端口相同。例如http://www.12306.cn中,http就是超文本传输协议,12306就是域名,cn就是端口。如果两个资源需要通信,那么他必须满足SOP。而在前端中我们使用ajax进行数据请求。
如果资源不同域,那么我们在使用ajax请求数据的时候,就会报错,表示拒绝访问。那如何进行跨域处理呢?事实上有三种方法1、JSONP,2、子域代理,3、CORS。由于第二种方法如今已经采用的非常少,所以我们在这儿不做讲解

一、JSONP(JSON with Padding)

带填充的JSON,是一种可以在JS中绕过同源策略,并发起跨域HTTP请求的使用模式,可以启动JS的跨域HTTP请求
同源策略有一个显著的例外,HTML脚本元素是可以规避SOP检查的。那就意味着我们可以采用动态注入脚本的方式向其他源发出HTTP请求。JSONP正是利用了这个例外情况进行跨域数据加载的。

1、工作原理

我们先来看一个例子:使用ajax请求一个普通的JSON文件。假设你使用ajax请求’http://jsonpjs.com/info.json‘,它会返回一个JSON文档,其中包含一些信息

{
    'title': 'jsonp explaintation',
    'author': 'Cornelius'
}

浏览器接受到这个json文件后,就会把他当成字符串进行处理,但是这个字符串我们需要把它转换为对象,才能够被javascript所使用,这里我们就可以使用json.parse函数来完成。当然由于同源策略的限制,ajax只能够在同一个域中才能够使用。但是正如我们前面提到的,script是html脚本元素它可以规避SOP的检查所以我们为了请求到json文件,我们可以使用这种方式

<script src='http://jsonpjs.com/info.json'></script>

通过script元素请求资源,当文件加载完成时,浏览器会把json响应当作Javascript解析。但是这样的情况下我们还是无法获得json数据。
由于该对象没有被存储,也没有赋值给一个变量,或者作为参数传递给一个函数,浏览器就会忽略它。
那么该如何获取JSON输出呢?
这儿有两种方法,第一种直接由服务器把json数据进行存储。例如有一个外部URL,http://jsonjs.com//info.js(注意文件扩展名是.js而不是.json),内容如下

var jsonResponse = {
    'title': 'jsonp explaintation',
    'author': 'Cornelius'
}

当文件加载完成后,我们就可以通过全局变量jsonResponse访问这个JSON对象了。当该变量包含所请求数据时,我们可以使用script.onload来通知代码。那么另外一种通过一个回调函数代替全局变量的方式来传递JSON对象

jsonHandler({
    'title': 'jsonp explaintation',
    'author': 'Cornelius'
})

使用这种方式的好处在于,我们不需要依靠script元素的onload事件来判断json是否可用,当info.js被解析时,回调便会自动执行。这需要加载<script>元素之前,在全局环境下定义好这个回调函数
通过<script>加载json的方法最大的缺点就是:应用程序加载这些文件时,需要预先知道全局变量或者回调函数的名称。下面是实现jsonp的简单实例

window.jsonpCallback = function (json) {
    // 处理这个json数据
}
var script = document.createElement('script')
script.src = 'http://jsonjs.com//info.js?callback=jsonpCallback'
document.body.appendChild(script)

2、局限性和安全性

这种跨域技术非常的简单和强大,但是他也有一些局限性和安全性
JSONP仅适用于http的get请求。只能使用GET请求就意味着很多限制,提交到服务器的数据量将受限于浏览器的最大URL长度。JSONP缺乏错误处理机制,如果脚本注入成功后,就会调用回调函数,但是注入失败后,没有任何提示。这就意味着,当JSONP遇到404、505或者其他服务器错误时,你是无法检测出错原因的。我们能够做的也只有超时,没有收到响应,便认为请求失败,执行对应的错误回调。
在安全方面,借助JSONP有可能进行跨站请求伪造(CSRF)攻击,当一个恶意网站使用访问者的浏览器向服务器发送请求并进行数据变更时,被称为CSRF攻击。由于请求会携带cookie信息,服务器会认为是用户自己想要提交表单或者发送请求,而得到用户的一些隐私数据

二、CORS

CORS是通过一系列特殊的HTTP头来解决这一问题,这些http头信息可以允许双方判断请求成功或者失败
在发送跨域HTTP请求时,支持CORS的浏览器会引入额外的Origin头信息来指定请求的源。这个头信息需要包含三个部分——协议、域名和端口
Origin: http://www.example.com
服务端的工作是检查头信息是否接受该请求。如果请求被接受,那么需要返回一个包含Access-Control-Allow-Origin,其值与客户端Origin值相同的响应头
Access-Control-Allow-Origin: http://www.example.com
如果资源是公共的允许任何源发送请求,服务器可以返回一个通配符
Access-Control-Allow-Origin: *
如果匹配成功,那么浏览器将会继续处理这个请求,否则禁止该请求。
一个使用CORS发起跨域请求的函数

function makeCORSRequest(url, method) {
    if (typeof XMLHttpRequest === 'undefined') {
        return null
    }
    var xhr = new XMLHttpRequest()
    if ('withCredentials' in xhr) {
        xhr.open(method, url, true)    // 标准浏览器支持cors
    } else if (typeof XDomainRequest !== 'undefined') {
        xhr = new XDomainRequest() // 支持cors的IE浏览器
        xhr.open(method, url)
    } else {
        xhr = null // 不支持CORS的浏览器
    }
}

默认情况下,使用CORS发送请求时,浏览器不会发送任何识别信息,如cookie或者http认证头,为了发送认证信息,必须将XMLHttpRequest对象的withCredentials属性设置为true
var xhr = new XmlHttpRequest()
xhr.withCredentials = true
如果浏览器支持识别信息,那么需要在响应头中返回Access-Control-Allow-Crendentials。
Access-Control-Allow-Crendentials:true
使用Cors时,如果请求方法不是GET,POST或者HEAD,或者使用了自定义的HTTP头,浏览器将会发起所谓的预检请求,预检要求就是判断请求是否合法
客户端会发送以下信息:

Origin---请求的源
Access-Control-Request-Method---Http请求方法
Access-Control-Request-Headers----以逗号分隔的请求自定义头
然后服务器返回的请求头
Access-Control-Allow-Origin----允许的请求源
Access-Control-Allow-Methods-----以逗号分隔的允许方法列表
Access-Control-Allow-Headers------以逗号分隔的允许头信息
Access-Control-Max-Age----预检请求的缓存时间
Access-Control-Allow-Crendentials------指名所请求的资源是否支持认证信息

当客户端收到服务器返回的信息后,就会使用前面申明的http方法进行请求
不过cors预检支持很有限:只有Firefox, Safari和Chrome。
下面是浏览器对CORS的兼容性表格

解决跨域的两种方案JSONP和CORS

第一次写技术文章,不足的地方还请各位多多指教
参考文献:跨域资源共享 CORS 详解 —阮一峰

    [third-party javascript ---- Ben Vinegar Anton Kovalyov][2]


发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>