授权和构建 RESTful 后端的正确方法是啥

Posted

技术标签:

【中文标题】授权和构建 RESTful 后端的正确方法是啥【英文标题】:What is the correct way to authorize and structure a RESTful backend授权和构建 RESTful 后端的正确方法是什么 【发布时间】:2013-02-14 17:52:32 【问题描述】:

很多关于 RESTful Web Services 的例子都没有考虑到当今许多应用程序都是多用户的问题。

想象一个暴露 RESTful API 的多用户后端。后端数据架构使用共享数据库和共享模式。每个表都将包含对tenant_id 的引用:

+-------------+----+-----------------+
|  tenant_name| id |   shared_secret |
+-------------+----+-----------------+
|         bob |  1 |   2737sm45sx543 |
+-------------+----+-----------------+
|       alice |  2 |   2190sl39sa8da |
+-------------+----+-----------------+

+-------------+----+-------+-----------+
|    pet_name | id |  type | tenant_id |
+-------------+----+-------+-----------+
|       fuffy |  1 |   dog |         1 |
+-------------+----+-------+-----------+
|       kerry |  2 |   cat |         2 |
+-------------+----+-------+-----------+

问题 1三个或更多客户端应用程序(即 Android、iOS 和 Web App)与 RESTful 后端交互,您将如何针对后端执行身份验证?

RESTful backend, API, HTTP-Verbs, shared database and schema 
|
|
+---- Web Application (Client 1)
|     |
|     + Alice
|     |
|     + Bob
|
+---- android Application (Client 2)
|     |
|     + Alice
|     |
|     + Bob
|
+---- ios Application (Client 3)
|     |
|     + Alice
|     |
|     + Bob
|

每个客户都应该允许 Alice 和 Bob 管理她/他的宠物。每个客户端都是一个 GUI,它将使用(在内部,发出 HTTP 请求)后端。问题:每个客户端如何才能对后端进行身份验证?

假设 HMAC(完全是 RESTful,没有会话):此方法涉及使用共享密钥对有效负载进行签名(从不通过网络发送)。每个客户端是否应该拥有自己的tenant 表副本(其中包含shared_secret 字段)?

Android App -> Client Sign -> Signed Request -> Backend -> Result
    Web App -> Client Sign -> Signed Request -> Backend -> Result

问题 2:资源 URI 应该是什么样的?

以下是获取 Bob 宠物的两种可能性:

可能性 #1:Authorization 标头为您提供租户的(唯一)名称:

GET /pets HTTP/1.1
Host: www.example.org
Authorization: bob:c29kYW9kYSBhb2lzYWRoIGYgZDUzNDUz

可能性 #2。 tenant_id 作为查询参数发送:

GET /pets/tenant_id=1 HTTP/1.1
Host: www.example.org
Authorization: bob:c29kYW9kYSBhb2lzYWRoIGYgZDUzNDUz

【问题讨论】:

小心使用短语多租户。我不相信它意味着你认为它的作用。在此处查看详细信息msdn.microsoft.com/en-us/library/aa479086.aspx @Gaz_Edge 这意味着在客户端之间虚拟分区数据,并且单个实例将为更多客户端提供服务,这就是我正在做的事情。你同意吗? 我认为你过于复杂了。您有 1 个 Web 服务、n 个 Web 服务客户端和 x 个用户,是吗? @Gaz_Edge 完全正确。你会怎么简化呢? 我在回答中提供了一个简单的方法。你觉得我的回答不会给你你想要的吗?如果是这样,请解释一下,我会尽力提供帮助。 【参考方案1】:

第 1 部分

(大声思考:您是否已经决定使用 HTTP 和 HMAC?如果是,您为什么要问我们?)

我建议使用带有基本身份验证的 HTTPS。简单的。毕竟Stripe 已经够用了。

参考资料:

Securing an API: SSL & HTTP Basic Authentication vs Signature 有关 HMAC 与替代品的良好比较,请参阅 What is HMAC Authentication and why is it useful? AviD on "Is BASIC-Auth secure if done over HTTPS?" 指出的一个警告“SSL 只保护网络服务器 - 任何内部路由、服务器日志记录等都将看到明文密码。”

更新:以下是有关如何处理身份验证的一些额外细节:

    每个客户端应用程序都将使用 API 密钥联系服务。使用 HTTPS 和基本身份验证,客户端将提供其 API 密钥作为基本身份验证用户名。它不需要提供密码,因为它使用的是 HTTPS。您需要为每个应用程序(Web 应用程序、Android、iOS)分配一个 API 密钥,我看到两种方法:

    A.一种选择是为每个用户提供一个跨客户端共享的 API 密钥。

    B.另一种选择是为每个客户提供一个独特的应用程序。)

    但是首先如何获得客户端的密钥呢?构建“密钥请求”API 端点。我建议给每个客户端一个“starter”键,它只用于联系这个端点。 (启动密钥不授予其他访问权限。)当用户第一次使用客户端时,他/她必须进行身份验证。客户端将此传递给“密钥请求”端点,以便它可以生成与用户关联的密钥。从那时起,每个客户端都有一个客户端绑定的 API 密钥。

第 2 部分

考虑为每个租户分配一个子域。如果您使用 Rails(或任何现代 Web 堆栈),您可以使用子域来查找租户 ID。然后你的 API 可以这样使用:

GET http://tenant1.app.co/pets
GET http://tenant2.app.co/pets
GET http://tenant3.app.co/pets

参考(特定于 Rails,但应该对整个 Web 堆栈都有帮助):

Writing Multi-Tenant Applications in Rails Adding multi-tenancy to your Rails app: acts_as_tenant Multitenancy in Rails

注意:正如您的示例所示,为简单起见,我不会为不同的租户重复使用相同的宠物 ID。例如,以下是一个简单的方法:

GET http://tenant1.app.co/pets/200
GET http://tenant2.app.co/pets/201
GET http://tenant3.app.co/pets/202

我所描述的方法比传递tenant_id 作为查询参数要干净得多。此外,使用tenant_id 作为参数感觉不对。正如我在 Ruby 和 Richardson 的“RESTful Web Services”中所读到的,我喜欢将参数用于更多“算法”的东西。

参考资料:

Multi-Tenant Database Architecture – Part 5 "如果查询字符串参数是作为算法的资源的输入,则它们是合适的 否则,应将这些值移到 URI 中”-Resource Oriented Architecture on Wikipedia

【讨论】:

我喜欢第 2 部分,我会选择子域。但是第 1 部分没有解释针对后端验证每个应用程序的挑战。也许是我的英语不好,我无法深入解释这个问题。我选择了 HMAC,因为请求之间没有状态。您为什么建议使用 Basic? 我建议使用 HTTPS + 基本身份验证,因为这是最简单的方法。如果您想深入了解所有优缺点,我提供了参考资料。 @Gremo,我刚刚添加了一些关于第 1 部分(身份验证)的额外细节。 感谢您的更新,我已经结束了赏金。如果您有时间,我很乐意看到您使用 HMAC 解释的答案的第一部分。【参考方案2】:

您所说的“多租户”是指应用程序/Web 服务用户吗?多租户通常意味着更复杂的事情msdn.microsoft.com/en-us/library/aa479086.aspx。

您需要使用您的网络服务对每个用户进行身份验证。这可以通过 SSL 上的基本 http 身份验证来完成。

从 Web 服务的角度来看,您将对所有三个客户端执行相同的身份验证。该服务不关心客户的类型 - 这就是重点。您可能需要为您的客户提供不同的表示,例如Xhtml 或 JSON。我喜欢保持简单,总是选择 JSON。

对于资源,管理它们的最简单方法是将用户资源作为顶层,然后为每个用户将所有资源链接在一起,例如

GET users/fred/pets - returns all pets for user fred
GET users/fred/pets/sparky - returns details on freds pet sparky

这样做的好处是您可以添加代码来授权每个请求,例如您可能有两个用户,fred 和 jack。两个用户都将通过身份验证,但您应该只允许 fred 请求他的资源,而让 jack 请求他的资源。您只需要在您的 api 中添加授权检查,例如从 URI 中获取用户名,获取经过身份验证的用户的用户名,检查它们是否相同。如果不返回类似 http 403 禁止,如果它们相同,则允许请求。

我认为如果您仍然不清楚,您需要阅读 REST 的详细信息。到目前为止,关于这个主题的最好的书是这本书RESTful Web Services。它从第一原则涵盖了 REST。它还有一个非常好的部分,介绍了设计资源以及如何管理用户和多个客户端。

【讨论】:

我将编辑问题以使其更清楚。同时:我只有一个 Web 服务(RESTful 后端)和n 可能的客户端(应用程序)在使用它。用户(租户)将使用n 客户端之一(可能更多,即webapp 和android 客户端)。 我想给 Gaz 一个赞成票,这样她/他就没有 666 分。【参考方案3】:

我不确定是否理解这个问题,因为在这种情况下,多租户似乎有点矫枉过正。 但是我可以尝试回答第二个问题。

REST 是一种“基于资源”的架构,您必须意识到/pets/pets/?tenant=1 不是指同一个资源:

/pets指的是当前用户的宠物, /pets/?tenant=1 指 Bob 的宠物。

虽然两种解决方案都没有错,但通常最好选择第二种解决方案。 URI 确实是为共享而设计的,与每个用户不同的抽象“我的宠物”相比,您有更多理由共享“鲍勃的宠物”(即使它需要身份验证和授权才能表示)。

请参阅Should resource ids be present in urls? 进行类似讨论...

【讨论】:

【参考方案4】:

如果 HTTP 是所有 3 种客户端类型都使用的协议,您应该只需要实现一种身份验证方案。您可以选择毒药 - Basic、Digest、Oauth2 或 Cookie 是一些常用的方法。由于它通过 HTTP,我认为没有理由复制此逻辑。根据您的平台,可能有几个框架会为您抽象这一点。

要区分客户端类型,可以使用 HTTP 标头user-agent 可能已经使这成为可能。另一种选择是使用您定义的自定义标题。任何 HTTP 客户端都可以设置标头,任何服务器都可以处理自定义标头。一个体面的 Web 框架将使您可以相对轻松地使用这些框架。作为后端服务,我怀疑您会希望统一处理所有客户端请求 - 或尽可能多地处理。当然,维护一个后端优于三个

至于您的 API 应该是什么样子 - 这完全取决于您。为了保持 RESTful,how to GET a cup of coffee 非常值得一读 - 插入到 Roy Fielding's thesis 的强制性链接。很有可能,您真正需要的是编写“资源丰富”链接的指南。

在您列出的选项中,如果用户可能需要访问系统中的所有宠物,更喜欢第二个,/pets?userId=bob。如果用户只需要访问他们的宠物,则首选第一个 /pets

【讨论】:

以上是关于授权和构建 RESTful 后端的正确方法是啥的主要内容,如果未能解决你的问题,请参考以下文章

为用户与其他实体交互构建 URL 的 RESTful 方式是啥?

在 RESTful API 中创建和验证实体模型及其 DTO 的正确方法是啥?

使用来自 Django 后端的数据创建图表/图形的最有效方法是啥

分离资源服务器和授权服务器的正确方法是啥?

使用 ToolbarItem(placement: .principal) re: largeTitle 的正确方法是啥?

在 ReactJS 中验证来自后端的数据的正确位置是啥?