JSONP分享-- 在JavaScript中跨域请求

如果你正在开发一个现代的基于web的应用程序,那么你:

  1. 在客户端使用JavaScript。
  2. 需要集成那些没有完全在你控制之下的服务(或者那些来自不同的域)。
  3. 在你的浏览器控制台中遇到过这个错误信息:

XMLHttpRequest cannot load http://external.service/. No ‘Access-Control-Allow-Origin‘ header is present on the requested resource. Origin ‘http://my.app‘ is therefore not allowed access.

每次我需要使用一些不能完全控制的外部服务或一些服务器API集成web 应用程序,我就会碰到这个错误。Google还没有给我提供了关于这个问题一个简洁的描述或替代执行跨域请求的概述,所以这篇文章将作为将来个人参考。

同源策略

我们之所以遇到这个问题,是因为我们违反了同源策略(SOP)。这是在浏览器实行的一个安全措施,用来限制不同源之间的文档(或脚本)的交互。

一个网页的同源是由其定义协议、域名和端口号决定的。例如,本页面的源是(‘http’,‘jvaneyck.wordpress.com’,80).具有相同源的资源可以完全互相访问。如果页面A和页面B拥有相同的源,那么页面A上的JavaScript可以执行HTTP请求到页面B的服务器,操纵页面B的DOM,甚至读取到页面B的cookies。注意,源是有网页的源地址定义的。阐明:从另一个域加载的javascript源文件(例如,从远程CDN引用的jQuery)将在HTML页面的源中运行,这个HTML是包含scrip的页面,而不是javascript文件来自的域。

对于特定的跨域HTTP请求,SOP规定以下一般规则:允许跨域写,禁止跨域读取。这意味着如果A和C是不同的源,A发送的HTTP请求会由C正确地接收(这些就是“写”),但是A中的脚本将无法读取任何数据--甚至来自C返回的响应代码。这就是跨域“读取”,并被浏览器屏蔽,导致出现上面的错误。换句话说,SOP不阻止攻击者向他们源写数据,它只是不允许他们读取来自你的域的数据(cookie, localStorage 或其他)或利用从他们域接收的响应来做任何事。

SOP是一件非常好的事情TM。它可以防止恶意脚本读取你的域的数据,并把其发送到他们的服务器。这意味着一些脚本小子将不能那么轻易地窃取cookies。

执行跨域请求

然而,有时你必须有意识地执行跨域请求。提示:这将需要一些额外的工作。合法的跨域请求示例:

  • 你必须集成第三方服务(如一个论坛),有一个REST API驻留在不同源。
  • 服务器端服务托管在不同的(子)域。
  • 客户端逻辑来自不同源而不是服务器端服务端点。
  • 。。。

取决于你在服务器端的控制数量,你有多个选项来启用跨域请求。我将讨论可能的解决方案:JSONP, 使用服务器端代理和CORS。

还有其他选择,使用最广泛的的技术是使用iframes和window.postMessage。我不会再本文讨论,但是对这些感兴趣的可以点击这里

示例代码

我写了一些代码来尝试使用不同方法,这些均会在本文讨论。你可以在我们的github中查看完整代码。他们应该很容易在本地运行,如果你想自己尝试的话。每个例子有2个网站,一个网站在源(‘http’,‘localhost‘,3000)另一个在(‘http’,‘localhost‘,3001)。他们是不同的源,所以3000请求3001被认为是跨域请求并被浏览器默认屏蔽。

失败的跨域请求

考虑以下场景:A域的页面想要执行一个GET请求到域B的页面。这就是所发生的:

浏览器把请求正确的发送到服务器:

GET / HTTP/1.1

服务器返回响应:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 57

{
"response": "This is data returned from the server"
}

然而在接收到响应,浏览器屏蔽响应进一步传播,并显示同源违反错误如上所示。例如,如果你使用jQuery,对GET请求进行done()回调将永远不会执行,你将无法读取从服务器返回的数据。

JSONP

JavaScript Object Notation with Padding(简称JSONP)是一种执行跨域请求的方法,通过利用HTML页面的script 标签可以加载来自不同域的情况。在我们进入细节之前,我想说它又一些重大的问题:

  • JSONP只能用来执行跨域GET请求。
  • 服务器必须明确地支持JSONP请求。
  • 你必须绝对地信任服务器提供的JSONP响应。
  • 如果服务器被盗用,JSONP可以使你的网站暴露大量的安全漏洞

JSONP依赖这样的事实:<script>标签可以有来自不同域的资源。当浏览器解析<script>标签,它会GET请求脚本内容(来自任何源)并在当前的页面中执行。通常,服务器会返回HTML或一些显示为数据格式的数据如XML或JSON。然而当向一个启用JSONP的服务器请求时,它会返回一个脚本块,这个脚本块执行一个回调函数,函数已在页面中指定,提供的实际数据作为参数。以防你的脑袋爆炸了,下面的示例会更具体。

源3000的页面想要获取存储在源3001的资源。源3000页面包含下面的script标签:

<script
    src=‘http://localhost:3001?callback=myCallbackFunction‘>
</script>

当浏览器解析这个script标签,它将正常的发出GET请求:

GET /?callback=myCallbackFunction HTTP/1.1

服务器没法返回原生JSON,而是返回一个脚本块,包含一个对一个函数的调用,函数名在URL中指定,输出的数据作为参数传递。

HTTP/1.1 200 OK
Content-Type: application/javascript

myCallbackFunction({‘response‘: ‘hello world from JSONP!‘});

这个脚本块在浏览器接收到后就立即被执行。在脚本块里的函数调用是在当前页面中评价的。当前页面定义了回调函数,它使用返回的数据:

<script>
    function myCallbackFunction(data){
        $(‘body‘).text(data.response);
    }
</script>

总结:

  • 由于JSONP的工作是通过包含一个script标签(无论是纯HTML或编程方式)是由GET请求获取的,它只支持跨域的HTTP GET请求。如果你想使用其他的HTTP请求(像POST, PUT或DELETE),就不能使用JSONP方法。
  • 这个方法要求你必须完全地信任服务器。这个服务器可能被盗用,并返回任意代码,将在你的页面中执行(因此允许访问你的网站cookies, localStorage,等等)。你可以使用frames和window.postMessage执行跨域调用来减轻这种危险。关于具体实现的方法,可以查看这个例子

服务器端代理

另一个绕过同源策略执行跨域请求是不做任何跨域请求的!如果你使用了一个代理,这个代理有你的域名,你可以简单地使用它在后端访问外部服务,并把结果返回给你的客户端代码。因为请求代码和代理在同一个域中,所以不违反同源策略。

这种技术不需要改变现有的服务器端代码。它需要有服务器端代理服务,且在相同的域中同样在浏览器中运行JavaScript代码。

为了完整性,我展示一个简短例子:

不是直接向http://localhost:3001执行GET请求,我们想自己域的代理服务器发送请求。

GET /proxy?urlToFetch=http%3A%2F%2Flocalhost%3A3001 HTTP/1.1

服务器将执行实际的GET请求外部服务。服务器端代码可以正常的执行跨域请求而不会发生错误,因此可以成功的调用。代理服务将结果输送给客户:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"response": "This is data returned from the server, proxy style!"
}

注意这种方法也有一些严重的缺点。例如,我们还没有在这篇文章中涉及与安全相关的主题。如果第三方服务使用cookies进行身份严重,那么你就不能使用这种方法。你自己的JavaScript代码是不能访问外部的域的cookies并且cookies也不能发送给你的代理服务,所以不能像第三方服务提供包含用户凭据的cookies。

CORS

你可能正感觉一个轻微的恶心。如果你觉得之前的机制都有“hacky”味道,那么你是绝对正确的。前面的方法都是绕过合法的浏览器安全机制并且绕过它总有写脏。

幸运地是,存在一个更干净的方法: Cross-Origin Resource Sharing(或者简称CORS).

CORS为服务器提供了一个机制来告诉浏览器域A读取请求自域B的数据是可以的。它是通过在响应头中包含一个新的Access-Control-Allow-Origin http 头。如果你还记得引入的错误信息,这正是浏览器试图告诉你的。当浏览器接收到跨域的响应时,它会检查CORS 头。如果响应头中指定的源匹配当前源,它允许读取访问响应。否则,你会得到讨厌的错误信息。

一个具体的例子。

像往常一样请求源3000:

GET / HTTP/1.1

源3001的服务器检查是否这个源可以访问数据,并在响应中增加额外的Access-Control-Allow-Origin头,列出请求源:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Content-Type: application/json; charset=utf-8
Content-Length: 62

{
"response": "This is data returned from the CORS server"
}

当浏览器接收到响应时它比较请求源(3000)和列在Access-Control-Allow-Origin头的源(也是3000)。由于他们匹配,浏览器允许源3000的代解释执行响应。

像之前一样,这种方法有一些局限性。例如IE比较老的版本只能部分支持CORS. 同时,对所有请求除了最简单请求,你必须有双倍的HTTP请求(参考:preflighting CORS requests)。

总结

在这篇文章中,我试图说明什么类型的请求被分类为跨域请求和在同源策略下他们为什么会被浏览器屏蔽。此外,我讨论了几种机制用来执行跨域请求。下表总结了这些机制。

机制 支持HTTP方法 服务器端修改要求 附注
JSONP GET Yes(返回script块,包含函数调用替代元素JSON) 需要完全信任服务器
Proxy ALL No(但是你的源需要一个额外的代理组件)  服务器后端执行请求,而不是浏览器。可能会产生进行身份验证的问题
CORS ALL Yes(返回额外的HTTP 头)  IE较老的版本不支持。更“复杂”的请求,需要额外的HTTP调用(preflighted 请求)

正如你看到的,即使最简单的跨域请求没有银弹。如果你已经控制服务器代码且你不需要支持遗留的浏览器,我强烈建议你使用CORS方法。一如既往,评你当前的需求,使用最适合你的方法。

译文:Cross-Domain requests in Javascript

很多地方翻译的不对,欢迎指正。

时间: 2024-07-30 10:04:34

JSONP分享-- 在JavaScript中跨域请求的相关文章

js中跨域请求原理及2种常见解决方案

一.同源策略: 说到跨域请求,首先得说说同源策略: 1995年,同源政策是由 Netscape 公司引入浏览器的.目前,所有浏览器都实行了这个政策. 同源策略是浏览器的一种安全策略,所谓同源是指,域名,协议,端口完全相同:目的就是为了保证用户信息的安全,防止恶意的网站窃取数据,防范跨站脚本的攻击,禁止客户端脚本(如 JavaScript)对不同域的服务进行跨站调用. 举例来说,http://www.example.com/dir/page.html 这个网址协议是 ttp://域名是 www.e

javascript跨域请求解决方法总结

javascript中有同源策略,javascript存在跨域通信的问题.典型例子如:Ajax无法直接请求跨域的普通文件,存在跨域无权限访问的问题. 几种常见的解决方法: JSONP  2.HTML5 postMessage 方法  3.document.domain + iframe  4.iframe+location.hash 一.JSONP web页面上只有<script><img><iframe>这些拥有"src"属性的标签是拥有跨域能力的

php中ajax跨域请求---小记

php中ajax跨域请求---小记 前端时间,遇到的一个问题,情况大约是这样: 原来的写法: 前端js文件中: $.ajax({ type:'get', url:'http://wan.xxx.com/xxx.js', success:function(data){ ......... } }) 很简单的一个ajax请求:后面在做迁移的时候,由于系统目录的安排,js文件放到了,img.xxx.com域名下,这样就设计到了跨域问题,于是就改成了这样: $.ajax({ type:'get', ur

JavaScript跨域请求和jsonp请求实例

<script type="text/javascript" src="./whenReady.js"></script> <script type="text/javascript"> /** * 一:跨域请求 * * 这个常见的JavaScript模块查询有href属性但没有title属性的所有<a>元素 * 并给他们注册onmouseover事件处理程序 * 这个事件处理程序使用XMLHttp

跨域请求之jQuery的ajax jsonp的使用解惑

转自:http://www.cnblogs.com/know/archive/2011/10/09/2204005.html 前天在项目中写的一个ajax jsonp的使用,出现了问题:可以成功获得请求结果,但没有执行success方法,直接执行了error方法提示错误——ajax jsonp之前并没有用过,对其的理解为跟普通的ajax请求差不多,没有深入了解:出现了这种错误,几经调试(检查后台的代码和js部分的属性设置)还是不行,让我感觉很是意外和不解.于是,决定仔细研究下ajax jsonp

JSonP跨域请求

我们在通过自己的页面或程序通过ajax请求其它网站或服务时,会存在一个ajax直接请求普通文件存在跨域无权限访问的问题,甭管你是静态页面.动态网页.web服务.WCF,只要是跨域请求,一律不准.不过我们又发现,Web页面上调用js文件时则不受是否跨域的影响(不仅如此,我们还发现凡是拥有"src"这个属性的标签都拥有跨域的能力,比如<script>.<img>.<iframe>).   于是可以判断,当前阶段如果想通过纯web端(ActiveX控件.服

Ajax 跨域请求 jsonp获取json数据

遇到Ajax的跨域请求出问题 找了中解决办法如下: 参考内容:http://justcoding.iteye.com/blog/1366102 由于受到浏览器的限制,该方法不允许跨域通信.如果尝试从不同的域请求数据,会出现安全错误.如果能控制数 据驻留的远程服务器并且每个请求都前往同一域,就可以避免这些安全错误.但是,如果仅停留在自己的服务器上,Web 应用程序还有什么用处呢?如果需要从多个第三方服务器收集数据时,又该怎么办? 理解同源策略 同源策略阻止从一个域上加载的脚本获取或操作另一个域上的

跨域请求之JSONP

跨域恳求的方法有很多种, 1,iframe 2,document.domain 3,window.name 4,script 5,XDomainRequest (IE8+) 6,XMLHTTPRequest (Firefox3.5+) 7,postMessage (HTML5) 8,后台代理 ... 它们有各自的优缺点,回来的数据格局也各不同,应根据需要慎重挑选.比方iframe回来html片段就对比适合,费老劲用它回来JSON就因小失大了.这篇开端我将打造一个有用的跨域恳求东西Sjax.运用s

Nginx反向代理、CORS、JSONP等跨域请求解决方法总结

由于 Javascript 同源策略的存在使得一个源中加载来自其它源中资源的行为受到了限制.即会出现跨域请求禁止. 通俗一点说就是如果存在协议.域名.端口或者子域名不同服务端,或一者为IP地址,一者为域名地址(在跨域问题上,域仅仅是通过“ url的首部 ”来识别而不会去尝试判断相同的IP地址对应着两个域或者两个域是否同属同一个IP),之中任意服务端旗下的客户端发起请求其它服务端资源的访问行动都是跨域的,而浏览器为了安全问题一般都限制了跨域访问,也就是不允许跨域请求资源. 但很多时候我们却又不得不