会话是否真的违反了RESTful架构?

557
在RESTful API中使用会话是否真的违反了RESTfulness原则?我看到许多不同的观点,但我并不认为会话是“不符合REST”的。从我的角度来看:
  • 身份验证并不违反RESTfulness(否则RESTful服务的用处就很少)
  • 身份验证通过在请求中发送身份验证令牌来完成,通常是在标头中
  • 需要以某种方式获取此身份验证令牌,并且可能会被取消注册,这时需要更新令牌
  • 服务器需要验证身份验证令牌(否则就不会进行身份验证)
那么,会话如何违反这一点呢?
  • 客户端使用cookie来实现会话
  • cookie只是额外的HTTP标头
  • 会话cookie可以随时获取和取消注册
  • 如果需要,会话cookie可以具有无限的生命周期
  • 会话ID(身份验证令牌)在服务器端进行验证
因此,对于客户端而言,会话cookie与任何其他基于HTTP标头的身份验证机制完全相同,除了它使用“Cookie”标头而不是“Authorization”或其他专有标头。如果没有会话附加到cookie值上,服务器端为什么会有所不同呢?只要服务器端遵守RESTful原则,服务器端实现不需要关注客户端。因此,cookie本身不应该使API不符合RESTful,并且会话对于客户端来说只是cookie。
我的假设是否错误?什么使会话cookie“不符合REST”呢?

5
我已经在这里详细讨论过这个问题了:https://dev59.com/O3M_5IYBdhLWcg3wn0vT#1297275 - Will Hartung
5
此外,如果你只是在使用会话进行身份验证,为什么不使用提供的标头呢?如果不是这样,并且你正在使用会话来处理其他对话状态,则违反了REST的无状态约束。 - Will Hartung
3
谢谢。你似乎在谈论用于暂时存储用户提交数据的会话,而在我的情况下,我只是将它们作为身份验证的实现细节来讨论。这可能是意见分歧的原因吗? - deceze
3
我的唯一观点是,如果你要使用头部来表示认证令牌,HTTP提供了一个比通用Cookie更好的选择。那么,为什么不使用它并保持其免费的语义(任何查看负载的人都可以看到负载中有一个分配给它的认证令牌)。 - Will Hartung
8
可以,但为什么不自己编写标题,或者劫持其他标题来作为授权令牌的标识呢?可以使用 X-XYZZY 标题,这只是语法问题。报头传递信息,"授权(Authorization)"标题比你的 cookie 更加 "自我记录(self-documenting)",因为 "所有人" 都知道 Auth 标题的用途。如果他们只看到 JSESSIONID (或其他内容),他们就无法做出任何假设,或者更糟糕的是,做出错误的假设(他还在会话中存储了什么,这还有其他用途吗等)。你在代码中命名变量时使用 Aq12hsg 吗?当然不是。同样的道理也适用于这里。 - Will Hartung
显示剩余8条评论
9个回答

366
首先,REST不是一种宗教,也不应该被视为这样。虽然使用RESTful服务有优势,但你只需要按照对你的应用有意义的程度遵循REST原则。
话虽如此,认证和客户端状态并不违反REST原则。虽然REST要求状态转换是无状态的,但这是指服务器本身。在本质上,REST的所有内容都是关于文档的。无状态的想法是,服务器是无状态的,而不是客户端。任何发出相同请求的客户端(相同的头信息、cookie、URI等)都应该被带到应用程序中的同一个位置。如果网站存储了用户的当前位置,并通过更新服务器端导航变量来管理导航,则会违反REST原则。另一个具有相同请求信息的客户端将根据服务器端状态被带到不同的位置。
Google的网络服务是RESTful系统的绝佳例子。每个请求都需要通过用户的身份验证密钥传递身份验证标头。这确实略微违反了REST原则,因为服务器正在跟踪身份验证密钥的状态。必须维护此密钥的状态,并且它具有某种过期日期/时间,在此之后不再授予访问权限。但是,正如我在帖子开头提到的那样,必须做出牺牲才能使应用程序实际工作。也就是说,必须以允许所有可能的客户端在其有效时间内继续授予访问权限的方式存储身份验证令牌。如果一个服务器管理身份验证密钥的状态,以至于另一个负载平衡服务器无法接管基于该密钥的请求,则您已经开始真正违反REST原则。 Google的服务确保您可以随时将您在手机上使用的身份验证令牌针对负载平衡服务器A进行请求,然后从桌面上击中负载平衡服务器B并仍然可以访问系统,并且如果请求相同,则被定向到相同的资源。
归根结底,您需要确保针对某些支持存储(数据库,缓存等)验证身份验证令牌,以确保尽可能保留REST属性。
我希望所有这些都有意义。如果您还没有,请查看wikipedia article on Representational State TransferConstraints section,这对于REST实际上在争论什么以及为什么非常有启发性。

7
如果REST的限制符合您的应用程序,那么请使用REST。您可以自由地应用这些限制的子集,获得一部分好处。但是,在此时,您已经创建了自己的架构风格。不过这并不是坏事。实际上,这正是Roy论文中的前四章所讨论的原则设计。REST只是其中的一个例子。 - Darrel Miller
4
一个很好的观点。老实说,我不确定Google是如何做到这一点的,但过期时间可能被编码到身份验证令牌中。不过,我认为我的更重要的观点仍然成立。有一些类型的状态必须要保持,并且只要你理解了为什么REST要求无状态,你可以在某种程度上违反它,而对系统的其他部分和RESTful架构的优势没有太多影响。 - Jared Harding
13
目前还没有提出其他论点,因此我接受这篇写得很好的回答。我认为重要的是,“无状态服务器”并不意味着“无状态服务器”,这是常常被误解或错误应用的。服务器可以(而且通常必须)拥有任何它想要的状态,只要它表现得是幂等的。 - deceze
11
我听过很多关于会话不够轻松的演讲。但是,如果你想构建一个Web应用程序,HTTP基本身份验证确实是一个真正的倒退。 - Ben Thurley
1
@Micah Henning,你犯了一个错误的假设,即服务器需要状态信息来验证身份验证令牌。我们可以合理地假设,如果您不知道私钥,则无法伪造由公共/私有密钥对签名的令牌。要验证令牌是否有效,您只需要公钥。我仍然认为完全符合RESTful的身份验证是可能的。 - jcoffland
显示剩余11条评论

337

首先,让我们定义一些术语:

  • RESTful:

    根据wikipedia的描述,符合REST约束条件的应用程序可以被称为“RESTful”。如果服务违反了任何所需的约束条件,则无法被视为RESTful。

  • 无状态约束:

    接下来,我们向客户端-服务器交互添加了一个约束:通信必须具有无状态性质,就像第3.4.3节(图5-3)中的客户端-无状态服务器(CSS)样式一样,因此客户端到服务器的每个请求都必须包含理解请求所需的所有信息,并且不能利用服务器上的任何存储上下文。因此,会话状态完全保留在客户端上。

    根据Fielding dissertation的描述。

服务器端的会话违反了REST的无状态约束,因此也违反了RESTfulness。
对于客户端而言,会话cookie与任何其他基于HTTP头的身份验证机制完全相同,只是它使用Cookie头而不是Authorization或其他专有头。
通过会话cookie,在服务器上存储客户端状态,因此您的请求具有上下文。让我们尝试向系统添加负载均衡器和另一个服务实例。在这种情况下,您必须在服务实例之间共享会话。维护和扩展这样的系统很困难,因此它的可扩展性很差...
在我看来,cookie没有问题。 cookie技术是一种客户端存储机制,其中存储的数据会自动附加到每个请求的cookie头中。我不知道哪个REST约束与这种技术有问题。因此,技术本身没有问题,问题在于它的使用。Fielding写了一个子章节,解释了他为什么认为HTTP cookie不好。
根据我的观点: - 认证并不违反RESTful(否则RESTful服务的用处就很小了) - 通过在请求中发送身份验证令牌来完成认证,通常是头部 - 需要以某种方式获取此身份验证令牌,并且可能会被撤销,在这种情况下需要更新 - 服务器需要验证身份验证令牌(否则它就不是认证)
你的观点非常有道理。唯一的问题在于在服务器上创建身份验证令牌的概念。您不需要那部分。您需要的是在客户端存储用户名和密码,并将其与每个请求一起发送。您所需的不仅是HTTP基本身份验证,还需要加密连接。

Figure 1. - Stateless authentication by trusted clients

  • 图1. - 受信任客户端的无状态身份验证

为了使每个请求都要进行身份验证,您可能需要在服务器端使用内存中的身份验证缓存来加快速度。

现在,这对于您编写的受信任客户端非常有效,但第三方客户端怎么办?它们无法获得用户的用户名、密码和所有权限。因此,您必须单独存储特定用户的第三方客户端可以拥有的权限。这样,客户端开发人员就可以注册其第三方客户端,并获取唯一的API密钥,而用户则可以允许第三方客户端访问其某些权限的部分。例如读取姓名和电子邮件地址,或列出他们的朋友等等... 在允许第三方客户端之后,服务器将生成一个访问令牌。这些访问令牌可以被第三方客户端用于访问用户授予的权限,如下所示:

Figure 2. - Stateless authentication by 3rd party clients

  • 图2 - 第三方客户端的无状态身份验证

因此,第三方客户端可以从受信任的客户端(或直接从用户)获取访问令牌。在此之后,它可以使用 API 密钥和访问令牌发送有效请求。这是最基本的第三方认证机制。您可以在每个第三方认证系统的文档中阅读更多有关实现细节的信息,例如 OAuth。当然,这可能会更加复杂和安全,例如您可以在服务器端签署每个单独请求的详细信息并随请求一起发送签名等等...... 实际解决方案取决于您应用程序的需求。


6
是的,你完全正确。自从我发了这个问题后,我完全改变了看法。在技术细节上看,会话cookie并不是什么特别的东西,但这种观点忽视了大局。因为你的漂亮图表,我接受了你的答案。 ;) - deceze
3
@inf3rno,全面的RESTful服务不能像传统方式实现的那样依赖会话cookie进行身份验证是确实的。然而,如果cookie包含服务器稍后需要的所有状态信息,您可以使用cookie执行身份验证。您还可以通过使用公钥/私钥对对其进行签名,使cookie免受篡改的影响。请参见下面的评论。 - jcoffland
1
@DerekPerkins 我正在使用防篡改的cookie,就像这篇文章中所述:https://hacks.mozilla.org/2012/12/using-secure-client-side-sessions-to-build-simple-and-scalable-node-js-applications-a-node-js-holiday-season-part-3/ 非常有趣的内容。不过有点具体,可能因人而异。 - slacktracer
2
@SiminJie 我认为你需要问自己是否真的需要一个REST API。这通常不是合理的解决方案。我的意思是,有许多限制大多数开发人员不理解或不关心。它需要大量的学习才能正确地完成,并且只有真正大型服务才值得付出努力。用一个业余项目尝试已经了解的东西会更容易些。 - inf3rno
16
我不明白为什么大家都似乎认可“应该将密码存储在客户端并随每个请求发送”的评论。这是一种非常糟糕的做法,会危及客户敏感数据。明文密码(必须明文发送)不应该被存储在任何地方。如果我们接受这个做法,那么你就像大多数身份验证系统一样使用令牌,在这种情况下,我们用于扩展令牌库的机制将具有与任何会话扩展性一样的可扩展性问题。 - lvoelk
显示剩余17条评论

13

Cookie并不是用于身份验证的。为什么要重新发明轮子呢?HTTP已经有了设计良好的身份验证机制。如果我们使用cookie,那么我们就只能将HTTP作为传输协议来使用,因此我们需要创建自己的信令系统,例如告诉用户他们提供了错误的身份验证信息(使用HTTP 401可能是不正确的,因为我们可能不会向客户端提供Www-Authenticate,因为HTTP规范要求 :))。值得注意的是,Set-Cookie只是给客户端的建议。它的内容可能会被保存,也可能不会被保存(例如,如果禁用cookie),而Authorization头会在每个请求中自动发送。

另一个问题是,想要获取授权cookie,你可能需要在某个地方提供你的凭据吧?如果是这样的话,那不就不符合RESTful了吗?下面是一个简单的例子:

  • 您尝试无cookie访问GET /a
  • 您以某种方式得到了一个授权请求
  • 您进行授权,例如POST /auth
  • 您获得了一个Set-Cookie
  • 您再次尝试带cookie的访问GET /a。但在这种情况下,GET /a是否具有幂等性呢?

总之,我认为如果我们访问某个资源并且需要进行身份验证,那么我们必须在该资源上进行身份验证,而不是在其他任何地方。


1
与此同时,我也更加赞同这个观点。我认为从技术上讲并没有太大的区别,这只是HTTP头而已。不过,如果需要通过单独的地址进行登录,则身份验证行为本身就不符合RESTful标准。因此,Cookie只是身份验证系统中更大问题的一种表现。 - deceze
这并没有考虑到Web浏览器仅支持“Authorization:Basic”或“Digest”的事实。如果您想在浏览器上下文中执行比基本身份验证或摘要身份验证更高级的操作(而且您应该这样做),那么您需要使用除“Authorization”标头之外的其他内容。 - Oliver Charlesworth
1
当然可以 - 如果你只在做纯JS,那么基本上没问题(除了例如Websockets)。但我的观点是,在浏览器场景中,基于API的身份验证并不一定是唯一的考虑因素。 - Oliver Charlesworth
8
没有cookie和有cookie的情况下,GET /a是两个不同的请求,它们的行为差异是可以被接受的。 - TRiG
1
继@TRiG之后,按照这个逻辑,带有身份验证头的GET /a与不带身份验证头的GET /a是相同的,因此对于REST来说同样无法使用。如果您要将一个HTTP头与另一个头区别对待,那么您至少需要解决这个问题。 - Jasper
显示剩余3条评论

10
实际上,RESTfulness仅适用于资源,如通用资源标识符所示。因此,谈论关于REST的头文件、cookie等内容并不合适。REST可以在任何协议上运行,尽管它通常是通过HTTP完成的。
主要的判断因素是:如果您发送一个REST调用,即URI,则一旦调用成功到达服务器,假设没有执行转换(PUT、POST、DELETE),那么该URI是否返回相同的内容?此测试将排除错误或身份验证请求的返回,因为在这种情况下,请求尚未到达服务器,这意味着将返回与给定URI对应的文档的servlet或应用程序。
同样,在POST或PUT的情况下,您可以发送给定的URI/payload,而无论您发送多少次消息,它都将始终更新相同的数据,以便随后的GET将返回一致的结果吗?
REST关注的是应用程序数据,而不是传输该数据所需的低级信息。
在以下博客文章中,Roy Fielding对整个REST思想进行了很好的总结:

http://groups.yahoo.com/neo/groups/rest-discuss/conversations/topics/5841

REST架构系统从一个稳定状态到下一个稳定状态,每个稳定状态既是潜在的起始状态又是潜在的终止状态。也就是说,RESTful系统由未知数量的组件遵循一组简单的规则,它们始终处于REST或从一个RESTful状态转换到另一个RESTful状态。每个状态可以通过包含的表示和提供的过渡集合来完全理解,过渡被限制为可理解的统一动作集合。该系统可能是一个复杂的状态图,但每个用户代理只能看到一个状态(当前的稳态),因此每个状态都是简单的并且可以独立分析。然而,用户能够随时创建自己的过渡(例如输入URL、选择书签、打开编辑器等)。
关于身份验证的问题,无论是通过cookie还是header完成的,只要信息不是URI和POST负载的一部分,它与REST没有任何关系。所以,关于无状态,我们只谈论应用程序数据。
例如,当用户输入数据到GUI屏幕时,客户端会跟踪哪些字段已经输入,哪些没有,缺少任何必填字段等。这都是客户端上下文,并且不应该被发送或由服务器跟踪。发送到服务器的是需要在已识别资源(通过URI)中修改的完整字段集,以便在该资源中从一个RESTful状态转换到另一个状态。
因此,客户端跟踪用户正在做什么,并仅向服务器发送逻辑上完整的状态转换。

3
我不明白这如何有助于回答提出的问题。 - jcoffland

2
据我所知,当我们谈论会话时,有两种类型的状态:
  • 客户端和服务器交互状态
  • 资源状态
在Rest中,无状态约束指的是第二种类型。使用cookie(或本地存储)不违反Rest,因为它与第一种类型相关。
Fielding说:“每个从客户端到服务器的请求都必须包含理解请求所需的所有信息,并且不能利用服务器上存储的任何上下文。因此,会话状态完全保留在客户端上。”
重要的是,要在服务器上执行的每个请求都需要来自客户端的所有必要数据。然后这被视为无状态的。再次强调,这里不是在讨论cookie,而是在讨论资源。

1
HTTP事务,基本访问认证并不适用于RBAC,因为基本访问认证每次使用加密的用户名:密码来识别,而在RBAC中需要的是用户想要为特定调用使用的角色。RBAC不验证用户名的权限,而是验证角色的权限。
您可以尝试连接如下:usernameRole:password,但这是一种不好的做法,而且效率也很低,因为当用户有更多角色时,身份验证引擎需要测试所有连接中的角色,并且每次都要再次调用。这将破坏RBAC最大的技术优势之一,即非常快速的授权测试。
因此,基本访问认证无法解决这个问题。
为了解决这个问题,必须进行会话维护,而根据一些答案,这似乎与REST相矛盾。
这就是我喜欢的答案关于REST不应该被视为宗教的原因。在复杂的业务案例中,例如医疗保健,RBAC是绝对常见和必要的。如果所有REST工具设计者都将REST视为宗教,那么他们将不被允许使用REST,这将是遗憾的。
对我来说,维护HTTP会话的方法并不多。可以使用带有sessionId的cookie或带有sessionId的标头。

如果有其他想法,我很乐意听取。


0

-2

不,使用会话并不一定违反RESTful规范。如果您遵循REST原则和约束条件,那么使用会话来维护状态只是多余的。毕竟,RESTful要求服务器不维护状态。


2
在我看来,大多数回答都误解了API符合RESTful的含义。一个RESTful API满足REST的约束条件:统一接口、无状态、可缓存、客户端-服务器、分层系统、按需代码。你的API完全可以在满足这些约束条件的同时实现会话。 - user14699123

-3
  1. 会话不是无状态的
  2. 你的意思是REST服务只适用于http吗?还是我理解错了?基于Cookie的会话只能用于自己的(!)基于http的服务!(从移动/控制台/桌面等设备中使用Cookie可能会有问题。)
  3. 如果您为第三方开发人员提供RESTful服务,请勿使用基于Cookie的会话,而应使用令牌来避免安全问题。

4
Cookie 不应用于存储服务器上会话的会话密钥,该服务器持有认证令牌。但如果 Cookie 本身持有认证令牌,则是可行的解决方案。(当然,Cookie 应该是 httponly 和安全的)。 - roberkules

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