779

RESTful 身份验证是什么意思,它是如何工作的?我在 Google 上找不到很好的概述。我唯一的理解是您在 URL 中传递了会话密钥(remeberal),但这可能是非常错误的。

4

14 回答 14

619

如何在 RESTful 客户端-服务器架构中处理身份验证是一个有争议的问题。

通常,它可以在 SOA over HTTP 世界中通过以下方式实现:

  • 基于 HTTPS 的 HTTP 基本身份验证;
  • Cookie 和会话管理;
  • HTTP 标头中的令牌(例如OAuth 2.0 + JWT);
  • 使用附加签名参数查询身份验证。

您必须适应,甚至更好地混合这些技术,以最好地匹配您的软件架构。

每个身份验证方案都有自己的优点和缺点,具体取决于您的安全策略和软件架构的目的。

基于 HTTPS 的 HTTP 基本身份验证

大多数 Web 服务都使用基于标准 HTTPS 协议的第一个解决方案。

GET /spec.html HTTP/1.1
Host: www.example.org
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

它很容易实现,默认在所有浏览器上都可用,但有一些已知的缺点,比如浏览器上显示的可怕的身份验证窗口会持续存在(这里没有类似 LogOut 的功能),一些服务器端额外的 CPU 消耗,以及用户名和密码(通过 HTTPS)传输到服务器的事实(在键盘输入期间让密码仅保留在客户端应该更安全,并作为安全哈希存储在服务器上) .

我们可以使用Digest Authentication,但它也需要 HTTPS,因为它容易受到MiMReplay攻击,并且特定于 HTTP。

通过 Cookie 进行会话

老实说,在服务器上管理的会话并不是真正的无状态。

一种可能性是在 cookie 内容中维护所有数据。而且,按照设计,cookie 是在服务器端处理的(事实上,客户端甚至不会尝试解释这个 cookie 数据:它只是在每次连续请求时将其交给服务器)。但是这个 cookie 数据是应用程序状态数据,所以在一个纯粹的无状态世界中,应该由客户端而不是服务器来管理它。

GET /spec.html HTTP/1.1
Host: www.example.org
Cookie: theme=light; sessionToken=abc123

cookie 技术本身是 HTTP 链接的,所以它不是真正的 RESTful,它应该是独立于协议的,恕我直言。它容易受到MiMReplay攻击。

通过令牌 (OAuth2) 授予

另一种方法是在 HTTP 标头中放置一个令牌,以便对请求进行身份验证。例如,这就是OAuth 2.0 所做的。请参阅RFC 6749

 GET /resource/1 HTTP/1.1
 Host: example.com
 Authorization: Bearer mF_9.B5f-4.1JqM

简而言之,这与 cookie 非常相似,并且存在相同的问题:不是无状态的,依赖于 HTTP 传输细节,并且存在许多安全漏洞——包括 MiM 和 Replay——因此只能通过 HTTPS 使用。通常,JWT用作令牌。

查询认证

查询身份验证包括通过 URI 上的一些附加参数对每个 RESTful 请求进行签名。请参阅此参考文章

在这篇文章中是这样定义的:

所有 REST 查询都必须通过使用私有凭证作为签名令牌以小写字母顺序对查询参数进行签名来进行身份验证。签名应该发生在 URL 编码查询字符串之前。

这种技术可能与无状态架构更兼容,也可以通过轻量会话管理(使用内存会话而不是数据库持久性)来实现。

例如,下面是来自上述链接的通用 URI 示例:

GET /object?apiKey=Qwerty2010

应该这样传送:

GET /object?timestamp=1261496500&apiKey=Qwerty2010&signature=abcdef0123456789

被签名的字符串是/object?apikey=Qwerty2010&timestamp=1261496500并且签名是使用 API 密钥的私有组件的该字符串的 SHA256 哈希。

服务器端数据缓存始终可用。例如,在我们的框架中,我们在 SQL 级别缓存响应,而不是在 URI 级别。所以添加这个额外的参数不会破坏缓存机制。

有关基于 JSON 和 REST 的客户端-服务器 ORM/SOA/MVC 框架中的 RESTful 身份验证的一些详细信息,请参阅本文。由于我们不仅允许通过 HTTP/1.1 进行通信,还允许通过命名管道或 GDI 消息(本地)进行通信,因此我们尝试实现真正的 RESTful 身份验证模式,而不依赖于 HTTP 特定性(如标头或 cookie)。

稍后注意:在 URI 中添加签名可能被视为不好的做法(例如,它将出现在 http 服务器日志中),因此必须减轻它,例如通过适当的 TTL 以避免重播。但是如果你的http日志被泄露,你肯定会遇到更大的安全问题。

在实践中,即将推出的 OAuth 2.0 的 MAC 令牌身份验证可能是对“由令牌授予”当前方案的巨大改进。但这仍然是一项正在进行的工作,并且与 HTTP 传输相关联。

结论

值得得出的结论是 REST 不仅基于 HTTP,即使在实践中,它也主要通过 HTTP 实现。REST 可以使用其他通信层。因此,无论 Google 回答什么,RESTful 身份验证都不仅仅是 HTTP 身份验证的同义词。它甚至根本不应该使用 HTTP 机制,而是应该从通信层抽象出来。如果您使用 HTTP 通信,由于Let's Encrypt 倡议,没有理由不使用正确的 HTTPS,这是任何身份验证方案之外的必要条件。

于 2011-08-23T09:29:08.410 回答
424

我怀疑那些热情地大喊“HTTP 身份验证”的人是否曾经尝试过使用 REST 制作基于浏览器的应用程序(而不是机器对机器的 Web 服务)(无意冒犯 - 我只是认为他们从未遇到过复杂情况) .

我发现在生成要在浏览器中查看的 HTML 页面的 RESTful 服务上使用 HTTP 身份验证的问题是:

  • 用户通常会得到一个丑陋的浏览器制作的登录框,这对用户非常不友好。您不能添加密码检索、帮助框等。
  • 注销或以不同的名称登录是一个问题 - 浏览器将不断向站点发送身份验证信息,直到您关闭窗口
  • 超时很困难

一篇非常有见地的文章逐点解决了这些问题但这导致了许多特定于浏览器的 javascript 黑客攻击、变通方法的变通方法等。因此,它也不向前兼容,因此随着新浏览器的发布需要不断维护。我不认为这种简洁明了的设计,加上我觉得这是很多额外的工作和头痛,只是为了让我可以热情地向我的朋友展示我的 REST-badge。

我相信cookies是解决方案。但是等等,饼干是邪恶的,不是吗?不,他们不是,cookie 的经常使用方式是邪恶的。cookie 本身只是一段客户端信息,就像浏览器在您浏览时跟踪的 HTTP 身份验证信息一样。并且这条客户端信息在每次请求时都会发送到服务器,就像 HTTP 身份验证信息一样。从概念上讲,唯一的区别是这块客户端状态的内容可以由服务器作为其响应的一部分来确定。

通过仅使用以下规则使会话成为 RESTful 资源:

  • 会话将键映射到用户 ID(可能还有超时的最后操作时间戳)
  • 如果会话存在,那么这意味着密钥是有效的。
  • 登录意味着 POST 到 /sessions,一个新的密钥被设置为一个 cookie
  • 注销意味着删除 /sessions/{key} (使用重载的 POST,请记住,我们是浏览器,HTML 5 还有很长的路要走)
  • 通过在每次请求时将密钥作为 cookie 发送并检查会话是否存在且有效来完成身份验证

现在,与 HTTP 身份验证的唯一区别在于,身份验证密钥由服务器生成并发送给不断将其发回的客户端,而不是客户端根据输入的凭据计算它。

converter42 补充说,当使用 https(我们应该这样做)时,重要的是 cookie 将设置其安全标志,以便永远不会通过非安全连接发送身份验证信息。好点,我自己没见过。

我觉得这是一个足够好的解决方案,但我必须承认,我还不足以成为安全专家来识别该方案中的潜在漏洞——我所知道的是数百个非 RESTful Web 应用程序基本上使用相同的登录协议(PHP 中的 $_SESSION、Java EE 中的 HttpSession 等)。cookie 标头内容仅用于寻址服务器端资源,就像接受语言可能用于访问翻译资源等一样。我觉得它是一样的,但也许其他人没有?你们觉得怎么样?

于 2009-07-16T07:39:07.587 回答
152

这里的好人已经对这个话题说得够多了。但这是我的 2 美分。

有2种交互方式:

  1. 人机交互 (HTM)
  2. 机器对机器 (MTM)

机器是共同点,表示为 REST API,参与者/客户要么是人,要么是机器。

现在,在真正的 RESTful 架构中,无状态的概念意味着必须为每个请求提供所有相关的应用程序状态(即客户端状态)。相关是指 REST API 处理请求和提供适当响应所需的任何内容。

当我们在人机应用程序的上下文中考虑这一点时,正如 Skrebbel 指出的那样,“基于浏览器”,这意味着在浏览器中运行的(Web)应用程序将需要在每个请求中发送其状态和相关信息它适用于后端 REST API。

考虑一下:您有一个数据/信息平台公开的 REST API 资产。也许您有一个处理所有数据立方体的自助式 BI 平台。但是您希望您的(人类)客户通过 (1) Web 应用程序、(2) 移动应用程序和 (3) 一些第 3 方应用程序来访问它。最后,即使是 MTM 链也会导致 HTM - 对。所以人类用户仍然处于信息链的顶端。

在前两种情况下,您有一个人机交互的案例,信息实际上是由人类用户使用的。在最后一种情况下,您有一个使用 REST API 的机器程序。

身份验证的概念全面适用。您将如何设计它以便以统一、安全的方式访问您的 REST API?在我看来,有两种方法:

方式一:

  1. 没有登录,开始。每个请求都执行登录
  2. 客户端随每个请求发送其识别参数 + 请求特定参数
  3. REST API 接受它们,转身,ping 用户存储(无论是什么)并确认身份验证
  4. 如果建立了身份验证,则为请求提供服务;否则,使用适当的 HTTP 状态代码拒绝
  5. 对目录中所有 REST API 的每个请求重复上述操作

方式2:

  1. 客户端以身份验证请求开始
  2. 登录 REST API 将处理所有此类请求
  3. 它接受身份验证参数(API 密钥、uid/pwd 或您选择的任何内容)并根据用户存储(LDAP、AD 或 MySQL DB 等)验证身份验证。
  4. 如果经过验证,则创建一个身份验证令牌并将其交还给客户端/调用者
  5. 然后,调用者将此身份验证令牌 + 请求特定参数与每个后续请求一起发送到其他业务 REST API,直到注销或租约到期

显然,在 Way-2 中,REST API 需要一种方法来识别和信任令牌是有效的。登录 API 执行了身份验证,因此您的目录中的其他 REST API 需要信任“代客密钥”。

当然,这意味着需要在 REST API 之间存储和共享身份验证密钥/令牌。这个共享的、受信任的令牌存储库可以是本地的/联合的,允许来自其他组织的 REST API 相互信任。

但我离题了。

关键是,需要维护和共享一个“状态”(关于客户端的身份验证状态),以便所有 REST API 可以创建一个信任圈。如果我们不这样做(即 Way-1),我们必须接受必须为任何/所有进入的请求执行身份验证行为。

执行身份验证是一个资源密集型过程。想象一下,针对您的用户存储对每个传入请求执行 SQL 查询以检查 uid/pwd 匹配。或者,加密和执行哈希匹配(AWS 风格)。在架构上,我怀疑每个 REST API 都需要使用通用的后端登录服务来执行此操作。因为,如果你不这样做,那么你就会到处乱扔身份验证代码。一个大乱子。

所以更多的层,更多的延迟。

现在,走 Way-1 并申请 HTM。您的(人类)用户是否真的关心您是否必须在每个请求中发送 uid/pwd/hash 或其他任何内容?不,只要您不通过每秒抛出身份验证/登录页面来打扰她。如果你这样做,祝你有客户好运。因此,您要做的是将登录信息存储在客户端的某处,在浏览器中,就在开始时,并在每次发出请求时发送。对于(人类)用户,她已经登录,并且“会话”可用。但实际上,她在每次请求时都经过身份验证。

与方式 2 相同。您的(人类)用户永远不会注意到。所以没有造成任何伤害。

如果我们将 Way-1 应用于 MTM 会怎样?在这种情况下,由于它是一台机器,我们可以通过要求它在每个请求中提交身份验证信息来让这个家伙感到厌烦。没人在乎!在 MTM 上执行 Way-2 不会引起任何特殊反应;它是一台该死的机器。它可以不在乎!

所以真的,问题是什么适合你的需要。无国籍是要付出代价的。付出代价并继续前进。如果你想成为一个纯粹主义者,那么也要为此付出代价,然后继续前进。

最后,哲学并不重要。真正重要的是信息发现、呈现和消费体验。如果人们喜欢你的 API,那么你就完成了你的工作。

于 2013-11-23T22:19:58.887 回答
55

这是一个真正完整的 RESTful 身份验证解决方案:

  1. 在身份验证服务器上创建公钥/私钥对。
  2. 将公钥分发给所有服务器。
  3. 当客户端进行身份验证时:

    3.1。发出一个令牌,其中包含以下内容:

    • 到期时间
    • 用户名(可选)
    • 用户 IP(可选)
    • 密码哈希(可选)

    3.2. 使用私钥加密令牌。

    3.3. 将加密的令牌发回给用户。

  4. 当用户访问任何 API 时,他们还必须传入他们的身份验证令牌。

  5. 服务器可以通过使用身份验证服务器的公钥对其进行解密来验证令牌是否有效。

这是无状态/RESTful 身份验证。

请注意,如果包含密码哈希,用户还将发送未加密的密码以及身份验证令牌。服务器可以通过比较哈希值来验证密码是否与用于创建身份验证令牌的密码匹配。需要使用 HTTPS 之类的安全连接。客户端的 Javascript 可以处理获取用户的密码并将其存储在客户端,无论是在内存中还是在 cookie 中,可能使用服务器的公钥加密。

于 2013-10-14T21:29:27.273 回答
41

老实说,我在这里看到了很好的答案,但让我有点困扰的是,当有人将整个无状态概念推向极端时,它会变得教条主义。它让我想起了那些只想拥抱纯 OO 的 Smalltalk 老粉丝,如果某些东西不是对象,那么你做错了。让我休息一下。

RESTful 方法应该让您的生活更轻松并减少会话的开销和成本,尝试遵循它,因为这是明智的做法,但是一旦您遵循纪律(任何学科/指南)到它的极端不再提供预期的好处,那么你做错了。当今一些最好的语言同时具有函数式编程和面向对象。

如果您解决问题的最简单方法是将身份验证密钥存储在 cookie 中并将其发送到 HTTP 标头,那么就这样做,只是不要滥用它。请记住,当会话变得又重又大时,它们是不好的,如果您的所有会话都包含一个包含密钥的短字符串,那么有什么大不了的?

我愿意接受评论中的更正,但我只是不明白(到目前为止)让我们的生活变得悲惨只是避免在我们的服务器中保留一个大的哈希字典的意义。

于 2013-08-13T20:09:03.473 回答
35

首先,一个 RESTful Web 服务是状态的(或者换句话说,无状态的))。因此,RESTful 服务没有也不应该包含会话或 cookie 的概念。在 RESTful 服务中进行身份验证或授权的方法是使用 RFC 2616 HTTP 规范中定义的 HTTP 授权标头。每个请求都应包含 HTTP Authorization 标头,并且应通过 HTTPs (SSL) 连接发送请求。这是在 HTTP RESTful Web 服务中进行身份验证和验证请求授权的正确方法。我在 Cisco Systems 为 Cisco PRIME Performance Manager 应用程序实施了 RESTful Web 服务。作为该 Web 服务的一部分,我还实现了身份验证/授权。

于 2013-03-26T23:13:29.420 回答
24

它当然不是关于“会话密钥”,因为它通常用于指代在 REST 的所有约束内执行的无会话身份验证。每个请求都是自描述的,携带足够的信息来自行授权请求,而无需任何服务器端应用程序状态。

解决此问题的最简单方法是从RFC 2617中 HTTP 的内置身份验证机制开始。

于 2008-11-26T03:06:21.503 回答
16

2019 年 2 月 16 日更新

下面前面提到的方法本质上是OAuth2.0的“资源所有者密码凭据”授权类型。这是启动和运行的简单方法。但是,通过这种方法,组织中的每个应用程序最终都将拥有自己的身份验证和授权机制。推荐的方法是“授权码”授权类型。此外,在我之前的回答中,我推荐使用浏览器 localStorage 来存储身份验证令牌。但是,我开始相信 cookie 是实现此目的的正确选择。我在这个 StackOverflow 答案中详细说明了我的原因、授权码授予类型的实现方法、安全注意事项等。


我认为以下方法可用于 REST 服务身份验证:

  1. 创建一个登录 RESTful API 以接受用户名和密码进行身份验证。在传输过程中使用 HTTP POST 方法来防止缓存和 SSL 以确保安全在成功验证后,API 返回两个 JWT - 一个访问令牌(较短的有效期,例如 30 分钟)和一个刷新令牌(较长的有效期,例如 24 小时)
  2. 客户端(基于 Web 的 UI)将 JWT 存储在本地存储中,并且在每个后续 API 调用中都会在“Authorization: Bearer #access token”标头中传递访问令牌
  3. API 通过验证签名和到期日期来检查令牌的有效性。如果令牌有效,请检查用户(它将 JWT 中的“子”声明解释为用户名)是否可以通过缓存查找访问 API。如果用户被授权访问API,执行业务逻辑
  4. 如果令牌过期,API 返回 HTTP 响应码 400
  5. 客户端在收到 400/401 时,使用“Authorization: Bearer #refresh token”标头中的刷新令牌调用另一个 REST API 以获取新的访问令牌。
  6. 在收到带有刷新令牌的调用时,通过检查签名和到期日期来检查刷新令牌是否有效。如果刷新令牌有效,则从数据库中刷新用户的访问权限缓存,并返回新的访问令牌和刷新令牌。如果刷新令牌无效,返回 HTTP 响应码 400
  7. 如果返回新的访问令牌和刷新令牌,则执行步骤 2。如果返回 HTTP 响应码 400,则客户端假定刷新令牌已过期,并要求用户输入用户名和密码
  8. 对于注销,清除本地存储

使用这种方法,我们每 30 分钟执行一次使用特定于用户的访问权限详细信息加载缓存的昂贵操作。因此,如果撤销访问权限或授予新访问权限,则需要 30 分钟才能反映或注销后再登录。

于 2017-01-14T13:30:16.190 回答
15

@skrebel ( http://www.berenddeboer.net/rest/authentication.html ) 提到的“非常有见地”的文章讨论了一种复杂但非常糟糕的身份验证方法。

您可以尝试在没有任何登录凭据的情况下访问该页面(应该只对经过身份验证的用户可见)http://www.berenddeboer.net/rest/site/authenticated.html 。

(对不起,我无法评论答案。)

我会说 REST 和身份验证根本不混合。REST 表示无状态,但“经过身份验证”是一种状态。您不能将它们都放在同一层。如果您是 RESTful 倡导者并且对状态不满意,那么您必须使用 HTTPS(即将安全问题留给另一层)。

于 2012-10-15T19:35:58.230 回答
12

我认为 RESTful 身份验证涉及将身份验证令牌作为请求中的参数传递。示例是 api 对 apikeys 的使用。我不相信使用 cookie 或 http auth 符合条件。

于 2009-01-19T06:45:34.217 回答
8

这样做的方法是:使用 OAuth 2.0 for Login

您可以使用除 Google 之外的其他身份验证方法,只要它支持 OAuth。

于 2011-12-08T22:44:15.620 回答
4

使用公钥基础设施,其中密钥的注册涉及正确绑定,可确保公钥以确保不可否认性的方式绑定到分配给它的个人

请参阅http://en.wikipedia.org/wiki/Public_key_infrastructure。如果您遵循正确的 PKI 标准,则可以识别并锁定不当使用被盗密钥的个人或代理。如果代理需要使用证书,则绑定会变得非常紧密。一个聪明敏捷的小偷可以逃走,但他们会留下更多的面包屑。

于 2013-11-02T04:08:40.923 回答
3

适用于保护任何 Web 应用程序的提示

如果您想保护您的应用程序,那么您绝对应该从使用 HTTPS 而不是 HTTP 开始,这可以确保在您和用户之间创建安全通道,这将防止嗅探来回发送给用户的数据并有助于保留数据交换机密。

您可以使用 JWTs (JSON Web Tokens) 来保护 RESTful API,与服务器端会话相比,这有很多好处,好处主要是:

1- 更具可扩展性,因为您的 API 服务器不必为每个用户维护会话(当您有很多会话时,这可能是一个很大的负担)

2- JWT 是自包含的,并且具有定义用户角色的声明,例如,他可以访问和在日期和到期日发布的内容(在此之后 JWT 将无效)

3-更容易跨负载均衡器处理,如果您有多个 API 服务器,因为您不必共享会话数据,也不必配置服务器以将会话路由到同一服务器,只要带有 JWT 的请求命中任何服务器,就可以对其进行身份验证&授权

4-减少对数据库的压力,并且您不必为每个请求不断存储和检索会话 ID 和数据

5-如果您使用强密钥签署 JWT,则 JWT 不会被篡改,因此您可以信任随请求发送的 JWT 中的声明,而无需检查用户会话以及他是否被授权,您只需检查 JWT 就可以知道该用户可以做什么和做什么。

许多库提供了在大多数编程语言中创建和验证 JWT 的简单方法,例如:在 node.js 中,最流行的一种是jsonwebtoken

由于 REST API 通常旨在保持服务器无状态,因此 JWT 与该概念更兼容,因为每个请求都使用自包含的授权令牌(JWT)发送,与使服务器是有状态的,因此它可以记住用户及其角色,但是,会话也被广泛使用并具有其优点,您可以根据需要进行搜索。

需要注意的一件重要事情是,您必须使用 HTTPS 将 JWT 安全地交付给客户端并将其保存在安全的地方(例如本地存储中)。

您可以从此链接了解有关 JWT的更多信息

于 2018-10-15T18:16:19.840 回答
2

根据我的理解来回答这个问题...

使用 REST 的身份验证系统,因此您无需实际跟踪或管理系统中的用户。这是通过使用 HTTP 方法 POST、GET、PUT、DELETE 完成的。我们采用这 4 种方法,并在数据库交互方面将它们视为 CREATE、READ、UPDATE、DELETE(但在 Web 上我们使用 POST 和 GET,因为这是锚标签目前支持的)。因此,将 POST 和 GET 视为我们的 CREATE/READ/UPDATE/DELETE (CRUD),然后我们可以在我们的 Web 应用程序中设计路由,从而能够推断出我们正在实现的 CRUD 操作。

例如,在 Ruby on Rails 应用程序中,我们可以构建我们的 Web 应用程序,这样,如果登录的用户访问http://store.com/account/logout,那么该页面的 GET 可以被视为尝试注销的用户. 在我们的 Rails 控制器中,我们将构建一个操作,将用户注销并将其发送回主页。

登录页面上的 GET 将产生一个表单。登录页面上的 POST 将被视为登录尝试,并获取 POST 数据并使用它进行登录。

对我来说,这是一种使用映射到其数据库含义的 HTTP 方法然后构建身份验证系统的做法,并牢记您不需要传递任何会话 ID 或跟踪会话。

我还在学习——如果您发现我所说的任何错误,请纠正我,如果您了解更多,请在此处发布。谢谢。

于 2009-01-19T06:14:49.177 回答