如何理解“RESTful API 是无状态的”?

Posted

技术标签:

【中文标题】如何理解“RESTful API 是无状态的”?【英文标题】:How to understand "RESTful API is stateless"? 【发布时间】:2016-03-11 20:19:51 【问题描述】:

我听说“RESTful API 应该是无状态的。所有状态信息都应该保存在客户端”。

但是当我从网页发出 AJAX 调用时,我注意到会话 ID cookie 总是被发送到服务器。使用该会话 ID,我可以获得服务器上的会话对象,因此我可以“获取/设置会话中的一些状态信息”。

这会破坏 RESTful API 的“无状态代码”吗?

添加 1

(我的问题背景如下。)

我尝试通过调用 RESTful API 来验证用户名和密码来实现登录页面。

每次用户尝试访问我网站的页面时,登录 servlet filter 将检查该用户的 session(这是调用 getSession() 的地方)以查看是否存在有效的登录信息。如果没有,登录过滤器会将用户重定向到登录页面。

在登录页面上,使用用户名和密码对服务器上的 RESTful API 进行 AJAX 调用。根据 RESTful API 调用的结果,页面上的 javascript 将决定是否让用户进入我的网站。

所以,在这种情况下,我不得不使用session

详细代码在这里: Is this login logic via RESTful call sound?

【问题讨论】:

你不应该让客户端上的javascript决定是否让用户进入网站。客户端不可信。 好的,我很难在没有看到实际代码的情况下评论细节,但请记住,用户可以在运行之前更改客户端代码,并替换对登录功能的调用不断说登录没问题。 毫无疑问,REST API 不能依赖于 HTTP 会话。看看我前段时间提供的answer。它会给你一些关于如何执行身份验证的灵感。该方法使用令牌,但可以修改以涵盖其他类型的身份验证。 @CássioMazzochiMolin 我同意你的看法。但是为什么 HttpSession 参数仍然可以在 RESTful API 签名中指定呢?看来Spring框架permits在RESTful API中使用了session。或者这只是依赖注入的副作用/巧合/误用? (参考:***.com/questions/26588310/…) 正如我之前提到的,nothing 会阻止您调用 HttpServletRequest.getSession() 并打破 REST 无状态约束。你提到的问题链接a good question about common REST mistakes。 【参考方案1】:

简单地说:在 REST 应用程序中,每个请求都必须包含服务器理解所需的所有信息,而不是依赖于服务器记住之前的请求。

在服务器上存储会话状态违反了 REST 架构的无状态约束。所以会话状态必须完全由客户端处理。

继续阅读以了解更多详情。

会话状态

传统的网络应用程序使用远程会话。在这种方法中,应用程序状态完全保存在服务器上。请参阅 Roy T. Fielding 的 dissertation 的以下引用:

3.4.6 Remote Session (RS)

远程会话样式是客户端-服务器的一种变体,它试图最小化客户端组件而不是服务器组件的复杂性,或最大化重用性。每个客户端在服务器上发起一个会话,然后调用服务器上的一系列服务,最终退出会话。应用程序状态完全保存在服务器上。 [...]

虽然这种方法带来了一些优势,但它降低了服务器的可扩展性:

远程会话样式的优点是更容易在服务器上集中维护接口,减少对功能扩展时部署的客户端不一致的担忧,如果交互使用扩展会话上下文来提高效率服务器。缺点是由于存储了应用程序状态,它降低了服务器的可扩展性,并降低了交互的可见性,因为监视器必须知道服务器的完整状态。

无状态约束

REST 架构风格定义在一组约束之上,包括服务器的无状态。根据 Fielding,REST 无状态约束定义如下:

5.1.3 Stateless

[...] 从客户端到服务器的每个请求都必须包含理解请求所需的所有信息,并且不能利用服务器上存储的任何上下文。因此,会话状态完全保留在客户端上。 [...]

这个约束引入了visibilityreliabilityscalability的属性:

提高了可见性,因为监控系统不必超越单个请求数据来确定请求的全部性质。可靠性得到了提高,因为它简化了从部分故障中恢复的任务。可扩展性得到了改进,因为不必在请求之间存储状态允许服务器组件快速释放资源,并进一步简化了实现,因为服务器不必跨请求管理资源使用情况。

认证与授权

如果客户端请求需要身份验证的受保护资源,则每个请求都必须包含正确验证/授权所需的所有数据。请参阅RFC 7235 中的这句话:

假定 HTTP 身份验证是无状态的:必须在请求中提供对请求进行身份验证所需的所有信息,而不是依赖于服务器记住先前的请求。

并且身份验证数据应该属于标准的 HTTP Authorization 标头。来自RFC 7235:

4.2. Authorization

Authorization 标头字段允许用户代理向源服务器验证自己的身份——通常但不一定在收到401(未授权)响应后。它的值由凭证组成,其中包含正在请求的资源领域的用户代理的身份验证信息。 [...]

这个 HTTP 头的名字很不幸,因为它携带 authentication 而不是 authorization 数据。

对于身份验证,您可以使用Basic HTTP Authentication 方案,它将凭据作为用户名和密码对传输,使用 Base64 编码:

Authorization: Basic <credentials>

如果您不想在每个请求中发送用户名和密码,可以将用户名和密码交换为在每个请求中发送的令牌(例如JWT)。 JWT 可以包含用户名、到期日期和任何其他可能与您的应用程序相关的元数据:

Authorization: Bearer <token>

你的服务器可能有什么问题

一旦你有了会话标识符,我猜想你的应用程序中某处正在创建一个 HTTP 会话。它可以在您自己的代码中,也可以在您正在使用的框架的代码中中。

在 Java 应用程序中,您必须确保以下方法不会被调用:

HttpServletRequest#getSession() HttpServletRequest#getSession(boolean)true

【讨论】:

谢谢。所以基本上,如果我遵循 RESTful 方法,HTTP 协议是我唯一的朋友。是的,我正在使用HttpServletRequest.getSession() 电话。似乎技术本身并不能阻止我这样做(实际上它有点像lures我这样做)。我需要坚持discipline @Cássio Mazzochi Molin,很好——我想我现在理解得更好了:这里的无状态实际上意味着每个连接都是“新的”,从某种意义上说,我们不必“证明”到我们在先前请求中发起会话的服务器(例如 cookie 的情况)。不可能在不同请求之间“中断”“会话”,因为现在没有。 @dinvlad 没错。在 RESTful 应用程序中,没有服务器端的会话之类的东西。以受保护资源为目标时,请求必须携带要进行身份验证/授权的凭据。保护 REST 应用程序的常用方法是基于 HTTPS 的基本身份验证。在这种方式中,客户端必须在每个请求中发送用户名和密码,然后服务器才能进行身份验证/授权。 @dinvlad 在基于令牌的认证方案中,令牌成为用户的凭证。用户名和密码等硬凭证被交换为必须在每个请求中发送的令牌,然后服务器可以执行身份验证/授权。令牌可以在短时间内有效,可以撤销,可以携带范围详细信息(可以使用令牌请求的内容)等。 没有任何带有数据库的应用程序意味着它不是无状态的吗?你在哪里画出使请求有状态的界限?除非您的应用程序只是纯函数,否则它必须从某个地方存储和检索数据,因此输入并不能完全确定输出。会话状态有何不同?这只是与当前用户正在做什么相关的另一种应用程序状态。

以上是关于如何理解“RESTful API 是无状态的”?的主要内容,如果未能解决你的问题,请参考以下文章

如何理解python的“变量无类型,数据(对象)有类型“

如何理解python的“变量无类型,数据(对象)有类型“

如何理解python的“变量无类型,数据(对象)有类型“

如何理解python的“变量无类型,数据(对象)有类型“

通俗理解决策树(概述无公式)

如何从 PyMC3 中的狄利克雷过程中提取无监督集群?