摘要
XMLHttpRequest 有一个已经不适合下一代web应用程序的安全模型. JSONRequest提出了一个新的浏览器服务, 她允许与任何JSON数据服务器在不暴露任何用户或组织危害的情况下进行双向数据交换. 她在页面脚本和JSON服务器之间进行数据交换. 我们希望浏览器厂商将这一特性加入他们的产品中, 以便改进后续的Web应用程序开发.
动机
下一代Web应用程序更多的会是数据密集型的. 他们需要访问服务器, 任何服务器, 然后交换数据. XMLHttpRequest接口建议这么做, 但是没有实现. 这主要受限于有缺陷的安全模型.
XMLHttpRequest 受到同源策略的限制. 这就限制了接口只能连接提供该页面的服务器. 这一规则是为了处理一些Web架构长期存在的共同的安全缺陷.
如果没有同源策略, 那么用户会受到XSS(跨站脚本)攻击. 在下面的例子中, 我们有一个被恶意创建在priate.net的页面. 该页面会尝试泄露用户与penzance.org之间的关系.
如果同源策略没有起作用, pirate.net 将会通过XMLHttpRequest发送一个请求到penzance.org. 这个请求会带上penzance.org的cookie. 如果penzance.org 使用cookie来作为认证, 那么它将会认为这个请求是用户自己发起的. 任意访问站点的请求将会带上与之关联的cookie. 这将允许priate.net拥有使用penzance.org的权限.
如果penzance.org在防火墙的后面, 而且如果内部服务器嘉定防火墙的存在使得不需要进行显式的认证. 那么pirate.net的页面就可以作为一个代理, 来访问penzance.org的内容并送回pirate.net. 这个可能性是存在的, 因为XMLHttpRequest可以包含类XML数据(比如HTML文档)和非XML文本.
同源策略将会制止这些攻击. 但是同样的, 她限制了很多合法的使用. 页面中的脚本访问其他服务器同时也不暴露用户或组织的危害的情形是存在的.
令人惊讶的是, 同源策略不适用于脚本. 一些开发者开始动态生成<script> 标签来与任何服务器通讯. 服务器返回一段包含数据的脚本. 遗憾的是, 这些代码与原始页面的代码拥有同样的执行权限. 这会允许代码窃取cookie或者直接访问原始服务器. 这是不安全的. 如果penzance.org的一个页面从pirate.net载入了一段脚本, 这段脚本会通过窃取cookie然后发起对penzance.org的请求来泄露penzance.org与用户的关系.
本文提出了一个允许任意页面脚本与任意服务器进行数据交换的安全, 可靠的数据服务. 这将会使得pirate.net的页面从任何服务器获取数据同时又不会对penzance.org产生任何泄露. 而penzance.org也可以在不泄露用户的情况下在它的页面上访问pirate.net的数据.
JSON
JSON是一种数据交换格式, 她是基于JavaScript的一个安全的子集. JSON可以表示简单或复杂结构的数据. JSON不能表示函数或表达式. 她只是严格的数据. 她又非常具体的语法规则. 我们可以很容易的判断一个JSON文本的语法是否有误. JSON文本可以很容易的转换成JavaScript的值, 这使得她非常方便的使用JavaScript. 许多其他编程语言, 包括: C#, Java, Perl, PHP, Python和Ruby 支持JSON. 更多关于JSON的信息可以在 www.JSON.org 上找到.
JSON和XML并不一样, 因此如果将HTML文本传入JSON解析将会报错.
JSONRequest
JSONRequest 是一个全局的JavaScript对象, 她提供了三种方法: post , get 和 cancel.
JSONRequest.post
JSONRequest.post 发起一个HTTP的POST请求, 带上JavaScript的对象或数组, 然后获取服务器响应, 并解析成JavaScript值. 如果parse成功了, 她将值返回给请求的脚本. 在发起请求的时候, 不发送任何HTTP认证信息或者cookie. 任何服务器返回了cookie将会导致请求失败. JSONRequest服务只能用来发送和接受JSON编码的数据. JSONRequest不会接受其他文本格式.
JSONRequest.post 有四个参数:
参数 类型 描述
url 字符串 POST的URL地址, URL不一定是相对于页面的.
send 对象 POST请求发送的JavaScript对象或数组. 她将会被序列化成JSON文本. 循环结构将会失败.
done 函数 回调函数格式: function (requestNumber, value, exception). 当请求完成后, 该函数会被调用. 如果请求成功, 函数会受到请求数字(request number)和返回值. 如果不成功, 会受到请求数字和一个异常对象. done函数仅仅在调用JSONRequest返回序列号后才会被调用.
timeout 数值 等待响应的时间, 单位: 毫秒. 这个参数是可选的, 默认值是: 10000 (10 秒).
如果请求的参数被接受, JSONReqeust.post 返回一个序列号. 如果请求被拒绝, 她会抛出一个 JSONRequestError 异常. 以下情况下请求会被拒绝:
  • `url`: 不是一个合法的URL字符串.
  • `send` 值无法被序列化. 如果不是对象或数组或者它是循环结构(函数和对象不会包含在序列化中).
  • `done`: 不是一个函数.
  • `timeout`: 超时时间不是一个正整数.
请求数字可以用来匹配请求和响应. 这可以方便那些不习惯使用函数值和闭包的程序员, 也可以用来取消请求.
例子:
requestNumber = JSONRequest.post(
"https://json.penzance.org/request",
{
user: "[email protected]",
t: "vlIj",
zip: 94089,
forecast: 7
},
function (requestNumber, value, exception) {
if (value) {
processResponse(value);
} else {
processError(exception);
}
}
);
在 JSONRequest.post 校验参数后, 它会将请求加入队列然后返回一个请求数字. 在请求返回后, `done` 函数会被调用.
没有cookie或者隐式的认证信息会被通过POST传送. 任何认证信息必须放在请求数据或者URL中. 从`send`数据序列化来的JSON文本将会被作为请求主体. 字符编码是UTF-8. 一个具体的实现可以选择用gzip来压缩JSON文本.
请求可以使用HTTP或者HTTPS. 这个选择与页面安全性无关的.
POST /request HTTP/1.1
Accept: application/jsonrequest
Content-Encoding: identity
Content-Length: 72
Content-Type: application/jsonrequest
Host: json.penzance.org

{"user":"[email protected]","forecast":7,"t":"vlIj","zip":94089}
在服务器确认请求后, 它可以在限定时间内返回响应. 如果时间超时了或者链接在整个响应发送完成前丢失了, 请求就会失败. 如果HTTP状态码不是200 OK, 请求也会失败.
HTTP/1.1 200 OK
Content-Type: application/jsonrequest
Content-Length: xxxx
响应的内容是UTF-8编码的JSON文本. 如果文本包含JSON编码错误, 请求将失败.
如果请求成功, `done`函数将会被调用, 并传入请求数字和JSON文本解析后的值. 如果请求失败, `done`函数同样会被调用, 并传入请求数字和一个异常对象, 以表示通讯失败. 如果服务器希望传递应用级别的错误, 那么它需要返回HTTP 200 OK, 并返回一个JSON文本.
浏览器必须能够保持对每个主机每个页面开放至少两个请求. 超出的请求可以被放入队列. 浏览器应该尝试保持连接活动. 这些连接与浏览器读取HTML页面以及相应资源的链接是独立的.
JSONRequest.get
JSONRequest.get 发起一个HTTP的GET请求, 然后获取服务器响应, 并解析成JavaScript值. 如果parse成功了, 她将值返回给请求的脚本. 在发起请求的时候, 不发送任何HTTP认证信息或者cookie. 任何服务器返回了cookie将会导致请求失败. JSONRequest.get服务只能用来接受JSON编码的数据. JSONRequest不会接受其他文本格式.
JSONRequest.get 带三个参数:
参数 类型 描述
url 字符串 POST的URL地址, URL不一定是相对于页面的.
done 函数 回调函数格式: function (requestNumber, value, exception). 当请求完成后, 该函数会被调用. 如果请求成功, 函数会受到请求数字(request number)和返回值. 如果不成功, 会受到请求数字和一个异常对象. done函数仅仅在调用JSONRequest返回序列号后才会被调用.
timeout 数值 等待响应的时间, 单位: 毫秒. 这个参数是可选的, 默认值是: 10000 (10 秒).
如果请求的参数被接受, JSONReqeust.get 返回一个序列号. 如果请求被拒绝, 她会抛出一个 JSONRequestError 异常. 以下情况下请求会被拒绝:
  • `url`: 不是一个合法的URL字符串.
  • `done`: 不是一个函数.
  • `timeout`: 超时时间不是一个正整数.
请求数字可以用来匹配请求和响应.
例子:
requestNumber = JSONRequest.get(
"https://json.penzance.org/request",
function (requestNumber, value, exception) {
if (value) {
processResponse(value);
} else {
processError(exception);
}
}
);
在 JSONRequest.get 校验参数后, 它会将请求加入队列然后返回一个请求数字. 在请求返回后, `done` 函数会被调用.
没有cookie或者隐式的认证信息会被通过GET传送. 任何认证信息必须放在请求的URL中.
请求可以使用HTTP或者HTTPS. 这个选择与页面安全性无关的.
GET /request HTTP/1.1
Accept: application/jsonrequest
Host: json.penzance.org
响应的内容是UTF-8编码的JSON文本. 如果文本包含JSON编码错误, 请求将失败.
如果请求成功, `done`函数将会被调用, 并传入请求数字和JSON文本解析后的值. 如果请求失败, `done`函数同样会被调用, 并传入请求数字和一个异常对象, 以表示通讯失败. 如果服务器希望传递应用级别的错误, 那么它需要返回HTTP 200 OK, 并返回一个JSON文本.
具体的实现可以将GET请求的返回结果进行缓存.
JSONRequest.clear
调用 JSONRequest.clear 允许我们从 GET 缓存中清除指定URL的缓存文档. 不返回任何内容. 用这个函数我们无法知道文档之前是否已经在缓存里.
JSONRequest.clear(url);
JSONRequest.cancel
通过调用 JSONRequest.cancel , 我们可以将指定请求数字的请求取消. 函数没有任何返回. 无法保证请求不被发送到服务器, 因为有可能在调用cancel之前请求已经被发送了.
JSONRequest.cancel(requestNumber);
如果请求仍然在队列中, 那么会将他从队列中删除.
如果请求已经在处理过程中了, 那么会尝试终止她.
如果找不到对应的请求, 那么取消操作将会被忽略.
如果请求被成功取消了, 那么该请求关联的`done`回调函数会被调用, 并且传入一个带有"canceled"的异常消息.
存在这样的可能性: 在Server的角度上看, 事务正常完成. 但是在client角度上看, 是被取消的.
HTTP头字段
Accept
JSONRequest唯一的accept类型是: application/jsonrequest. 用这个独立的了性可以防止JSONRequest与那些假定防火墙足以保护他们免受意外的Web访问的老旧的系统进行通讯.
Content-Type
JSONRequest唯一的Content-Type是: application/jsonrequest.
Content-Encoding
内容编码可以是: identity(默认值)或gzip.
Exceptions
异常可能在JSONRequest函数或者`done`回调函数被调用的时候产生. 异常对象包含了一个值为: JSONRequestError 的`name`属性, 还有一个包含具体错误信息的`message`属性.
{name: "JSONRequestError", message: "error message"}
下面是一些可能的消息:
消息 含义
"bad URL" URL格式不对导致无法发起请求.
"bad data" `send` 数据不是对象或者数组, 或者有循环结构, 或者大小太大.
"bad function" 回调函数不是接受三个参数的有效的函数.
"bad timeout" `timeout`参数不是一个正整数.
"bad ok" 服务器返回的不是HTTP 200 OK
"no response" 服务器不返回任何数据或者请求超时.
"bad response" 服务器返回的数据不是有效的JSON文本. 或者带了意想不到的HTTP头部信息.
"canceled" 请求被取消.
延迟
当请求由于(通过传入 "not ok" , "no response" 或 "bad response" 给`done`回调函数)失败后, 延迟将会被添加到后续所有请求中. 这是为了阻止DOS攻击, 定时分析攻击和穷举攻击.
每次失败都将增加500毫秒的延迟, 并加上一个0~511的随机时延. 每次成功都会将延迟减少10毫秒知道延迟为0为止. 每个页面保持自己的时延值.
取消将会增加20毫秒的时延
安全性
JSONRequest 有一些特性可以避开同源策略.
  • JSONRequest 不在HTTP头里发送或接收任何cookie或者密码. 这避免了假授权的情况. 知道一个网站的名字仍然无法可以使用它网站的证书.
  • JSONRequest仅使用JSON文本. JSONRequest 不能被用来访问旧数据或者文件或者脚本. 这避免了假定访问授权足够的网站内部的攻击. 如果编码不是UTF-8的, 那么请求将会失败. 次优的别名和代理将会失败. 响应内容如果不是严格的JSON格式同样会导致请求失败. 如果服务器不用JSON来响应POST请求, 请求也会失败.
  • 如果响应不包含JSONRequest的Content-Type, 请求也会失败. 这可以确保JSONRequest不会从不安全的老旧服务器中获取数据.
  • JSONRequest 只会泄露很少的错误信息. 在一些情况下, 攻击者企图通过错误细心来获取有用的信息. JSONRequest 不返回这些信息给脚本. 她可能会通过日志或者其他途径将这些信息告知给用户, 而不是脚本可以直接访问的.
  • 如果之前的请求失败了, JSONRequest 在发送新请求之前, 会将时延进行累积. 这可以防止定时分析攻击和拒绝服务攻击.
JSONRequest 只做一件事: 她在JSON服务器与页面脚本之间交换数据. 她提供了非常有价值的服务, 同时又不会引入新的安全问题.
防火墙内德浏览器有访问内部服务器(penzance.org)的能力. 而防火墙外面的电脑不行. 那么外部电脑(pirate.net)是不是可以让浏览器作为它与内部服务器通讯的代理呢?
受限于同源策略, XMLHttpRequest 不允许pirate.net页面上的脚本与penzance.org通讯.
JSONRequest允许这样的访问, 但她有一些限制:
  • 两个方向的 Content-Type 必须都是: application/jsonrequest.
  • POST的数据是JSON格式的.
  • 响应的数据是JSON格式的.
  • 两边的编码都必须是UTF-8, 这是严格限制的.
那对于那些接受POST的老旧应用程序应该怎么办? JSONRequest 会不会被不巧当的用来POST到这些应用程序从而破坏数据库? JSONRequest通过不发送任何cookie和HTTP认证信息来减少这种危险.
相比form.submit, 它会在POST的时候发送cooke和HTTP认证信息. JSONRequest 比form.submit更安全. 通过使用仅仅响应良好格式的JSONRequest, 应用程序将变得更安全.
全双工
JSONRequest 被设计支持全双工连接. 她允许服务端的应用程序异步传输数据. 这是通过同时使用两个请求来完成的: 一个发送数据, 一个接受数据. 通过使用`timeout`超时机制, POST请求可以保持等到状态, 直到服务器决定她有及时的数据发送.
全双工连接可以用于实时通知程序, 如: 流程管理和财务.她也可以用在协同应用上, 如: 即时消息、即时邮件、聊天、游戏、演示和共享的应用程序.
原文: JSONRequest
地址: http://www.json.org/JSONRequest.html