RESTful身份验证

795
RESTful认证是什么意思,它是如何工作的?我在谷歌上找不到一个好的概述。我唯一了解的是你可以通过URL传递会话密钥(rememberal),但这可能是完全错误的。

4
当我谷歌“Restful Authentication”时,我发现有很多Ruby on Rails插件。我猜想这些并不是你要找的。如果不是Ruby on Rails,那么你需要使用什么编程语言?需要用到什么网络服务器? - S.Lott
3
如果您使用HTTPS,那将不会出现严重的错误。完整的HTTP请求以及URL将被加密。 - Bharat Khatri
5
@BharatKhatri:是的,我不会在 URL 中向用户传递敏感信息。这种信息更有可能因为实际需要而泄露出去。HTTPS 无法防止意外泄漏。 - Jo So
3
“real RESTful authentication” 是什么意思?我对此很感兴趣,因为我刚刚实施了被接受答案中的第三种方式,但我不满意它(我不喜欢URL中的附加参数)。 - BlueLettuce16
5
有些人使用https://jwt.io/introduction/来解决这个问题。我现在正在研究这个问题,以解决我的情况:http://stackoverflow.com/questions/36974163/laravel-rest-authentication-when-post-via-httpclient-android >>希望这能正常运行。 - toha
14个回答

630
在RESTful客户端服务器架构中如何处理身份验证是一个有争议的问题。通常,在SOA over HTTP世界中可以通过以下方式实现:
  • HTTPS上的HTTP基本认证;
  • Cookie和会话管理;
  • HTTP标头中的令牌(例如OAuth 2.0 + JWT);
  • 使用附加签名参数的查询认证。
您需要调整或更好地混合这些技术,以最好地匹配您的软件架构。
每种身份验证方案都有其自己的优缺点,具体取决于安全策略和软件架构的目的。
HTTP基本认证是第一种解决方案,基于标准HTTPS协议,被大多数Web服务使用。
GET /spec.html HTTP/1.1
Host: www.example.org
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

这是一种易于实现、在所有浏览器上默认可用的方法,但也有一些已知的缺点,比如浏览器显示的糟糕认证窗口(没有类似LogOut的功能),服务器端额外的CPU消耗,以及用户名和密码通过HTTPS传输到服务器(让密码只在客户端保留,输入时存储为安全哈希值更加安全)。

我们可以使用摘要认证,但它同样需要HTTPS,因为它容易受到中间人重放攻击的攻击,并且仅适用于HTTP。

通过Cookies进行会话

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

一种可能的解决方案是将所有数据都保存在cookie内容中。而且,按设计,cookie是在服务器端处理的(事实上,客户端甚至不尝试解释此cookie数据:它只是在每个连续请求中将其返回给服务器)。但是这个cookie数据是应用程序状态数据,所以在一个真正的无状态世界中,客户端应该管理它,而不是服务器。

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

Cookie技术本身与HTTP相关,因此不符合真正的RESTful标准,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编码查询字符串之前发生。

这种技术可能更适用于无状态架构,并且还可以使用轻量级会话管理(使用内存会话而不是DB持久性)来实现。
例如,这是来自上面链接的通用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令牌身份验证可能在"Granted by Token"当前方案方面有很大改进。但这仍然是正在进行的工作,并且与HTTP传输相关。

结论

值得指出的是,REST不仅基于HTTP,虽然在实践中,它主要通过HTTP实现。REST可以使用其他通信层。因此,RESTful身份验证并不仅仅是HTTP身份验证的同义词,无论Google如何回答。它甚至不应使用HTTP机制,而应该从通信层抽象出来。如果使用HTTP通信,由于Let's Encrypt计划的存在,没有理由不使用适当的HTTPS,这是任何身份验证方案所必需的补充。

7
如果您使用“ Cookie”来取代“HTTP基本认证”,则可以使用一种具有过期身份验证和注销功能的真正无状态身份验证方法。例如,实现可以使用名为“ Emulated-HTTP-Basic-Auth”的Cookie,其值类似于真正的HTTP基本认证,并在此基础上设置过期时间。注销可以通过删除该Cookie来实现。我猜测,任何能够支持HTTP基本认证的客户端也能够以这种方式进行Cookie身份验证。 - Mikko Rantalainen
5
但是,正如我所写的那样,这个cookie仍然将由服务器管理。它某种程度上是无状态的,但不是“纯粹”的无状态。在所有情况下,您都需要专门用于客户端登录/注销的JavaScript代码,这是完全可能的,例如使用HTTP摘要身份验证-好主意,但在这里重新发明轮子并没有太多好处。 - Arnaud Bouchez
5
我认为服务器实现了配置标题的用户界面和逻辑,但标题本身是无状态的。针对API设计的客户端可以跳过使用服务器来配置标题,只需像HTTP基本身份验证一样传递所需信息即可。我的观点是,常见的UA(浏览器)对基本身份验证的实现非常差,无法使用。服务器提供的另一个标题(“Cookie”)的仿真功能可以代替使用基本身份验证。 - Mikko Rantalainen
8
我认为正确答案是:https://dev59.com/dG025IYBdhLWcg3wVkef。 - graffic
9
如果服务器返回401未授权响应,则会出现不太美观的HTTP授权密码提示。如果您不喜欢它,只需发送403禁止即可。错误页面可能包括登录方法或链接。然而,无论状态是服务器端还是客户端,反对cookie和http身份验证最大的论据是它们容易受到跨站请求伪造的攻击。因此,最好的方法是使用自定义授权方案、自定义授权头或自定义GET或POST参数。 - Dobes Vandermeer
显示剩余12条评论

428

我怀疑那些热情呼喊“HTTP身份验证”的人是否曾尝试过使用REST(而不是机器对机器的Web服务)创建基于浏览器的应用程序(无意冒犯 - 我只是认为他们从未面临过这些复杂问题)。

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

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

一篇非常有见地的文章逐一解决了这些问题:这里,但这导致了很多特定于浏览器的JavaScript hackery,解决方法的解决方法等等。因此,它也不是向前兼容的,因此需要随着新的浏览器发布进行不断的维护。我认为这不是一个清晰明了的设计方案,而且我认为这是额外的工作和头痛,只是为了让我可以自豪地向我的朋友展示我的REST徽章。

我相信Cookies是解决方案。但是等等,Cookie是邪恶的,不是吗? 不,它们不是,Cookie经常使用的方式是邪恶的。 Cookie本身只是客户端信息,就像浏览器会跟踪您在浏览过程中的HTTP身份验证信息一样。 并且,此客户端状态片段在每个请求中都会发送到服务器,就像HTTP身份验证信息一样。 从概念上讲,唯一的区别在于此客户端状态片段的内容可以由服务器作为其响应的一部分确定。

通过制定以下规则,将会话作为RESTful资源:

  • 一个会话将键映射到用户ID(以及可能的最后操作时间戳以进行超时)
  • 如果存在会话,则意味着该键是有效的。
  • 登陆意味着向 /sessions 发送一个 POST 请求,并将一个新的 key 设置为 cookie
  • 注销意味着 DELETE /sessions/{key}(使用重载的 POST,记住,我们是一个浏览器,而 HTML 5 还有很长的路要走)
  • 认证是通过在每个请求中发送 cookie 标识并检查会话是否存在且有效来完成的

唯一不同于HTTP认证的地方是,认证密钥是由服务器生成并发送给客户端保持发送回来的,而不是客户端从输入的凭据计算出来。

converter42 补充说,在使用https时(我们应该这样做),重要的是设置cookie的secure标志,以便身份验证信息永远不会通过非安全连接发送。这是一个很好的观点,我自己还没有看到过。

我认为这是一个足够的解决方案,可以正常工作,但我必须承认,我不是足够的安全专家,无法确定此方案可能存在的潜在隐患 - 我所知道的是,成百上千个非RESTful Web应用程序基本上使用相同的登录协议(PHP中的 $_SESSION,Java EE 中的 HttpSession等)。cookie 头部内容只是用来访问服务器端资源,就像 accept-language 可以用来访问翻译资源一样。我认为这是一样的,但也许其他人不这么认为?你们觉得呢?


71
这是一个实用的答案,提出的解决方案可行。但在同一句话中使用“RESTful”和“session”的术语是错误的(除非在它们之间还有“不是”)。换句话说:任何使用会话的Web服务都不是RESTful(根据定义)。别误会 - 您仍然可以使用此解决方案(您的经验可能有所不同),但术语“RESTful”不能用于描述它。我推荐阅读《REST设计指南》这本O'Reilly书籍,它非常易读并深入讲解了该主题。 - johndodo
23
纯REST方案每次请求资源时都会发送认证数据,这并不完美(HTTP Auth就是这样做的)。提议的解决方案可行,并且对于大多数用例来说更好,但它不符合RESTful的原则。没有必要争论,我也使用这个解决方案。我只是不声称它是符合RESTful原则的。 :) - johndodo
99
那就举个例子吧,还有什么其他行之有效的方式?我真的很想知道。HTTP认证肯定不是,你不能在不关闭浏览器的情况下注销,而且你不能提供良好的登录用户体验,而不需要大量特定于浏览器的、不具备未来兼容性的JS。我并不太关心“纯粹的RESTful”与“几乎RESTful”的整个相关宗教争议,但如果你说有几种方法,那么你应该将它们列举出来。 - skrebbel
16
真正符合RESTful标准的身份验证需要使用包含HTTP身份验证值的cookie,以便服务器可以提供登录和密码输入的用户界面,并且服务器可以通过删除cookie来强制注销。此外,当身份验证失败时,服务器不应该响应401要求登录,而应该使用临时重定向到登录页面,成功登录后再使用临时重定向返回到之前的位置。另外,服务器必须将注销操作(POST表单)嵌入到几乎每个已登录用户的页面中。 - Mikko Rantalainen
17
只要清楚表明会话仅存在于客户端,我认为在同一句话中使用“restful”和“session”没有任何问题。我不确定为什么这个概念如此重要。 - Joe Phillips
显示剩余11条评论

156

已经有很多人在这个话题上说了足够多的话了。但是这是我的看法。

有两种交互模式:

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

机器是公共因素,表示为REST API,参与者/客户端可以是人或机器。

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

当我们在人与机器的应用程序环境中考虑这一点时,“基于浏览器的”就像Skrebbel上面指出的那样,这意味着运行在浏览器中的(网络)应用程序将需要在每个请求发送其状态和相关信息到后端REST API。

考虑这种情况:您拥有一个暴露为REST API的数据/信息平台资源。也许您有一个自助式BI平台,可以处理所有数据立方体。但是你希望你的(人类)客户通过(1)网络应用程序、(2)移动应用程序和(3)某些第三方应用程序访问这个平台。最终,即使是一系列的MTMs也会导致HTM-对吗。所以人类用户仍然是信息链的顶点。

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

认证的概念适用于所有情况。您将如何设计这样一种方式,使得您的REST API以统一、安全的方式被访问?在我看来,有两种方法:

方法一:

  1. 首先没有登录。每个请求执行登录
  2. 客户端在每个请求中发送其标识参数+请求特定参数
  3. REST API接受它们,反转过来,ping用户存储(无论它是什么)并确认auth
  • 如果已经建立了身份验证,则服务该请求;否则,拒绝并使用适当的HTTP状态码
  • 对于目录中所有REST API的每个请求重复上述操作
  • 方式-2:

    1. 客户端开始进行身份验证请求
    2. 登录REST API将处理所有这些请求
    3. 它获取认证参数(API密钥、uid/pwd或您选择的其他内容),并根据用户存储库(LDAP、AD或MySQL DB等)验证认证
    4. 如果验证通过,则创建一个认证令牌并将其交给客户端/调用者
    5. 然后,调用者向其他业务REST API发送此auth token +请求特定参数,直到注销或租赁到期为止

    显然,在Way-2中,REST API需要一种方法来识别和信任有效的令牌。登录API执行了auth验证,因此其他REST API在目录中需要信任那个“valet key”。

    当然,这意味着需要存储和共享auth key/token。这个共享的可信令牌存储库可以是本地/联合的,允许来自其他组织的REST API互相信任。

    但我偏离主题了。

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

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

    所以,层数越多,延迟越大。

    现在,采用方式1并应用于HTM。对于你的(人类)用户来说,是否必须在每个请求中发送uid/pwd/hash或其他信息并不重要,只要你不会每秒钟都跳出授权/登录页面打扰她。如果你这样做,祝你好运,因为你将失去客户。因此,你需要在开始时将登录信息存储在浏览器中的客户端,并随每个请求一起发送。对于(人类)用户来说,她已经登录,并且“会话”可用。但实际上,在每个请求上进行身份验证。
    同样适用于方式2。你的(人类)用户永远也不会注意到。所以并没有什么危害。
    如果我们将方式1应用于MTM,由于它是一台机器,我们可以要求它在每个请求中提交身份验证信息而不会感到烦恼。没人在乎!执行方式2对MTM不会引起任何特殊反应;它只是一台该死的机器。它毫不在意!
    因此,真正的问题是什么适合你的需求。无状态需要付出代价。付出代价并前进。如果你想成为一个纯粹主义者,那么也要为此付出代价,然后继续前进。
    最终,哲学观点并不重要。真正重要的是信息的发现、呈现和消费体验。如果人们喜欢你的API,那么你已经完成了你的工作。

    6
    先生,您讲解得非常清晰,我对基本问题/问题有了明确的理解。您就像佛陀一样!我可以补充一点,通过在传输层使用HTTPS,我们甚至可以防止中间人攻击,这样就不会有人劫持我的标识符密钥(如果选择方式1) - Vishnoo Rath
    12
    我看了你的回答。在你的解决方案中,每个由用户点击浏览器发起的网络请求都需要将“身份验证令牌”发送回用户所点击的任何API。然后呢?该API对令牌进行检查。检查什么?针对一种维护该令牌是否有效的“令牌存储”。你不认为这个“令牌存储”就成为“状态”的守护者了吗?不管怎样,无论你如何看待这个问题,都有某个人需要知道有关用户活动中传递的“令牌”的一些信息。这就是状态信息的所在地。 - Kingz
    8
    “无状态”服务实际上指的是特定服务器组件(CRUD API)不保留任何状态。它们不能识别一个用户和另一个用户,并在一次交易中完整地完成用户的请求,这就是“无状态”的含义。但是,必须有人在某个地方坐着并判断该用户是否有效。没有其他方法可以做到这一点;需要使用密钥、密码或其他验证方式来验证和授权用户端传输的任何内容。 - Kingz
    3
    你漏掉了“第三种方法”——混合方法。客户端登录方式与“第二种方法”相同,但是像“第一种方法”一样,不会将凭据与任何服务器端状态进行检查。无论如何,都会创建一个身份验证令牌并像“第二种方式”一样发送回客户端。稍后使用非对称加密检查此令牌的真实性,而不查找任何特定于客户端的状态。 - jcoffland
    2
    @jcoffland,您所描述的是一个系统,该系统确定用户在某个时刻通过了身份验证检查,但不确定是哪种身份验证检查。您不知道他们是谁,更不知道他们有什么访问权限,只知道他们在某个时刻提供了通过的凭据。那么,如何找出他们是谁以及他们是否应该访问当前资源?或者您的系统上所有经过身份验证的用户都可以访问所有受保护的资源吗? - Merlyn Morgan-Graham
    显示剩余2条评论

    55

    这是一个完全符合RESTful标准的身份验证解决方案:

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

      3.1. 发布一个包含以下内容的令牌:

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

      3.2. 用私钥加密令牌。

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

    4. 当用户访问任何API时,他们还必须传递他们的认证令牌。

    5. 服务器可以通过使用认证服务器的公钥来解密令牌并验证其有效性。

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

    请注意,如果包括密码哈希,则用户还将随身份验证令牌一起发送未加密的密码。 服务器可以通过比较哈希值来验证密码是否与用于创建身份验证令牌的密码匹配。需要使用安全连接,如HTTPS。客户端JavaScript可以处理获取用户的密码,并在客户端中存储它,例如在内存或Cookie中,可能使用服务器的公钥加密。


    5
    如果有人获取了该身份验证令牌并使用它调用API,假装成客户端,那怎么办? - Abidi
    2
    @Abidi,是的,这是一个问题。您可以要求输入密码。密码的哈希值可以包含在身份验证令牌中。如果有人能够窃取令牌,则会容易受到离线暴力攻击的威胁。如果选择了强密码短语,则不会有问题。请注意,如果使用https,则令牌盗窃需要攻击者首先获得对客户端计算机的访问权限。 - jcoffland
    2
    非对称加密和解密比对称加密慢得多(需要更多计算资源)。在每次调用时,服务器使用公钥解密令牌将是一个巨大的性能瓶颈。 - Craig Tullis
    5
    @jcoffland,你在这里真的推广了你的答案(反复地 :-)),但我忍不住要评论一下使用非对称加密进行每次调用的性能问题(计算强度)。我认为如果采用这种方法,就无法实现可扩展性。请了解HTTPS和SPDY协议。它们会尽力保持连接开放(HTTP keep-alives,这是状态),并在同一个连接上批量提供多个资源(更多的状态),当然SSL本身只使用非对称加密来交换对称密码键(也是状态)。 - Craig Tullis
    2
    因为对称加密比非对称加密快一个数量级。HTTPS最慢、最阻塞管道的方面是初始握手,涉及使用公钥/私钥加密消息。如果HTTPS没有在所有随后的通信中切换到共享秘密的对称加密,则实际的、实用的现实世界性能将是无法接受的,解决方案至少不能扩展,而不会产生无法接受的资源成本。 - Craig Tullis
    显示剩余12条评论

    42
    说实话,我在这里看到了很棒的答案,但有一件事情让我有些不爽,那就是有人会将整个无状态(Stateless)概念推至极端,变得教条主义。这让我想起那些只想拥抱纯面向对象编程(OO)的老旧Smalltalk粉丝,如果某物不是一个对象,那你就做错了。别再过度解读了。
    RESTful的方法应该让你的生活更加简单,减少会话的开销和成本,因此尽量遵循它是明智之举;但是,一旦你过分追求某种规则(任何规则/指南),超出了它原本预期的效益范畴,那么你就是在错误地使用它。今天最好的一些语言都是兼具函数式编程和面向对象编程的。
    如果你解决问题的最简单方式是将认证密钥存储在cookie中,并通过HTTP头发送它,请这样做,只是不要滥用它。请记住,当会话变得沉重和庞大时,会话就不好了,如果你的会话仅包含一个短字符串,其中包含一个密钥,那还有什么大不了的?
    我愿意接受评论中的更正,但是迄今为止,我只是不明白为什么我们要把自己的生活搞得那么痛苦,以避免在服务器上保存一个大字典的散列。

    2
    人们并不想禁止你使用会话。你可以自由地这样做。但是如果你这样做,它就不是REST。 - André Caldas
    8
    @AndréCaldas,这并不是说拥有函数或原始类型与面向对象编程不同。我并不是在说使用会话是可取的。我只是就按照一组惯例的程度发表了我的意见,这些惯例已经不能再为某人提供利益了。顺便说一句,注意到我没有反对您的言论,但我不会说它不是REST,我会说它不是纯粹的REST。 - arg20
    那么如果它不是RESTful,我们该如何称呼它呢? 而且,如果一个请求包括会话ID,那么这与包括用户ID一样无状态,对吧?为什么用户ID是无状态的,而会话ID是有状态的呢? - mfhholmes
    1
    Cookies容易受到跨站请求伪造攻击,因此它们使安全漏洞更容易发生。最好使用浏览器不自动发送的内容,例如自定义标头或自定义授权方案。 - Dobes Vandermeer
    1
    事实上,试图成为无状态并不是教条主义,而是关于SOA本身的一个共同概念。服务应始终受益于解耦和无状态:在实践中,这有助于扩展、可用性和可维护性。当然,应尽可能做到这一点,并最终需要一些“编排服务”来将这些无状态服务管理为有状态的实用方法。 - Arnaud Bouchez
    显示剩余5条评论

    35

    首先,RESTful web服务是无状态的(或者说是无会话的)。因此,RESTful服务不应该有和不应该使用会话或cookie的概念。在RESTful服务中进行身份验证或授权的方式是使用RFC 2616 HTTP规范中定义的HTTP授权头。每个请求都应该包含HTTP授权头,并且请求应该通过HTTPs(SSL)连接发送。这是在HTTP RESTful web服务中进行身份验证和验证请求授权的正确方式。我已经为思科系统的Cisco PRIME性能管理应用程序实现了一个RESTful web服务,并作为该Web服务的一部分,我还实现了身份验证/授权。


    5
    HTTP身份验证仍然需要服务器跟踪用户ID和密码,这并不完全是无状态的。 - jcoffland
    24
    每个请求都是独立的,没有前置请求的要求,因此它在这个意义上是无状态的。关于如何在服务器上实现这一点,如果身份验证很耗费资源,您可以进行一些缓存并在缓存未命中时重新进行身份验证。很少有服务器是完全无状态的,其输出仅是输入的函数。通常情况下,它是对某种状态进行查询或更新。 - Erik Martino
    3
    不是真的。在这种情况下,您所有的请求都需要之前交易的状态,即用户注册信息。我不明白为什么人们一直试图说存储在服务器上的用户名和密码不属于服务器端状态。请参阅我的答案。 - jcoffland
    1
    @jcoffland,此外,您的解决方案在很大程度上依赖于API服务器解密已签名的令牌的能力。我认为这种方法不仅过于具体,而且有点过于复杂,不能被视为R. Fielding用来解决RESTful身份验证问题的唯一方法。 - Michael Ekoka
    2
    @jcoffland,你明白非对称加密有多么计算密集(因此资源密集和极其缓慢)吗?你正在谈论一种方案,该方案将在每个请求上使用非对称加密。 HTTPS中最慢的部分是初始握手,它涉及创建公钥/私钥以非对称加密共享密钥,随后用于对所有随后的通信进行对称加密。 - Craig Tullis
    显示剩余6条评论

    24

    这当然不是关于"会话密钥"的问题,而通常用于指代在REST的所有约束条件下执行的无状态认证。每个请求都是自描述的,携带足够的信息以便单独授权该请求,无需任何服务器端应用程序状态。

    最简单的方法是从HTTP内置的身份验证机制开始,该机制在RFC 2617中定义。


    HTTP身份验证要求服务器存储用户名和密码。这是服务器端状态,因此并不严格符合REST。请参见我的答案。 - jcoffland
    4
    @jcoffland说的不完全正确。首先,HTTP身份验证并不要求服务器存储密码本身,而是存储密码的哈希值(推荐使用8轮以上的bcrypt算法)。其次,由于授权头随每个请求一起发送,所以服务器不会有任何状态。如果你认为存储密码哈希值算是“状态”,那么它们和存储公钥一样,并不算是真正的状态。 - Boris B.
    1
    @Boris B.,是的,我理解密码是以哈希方式存储的。 哈希密码仍然是客户端特定状态。 与我的解决方案中描述的存储公钥的方式不同,公钥只有一个,即认证服务器的公钥。 这与为每个用户存储密码哈希值非常不同。 无论如何,如果服务器为每个用户存储密码,则它正在存储每个用户的状态,并且不是100%的REST。 - jcoffland
    8
    我认为在服务器上存储用户的哈希密码不应被认为是服务器端状态。用户是资源,包含姓名、地址或哈希密码等信息。 - Codepunkt

    17

    2019年2月16日更新

    之前提到的方法实质上是OAuth2.0的“资源所有者密码凭证”授权类型。这是一个轻松上手的方式。然而,采用这种方法,组织中的每个应用程序都将拥有自己的身份验证和授权机制。推荐的方法是“授权码”授权类型。此外,在我之前下面的回答中,我推荐使用浏览器localStorage来存储认证令牌。然而,我现在认为cookie是这个目的的正确选项。我在this StackOverflow answer中详细说明了我的原因、授权码授权类型实现方法、安全考虑等。


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

    采用这种方法,我们每30分钟就会对缓存进行昂贵的操作,以加载用户特定的访问权限详细信息。因此,如果撤销了某个访问权限或授予了新的访问权限,则需要等待30分钟才能反映出来,或者先注销再登录。


    2
    那么你会在使用 Angular 制作静态网站的 API 中使用它吗? 那移动应用呢? - Yazan Rawashdeh

    15
    @skrebel提到的“非常有深度”的文章(http://www.berenddeboer.net/rest/authentication.html)讨论了一种复杂但真正破损的身份验证方法。
    您可以尝试访问页面(仅供已验证用户查看)http://www.berenddeboer.net/rest/site/authenticated.html,不需要任何登录凭据。
    对于REST和身份验证,我认为二者根本不能混合在一起。 REST意味着无状态,而“已验证”是一种状态。您无法同时在同一层上拥有它们。如果您是RESTful的倡导者,并且对状态表示不满意,则必须使用HTTPS(即将安全问题留给另一层)。

    Stripe.com会对你关于REST和身份验证不兼容的评论持不同意见。 - Erik
    无状态仅指服务器,而不是客户端。客户端可以记住会话的所有状态,并在每个请求中发送相关信息。 - Dobes Vandermeer
    终于有人讲了些有道理的话,但是使用公钥加密可以实现无状态身份验证。请参见我的答案。 - jcoffland
    2
    服务器没有“已认证”状态。它通过超媒体接收信息,并必须处理该信息以返回所请求的内容。不多不少。如果资源受保护并需要身份验证和授权,则提供的超媒体必须包括该信息。我不知道在返回资源之前对用户进行身份验证意味着服务器正在跟踪状态的概念来自哪里。提供用户名和密码可以很好地被视为提供更多的过滤参数。 - Michael Ekoka
    我认为REST和身份验证根本不搭配。这听起来像是一些常识。但是,一个与身份验证不兼容的系统(“已验证”本身当然是一种状态)其实是有限用处的。我感觉我们都在争论实用性和纯粹主义教条之间的交叉点,而实际上实用性应该胜出。REST有很多方面是非常有益的,而不必为了避免身份验证方面的状态而扭曲其原本的设计,不是吗? - Craig Tullis

    12

    我认为RESTful身份验证涉及将身份验证令牌作为请求的参数传递。例如,API使用API密钥。我不相信使用Cookie或HTTP身份验证符合要求。


    应避免使用Cookies和HTTP身份验证,因为存在CSRF漏洞。 - Dobes Vandermeer
    @DobesVandermeer,您能否看一下我的问题并提供帮助?https://stackoverflow.com/questions/60111743/how-to-prevent-rest-web-service-authentication-with-stolen-token - Hemant Metalia

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接