RESTful集合资源-惯用的JSON表示和往返



我有一个名为Columns的集合资源。带有Accept: application/jsonGET不能直接返回集合,所以我的表示需要将其嵌套在一个属性中:-

{ "propertyName": [
{ "Id": "Column1", "Description": "Description 1" },
{ "Id": "Column2", "Description": "Description 2" }
]
}

问题:

  1. 对于上面的标识符propertyName最好的名称是什么?应该是:

    • d(即d是一个既定的约定,还是特定于某些特定的框架(MS WCF和MS ASP.NET AJAX?)
    • 结果(即是既定惯例还是特定于某些特定规范(MS OData)?)
    • (即,顶级属性应该有一个明确的名称,它有助于消除我将通用application/json用作媒体类型的歧义)

    NB我觉得应该有一些东西来包装它很舒服,正如@tuesptre所指出的,XML或任何其他表示都会迫使你在某种程度上包装它

  2. 当PUT返回内容时,是否应该保留所述属性中的相同包装[考虑到出于安全原因实际上没有必要,而且传统的JSON使用习惯可能是删除PUT和POST的嵌套,因为它们不需要防范脚本攻击]?

    我的直觉告诉我,它应该像其他表示一样对称,但可能有现有技术可以丢弃d/*结果**[假设这是第1部分的答案]*

    或者PUT back(或POST)应该放弃对包装属性的需求,只使用:-

    [{"Id":"第1列","Description":"Description 1"},{"Id":"Column2","Description":"Description 2"}]
    • 如果希望添加根级元数据,根级元数据会放在哪里
    • 一个编写POST的人怎么知道它需要对称

EDIT:我特别感兴趣的是一个答案,它有一个合理的理由,特别考虑了JSON对客户端使用的影响。例如,HAL注意定义一个对两个目标表示都有意义的绑定。

编辑2:尚未接受,为什么到目前为止,这些答案没有被引用,也没有任何让它们比我在搜索前20名中选出的内容更突出的地方。我是不是太挑剔了?我想我是(或者更可能的是,我只是问不出正确的问题:D)。有点疯狂的是,一周零3天,即使有(公认的微不足道的)奖金,仍然只能获得123次浏览(其中3个答案还不错)

更新的答案

针对你的问题(而不是在我最初的回答中偏离一点切线:D),以下是我的意见:

1) 我对此的主要看法是我不喜欢d。作为一个使用API的客户,我会感到困惑。它到底代表什么?数据

其他选项看起来不错。Columns很好,因为它将用户请求的内容镜像回用户。

如果您正在进行分页,那么另一个选项可能是类似pageslice的选项,因为它向客户端表明,他们不会接收集合的全部内容。

{
"offset": 0,
"limit": 100,
"page" : [
...
]
}

2) TBH,我不认为你对此采取哪种方式有多大区别,但如果是我,我可能不会麻烦寄回信封,因为我认为没有任何需要(见下文),为什么要让请求结构比需要的更复杂?

我觉得把信封寄回去会很奇怪。POST应该允许您将项目添加到集合中,那么为什么客户端需要投递信封才能做到这一点呢?

从RESTful的角度来看,将信封推回是有意义的,因为它可以被视为更新与整个集合相关的元数据。我认为值得思考的是,你将在信封中公开什么样的元数据。我认为所有适合这个信封的东西(如分页、聚合、搜索方面和类似的元数据)都是只读的,所以客户端将其发送回服务器是没有意义的。如果你发现自己的信封里有很多数据,客户端可以变异,那么这可能是一个迹象,可以将这些数据分解为一个单独的资源,并将列表作为子集合。垃圾示例:

/animals

{
"farmName": "farm",
"paging": {},
"animals": [
...
]
}

可以分解为:

/farm/1

{
"id": 1,
"farmName": "farm"
}

/farm/1/animals

{
"paging": {},
"animals": [
...
]
}

注意:即使有了这种拆分,你仍然可以使用类似Facebook或LinkedIn的字段扩展语法将两者组合作为一个单独的响应返回。例如http://example.com/api/farm/1?field=animals.offset(0).limit(10)

作为回应,对于您的问题,即客户端应该如何知道它们的POSTing和PUTing应该是什么样子的JSON负载,这应该反映在您的API文档中。我不确定是否有更好的工具可以实现这一点,但Swagger提供了一个规范,允许您使用JSONSchema来记录您的请求体应该是什么样子——查看此页面了解如何定义您的模式,以及查看此页面如何将它们作为body类型的参数进行引用。不幸的是,Swagger还没有在它的高级web UI中可视化请求体,但它是开源的,所以你可以随时添加一些东西来实现这一点。

原始答案

查看William在该页面讨论线程中的评论-他提出了一种完全避免漏洞的方法,这意味着你可以安全地在响应的根处使用JSON数组,然后你不必担心你们中的任何一个问题。

您链接到的漏洞利用依赖于您的API使用Cookie来验证用户的会话-只需使用查询字符串参数,即可删除该漏洞利用。无论如何,这样做可能是值得的,因为在API上使用Cookie进行身份验证不是很RESTful-您的一些客户可能不是网络浏览器,可能不想处理Cookie。

为什么这个修复有效

该漏洞是CSRF攻击的一种形式,依赖于攻击者能够将他/她自己页面上的script标记添加到API上的敏感资源。

<script src="http://mysite.com/api/columns"></script> 

受害者网络浏览器会将存储在mysite.com下的所有cookie发送到您的服务器和您的服务器,这看起来像是一个合法的请求-您将检查session_idcookie(或您的服务器端框架所称的cookie),并查看用户是否通过了身份验证。请求将如下所示:

GET http://mysite.com/api/columns
Cookie: session_id=123456789;

如果您更改API,则忽略Cookie并使用session_id查询字符串参数,攻击者将无法欺骗受害者web浏览器将session_id发送到您的API。

一个有效的请求现在看起来是这样的:

GET http://mysite.com/api/columns?session_id=123456789

如果使用JavaScript客户端进行上述请求,则可以从cookie中获取session_id。从另一个域使用JavaScript的攻击者将无法做到这一点,因为您无法获得其他域的cookie(请参阅此处)。

现在我们已经解决了这个问题,并且忽略了session_id cookie,攻击者网站上的脚本标签仍然会发送类似的请求,并显示如下GET行:

GET http://mysite.com/api/columns

但是,由于GET缺少所需的session_id查询字符串参数,服务器将以403 Forbidden进行响应。

如果我没有为此API对用户进行身份验证,该怎么办

如果你没有对用户进行身份验证,那么你的数据就不可能是敏感的,任何人都可以调用URI。CSRF应该不是一个问题,因为在没有身份验证的情况下,即使您阻止了CSRF攻击,攻击者也可以直接调用您的API服务器端来获取您的数据,并以他/她想要的任何方式使用它。

  1. 我会选择'd',因为它清楚地将资源的"信封"与其内容分开。这也将使消费者更容易解析您的响应,而不是在能够访问给定资源的包装属性之前"猜测"该属性的名称。

  2. 我想你说的是两件不同的事情:

    • POST请求应在application/x-www-form-urlencoded中发送。如果您选择在回复中包含新创建的资源的表示,那么您的回复基本上应该反映GET。(在HTTP中不是强制性的)
    • PUT绝对应该与GET对称。PUT请求的目的是用另一个资源表示替换现有的资源表示。让两个请求共享相同的约定是有道理的,不是吗
  1. 选择"Columns",因为它在语义上有意义。这有助于思考JSON和XML如何相互镜像。

  2. 如果你想把集合放回去,你也可以使用相同的媒体类型(语法、格式,你会怎么称呼它。)

最新更新