关于 REST 中的“无状态”的一个疑问。

2020-12-24 00:39:54 +08:00
 mitu9527

下面的内容引自 REST 论文原文:

We next add a constraint to the client-server interaction: communication must be stateless in nature, as in the client-stateless-server (CSS) style of Section 3.4.3 (Figure 5-3), such that each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. Session state is therefore kept entirely on the client.

重点是最后一句,翻译过来就是“因此,会话状态完全保存在客户端上”。虽然出自官方,但我对此还是有疑问:

把会话数据存在客户端,也就是使用所谓的“客户端会话”真的算是“无状态”么?

我又去读了很多关于“无状态”的文章、帖子和回复,我看到不少人说这种确实是“无状态”,但我个人总觉得不对。客户端会话数据也是由服务端生成,最终也是由服务端读取,只不过没有被保存在服务端,而是被保存到了客户端,所以它和常见的“服务端会话”的区别只是存储的地方不一样(这种“去中心化”确实有它的好处,但缺点也不少),但作用完全是一样的,难道不是么?

把会话数据从客户端传给服务端,然后服务端读取传过来的会话数据并进行使用,这不也是在读取本次交互过程(或者说会话)中前面某个请求所产生的数据么?那请求和请求之间还算是互相独立么?说好的“人生只如初见”呢?

现在的我觉得如果要“无状态” ,就绝对不应该有“会话”这个概念,不论是“服务端会话”还是“客户端会话”,都不应该存在,感觉使用“客户端会话”来符合“无状态”有点像掩耳盗铃似的。

最后在重复一遍问题:把会话数据存在客户端,也就是使用所谓的“客户端会话”真的算是“无状态”么?

5957 次点击
所在节点    程序员
82 条回复
ZSeptember
2020-12-24 14:45:05 +08:00
客户端生成是不可能的,后端得默认客户端不可信,token 之类的肯定得后端生成。
无状态只是指后端无状态,不需要保存维持 session,可以水平扩展。
session 有不同级别,一个用户登录以后在 token 有效期内所有操作都可以当作一次 session 内的。
也可以是单次 浏览器一个窗口的,比如 浏览器的 session storage,也可以说是单次链接的。
一般在网页端,还是会认为 登录以后,所有操作都在同一个 session 的。
mitu9527
2020-12-24 15:12:15 +08:00
@ZSeptember 我这里不是说 token 啊,做认证的 token 当然在服务端生成,这没毛病。但是交互过程中又不仅仅登陆这一种“状态”,还有各种交互状态,很多人说把这些状态全部丢到客户端,就比如直接丢到 JWT 中。

还是拿我上面举过的例子来说吧。

比如,我有一个账户,即属于运营人员角色,又属于某种管理人员角色,但运行时角色只能二选一,即角色运行时互斥。比如服务端通过一个接口返回可选的角色,客户端把这些角色展现给用户,然后用户选择某个角色,客户端再把用户的选择(即所谓的“状态”)保存在客户端本地存储中,后面每次请求带上该角色(状态)即可。

注意,这个状态可不是服务端在服务端代码里面生成,然后再丢到响应头或者主体里面返回给客户端的,就是地地道道客户端自己生成并保存到客户端本地存储中的。你能说这种有问题么?再者服务端当然不会信客户端的数据啊,但是服务端本来就该做验证啊,比如验证用户是谁,存不存在,权限够不够等等,客户端发篡改的数据过来,服务端验证也过不了。
liman
2020-12-24 15:25:54 +08:00
牛逼!哒哒哒哒哒哒
wzwb
2020-12-24 15:39:46 +08:00
@mitu9527 楼主你这里描述的不是一种业务实现吗,和是不是服务端生成的没有关系。你讲的这个例子是让用户自己选择角色,那按你讲的翻过来的情况,由服务端生成,服务端怎么生成呢,服务器又不可能提前预知用户要选什么角色
ZSeptember
2020-12-24 15:45:02 +08:00
@mitu9527 #62 这个场景,JWT 需要保存有一个用户的角色列表,当前角色不应该放在 JWT 的。服务端会校验当前角色是否在 JWT 的角色列表中,所以服务端还是无状态的。当然这同时也暴露了 JWT 的一个缺陷,无法注销,如果用户角色调整,删除了一个角色,用原来的 token 就可以越权了。
mitu9527
2020-12-24 15:57:11 +08:00
@wzwb 发起确实是用户发起,然后告知客户端,然后客户端发给服务端,然后服务端生成会话数据,然后发回到客户端保存,然后下次客户端再自动发回给服务端。第四句就是生成,然后你不觉得第三句到第五句是在兜圈子么?客户端都知道用户在交互中的选择了,干嘛还要发给服务端,直接自己保存不好么,然后每次请求都发给服务端,服务端直接用不就成了。
wzwb
2020-12-24 16:05:13 +08:00
@mitu9527 如果服务端不保存这个状态,那如果用户换了设备呢,或者重新登录。客户端的数据此时都没了,难道让用户重新再选一遍,这个时候不应该由服务器返回历史数据吗
mitu9527
2020-12-24 16:12:17 +08:00
@ZSeptember JWT 里面就一个用户标识和过期时间,然后存在客户端本地存储,用户当前所选的角色也存在客户端本地存储,每次向服务端发送请求,直接把 JWT 放在 Authentication 头中,把当前所选的角色放在请求参数或者主题中即可。如果有人把客户端选的角色删了,本次请求也验证不过啊,返回一个业务状态码就能和客户端说清楚了啊,所以有什么好担心的呢?

在服务端看来所谓的登陆就是创建一个令牌资源,令牌资源能创建,当然就该保存,当然也就能删除。服务端颁发 JWT 后,它确实能通过防篡改、伪造的检查,但 JWT 只不过是一个壳,真正重要的是 payload 里面的 jwt id,它可以用来识别用户,也可以被回收。
mitu9527
2020-12-24 16:22:56 +08:00
@wzwb 你自己到底要用服务端会话还是不用?如果你选择不用,那你说的由服务器返回历史数据从何谈起?如果你选择用服务端会话,那就没啥要讨论的了。

在用户做选择的时候一个请求发给服务端,告诉服务端客户选了哪个角色当作自己的全局默认角色不就可以了,服务端把这种角色保存起来就可以了,而且这种数据也不应该保存在服务端会话中,这应该保存在数据库中吧。
wzwb
2020-12-24 16:28:42 +08:00
@mitu9527 我看了你说的后一种情况,我感觉你是没理解这里说的无状态是什么意思。你后面讲的这种情况就是 OpaqueToken 或者 sessionId 的用法,这才是这个上下文里面说的有状态的情况。如果别人发一个用户标识来,你为了验证参数是否合法,不可避免的要和数据库、缓存或者认证服务器交互查询出来用户信息,而 jwt 就是为了减少这部分的查询设计的,可以在服务端离线验证减少不必要的查询。
eason1874
2020-12-24 16:28:53 +08:00
我理解的无状态是指“服务端的某一层或某一个组件,不专门为访问用户维护一份会话数据”,这不代表整个服务端没有状态。

一个组件有状态是怎么样的?比如 FTP,当你连接 FTP 打开一个目录,你问 FTP 我在哪儿,FTP 会给你返回当前目录。这就叫有状态,FTP 自己维护了会话。

无状态呢? HTTP 是无状态的,当你连接 HTTP 打开一个目录,你问 HTTP 我在哪儿,HTTP 并不知道,因为 HTTP 处理完就算了,这就叫无状态。你告诉 HTTP 要去哪个路径,HTTP 就带你去,但是不会记下来,所以你每次都要告诉 HTTP 去哪儿。

但是,这并不代表着通过 HTTP 实现的业务就不能有状态了。通过 IP 、HTTP Header 、Cookies 等信息,你也可以标记唯一用户,在处理 HTTP 请求的时候给用户维护一份会话数据,但这就跟 HTTP 无状态没关系了,你不能把通过其他方式实现的会话说成是 HTTP 的状态。

所以说,把会话数据存在客户端也可以是无状态。而且无状态必须要把会话数据存在客户端,这样才能告诉无状态的服务组件我是谁我要去哪儿。如果只存一个身份标识,像以前的 Session ID,就要求服务组件有状态了,在多服务器的时候必须同步会话数据。
mitu9527
2020-12-24 16:38:42 +08:00
@wzwb 大家公认的 Authentication: Basic username:password 这种是 RESTful 的,这种没去根据用户名和密码查询么?为了不在每次请求中都传输账户和密码,所以发明了令牌代替用户名和密码来做认证,所以有啥问题?

你能试着先把会话那一套东西忘掉么,要不然就没办法聊了,每说两三句,你的思路就回到会话那边去了。
pinews
2020-12-24 16:43:23 +08:00
@mitu9527 我觉得这个无状态是相对的,不是绝对的,没有两次请求是完全相同的,除非符合你的预期,即达成协议的无状态满足需求。
比如普通人吃饭感受个色香味就完了,美食家则要感受更多的信息,老板则要感受能否带来盈利,你就必须用心去体验。

原来的 http 协议之前,邮件,用户组,ftp 都是必须认证,一步接一步,最后退出这样的流程,
http 也支持长连接和基本认证,现在注册登录却没有使用那种形式,另外 http 的繁琐也出现了 WebSocket,都是为了最合适的应用场景。

新名词的确不一定名副其实,为了推销自己使劲吹。
wzwb
2020-12-24 16:48:43 +08:00
@mitu9527 感觉你可能没有实践过任何一种认证方式。你说的用法才是典型的有状态,依赖于用户标识查询用户然后验证参数(除非你选择不验证,否则必须得查询)。jwt 是为了资源服务器能在本地离线验证用户设计的,所以不需要保存或者同步会话的操作,只需要解析 jwt 就能获取到用户信息,这种服务器才可以随时且独立的伸缩,你说的那种情况随着资源服务器的伸缩,数据库、缓存或者认证服务器都需要同步伸缩,否则就会遇到单点问题。
mitu9527
2020-12-24 17:04:07 +08:00
@wzwb 你说的不是就会话么,而且是就是客户端会话,只不过借助 JWT 实现了,所以我才说你一直没离开会话,而我想讨论的就是完全离开会话,包括服务端会话和客户端会话。我这种 JWT,就是做认证用户的,多余的什么都没有,服务端也不保存,交互状态我也认为应该和 JWT 一样,都存在客户端,服务端什么都没有,根本就不存在中心化的问题,哪里来的单点和水平扩展问题。

算了算了,没啥好讨论的了。
wzwb
2020-12-24 17:07:09 +08:00
“大家公认的 Authentication: Basic username:password 这种是 RESTful 的”
=========

而且你能讲出来这种话我也不知道说什么好了。请你看一下你发出来原文好吗"and cannot take advantage of any stored context on the server." 你是不是不理解什么叫 any stored context on the server.
wzwb
2020-12-24 17:10:36 +08:00
@mitu9527 你说的这种方式不可避免的要用到”any stored context on the server“,否则怎么验证用户的参数是否合法,他发过来一个管理员角色,你这种方式能在”cannot take advantage of any stored context on the server“的情况下验证他是不是管理员吗,但是 Jwt 可以做到
mitu9527
2020-12-24 17:12:23 +08:00
@wzwb 这是客户端发送的请求头部啊,这有服务端什么事么?你真的知道这个是什么么?
mitu9527
2020-12-24 17:15:11 +08:00
@wzwb 服务端存的是资源和资源的状态,你却在纠结服务端数据库里面有保存数据,就是中心化,就是有状态。哪个人敢站出来说无状态就是不能在数据库里面存数据?你这是开始扯皮了???
ZSeptember
2020-12-24 17:31:43 +08:00
不说其他,就说 JWT 无状态,就是说服务器不需要从其他任何中心化的存储中去取数据就能校验 token 的身份。。只要 token 就能校验,所以才有我说的问题。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/738435

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX