V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
mitu9527
V2EX  ›  程序员

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

  •  1
     
  •   mitu9527 · 2020-12-24 00:39:54 +08:00 · 5337 次点击
    这是一个创建于 1190 天前的主题,其中的信息可能已经有所发展或是发生改变。

    下面的内容引自 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.

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

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

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

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

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

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

    82 条回复    2020-12-24 17:42:45 +08:00
    Jooooooooo
        1
    Jooooooooo  
       2020-12-24 00:43:07 +08:00   ❤️ 2
    又要提那一句: 少纠结定义问题

    有具体问题直接具体分析
    GeruzoniAnsasu
        2
    GeruzoniAnsasu  
       2020-12-24 00:44:02 +08:00   ❤️ 3
    such that each request from client to server must contain all of the information necessary to understand the request

    划重点
    这是所谓的无状态,比如登录与否的状态不应由连接来保持,而是每个请求中都含有完整的鉴权信息
    mitu9527
        3
    mitu9527  
    OP
       2020-12-24 00:47:02 +08:00
    @Jooooooooo 我也不想纠结这个问题,只不过看到好多人的想法和自己不同,连原论文都和自己的想法不同,三人成虎啊,我担心自己是不是哪里没搞明白,所以才来提问。
    Jooooooooo
        4
    Jooooooooo  
       2020-12-24 00:48:33 +08:00
    @mitu9527 你能弄明白为什么要有无状态, 会话这些东西. 及其特性带来的好处就行了.
    fengchang
        5
    fengchang  
       2020-12-24 00:49:36 +08:00   ❤️ 1
    我觉得算,你这个问题很奇怪
    mitu9527
        6
    mitu9527  
    OP
       2020-12-24 00:53:49 +08:00
    @fengchang 理由是?你不觉得都已经有会话状态了,还能说再说是“无状态”么?
    mitu9527
        7
    mitu9527  
    OP
       2020-12-24 00:57:11 +08:00
    @GeruzoniAnsasu 所以你的观点是?算还是不算?
    GeruzoniAnsasu
        8
    GeruzoniAnsasu  
       2020-12-24 01:01:10 +08:00   ❤️ 1
    @mitu9527 我的观点是



























    我遵循“智能 REST*互联网+技术”
    这个技术的核心要义是 Each request from client to server must contain all of the information necessary to understand the request.
    Bromine0x23
        9
    Bromine0x23  
       2020-12-24 01:02:25 +08:00
    https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_4_3

    3.4.3 Client-Stateless-Server (CSS)
    The client-stateless-server style derives from client-server with the additional constraint that no session state is allowed on the server component. 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 kept entirely on the client.



    https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_3

    5.1.3 Stateless
    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.
    YouMoeYi
        10
    YouMoeYi  
       2020-12-24 01:06:56 +08:00 via Android
    老哥我觉得你可能搞错了无状态所形容的对象。我们说 HTTP 是无状态的,是指 HTTP 在两次连接之间不能保存状态而不是客户端或者服务端不能保存状态,客户端和服务端的会话就是用来解决 HTTP 无状态所带来的缺陷。
    fengchang
        11
    fengchang  
       2020-12-24 01:13:24 +08:00   ❤️ 1
    @mitu9527 REST 是一项服务端的技术,它的范围就到 API,它不关心你是用浏览器、客户端、curl 还是 Postman 访问。定义这个事情当然是定义者说了算,他的定义就是 REST 的服务端不保存状态,你为啥非要说客户端也不能保存会话才算无状态?

    你可以用 curl 访问 RESTful 的服务,然后把 cookie 记在脑子里,每次输入命令的时候打出来,这样客户端也无状态了。那是不是可以继续问“脑子里有会话真的算是无状态吗”?
    lihongming
        12
    lihongming  
       2020-12-24 02:00:34 +08:00 via iPhone   ❤️ 1
    无状态是相对于以前的 session 来说的,以前登录状态可以保存在 session 里,服务端拿着客户端传来的 session id 就知道状态了。

    而在横向扩展司空见惯的云计算时代,各服务器之间同步 session 是一个成本很高的任务,所以状态就放在客户端了。这思想有点依赖注入的感觉——你需要一个状态,所以我就在调用你的时候给你个状态。服务端根据签名算法验证一下客户端状态的真伪,就可以直接用了。
    ysc3839
        13
    ysc3839  
       2020-12-24 02:38:51 +08:00 via Android   ❤️ 1
    个人理解这里说的“无状态”是指服务器无 session 状态。不是 HTTP 那个无状态,也不代表服务器不使用数据库保存其他信息。
    原因就是楼上所说的不同服务器同步 session 成本高。理论上也可以把 session 存在数据库中,但是成本也是比存客户端高。
    yzbythesea
        14
    yzbythesea  
       2020-12-24 04:28:07 +08:00
    stateless 指的是你这个 API call 不需要知道任何 之前 API call 的内容和状态,是独立的。
    zjsxwc
        15
    zjsxwc  
       2020-12-24 08:24:15 +08:00 via Android   ❤️ 1
    服务端保存状态数据,客户端没有保存状态数据,就是 statefull,比如对于用户登录状态数据来说,我们常见的客户端请求使用 sessionId 模式。


    服务端尽可能少保存状态数据,客户端保存状态数据,就是 stateless,比如对于资源数据来说,我们常见的在 url 里包含资源 id,每次访问这个 url 就是同一个资源内容,在客户端里面保存用户访问资源历史,而不是在服务端里面保存用户访问资源历史。
    airfling
        16
    airfling  
       2020-12-24 08:28:44 +08:00   ❤️ 1
    所有的请求都带 bearr token,token 里面自己带了加密的用户信息和有效时间
    whileFalse
        17
    whileFalse  
       2020-12-24 08:37:26 +08:00   ❤️ 1
    我司的做法是,前端 Node 服务有 Session 状态,会用 Session 保存用户 ID,可能还有其他用户信息。
    后端 Java 服务就没有登录状态这说法了。Node 传过来哪个用户的行为后端就相信是这个用户。

    LZ 引用的论文是不是要把用户 ID 甚至用户权限都放在短效 JWT 里面呀。
    mitu9527
        18
    mitu9527  
    OP
       2020-12-24 09:09:08 +08:00
    @whileFalse 我个人反对把用户 ID 和用户权限放在 JWT 里面,因为这时候 JWT 本质上就是我说的客户端会话而已,不过好多人都说这么做可以,我质疑的就是这种用法。

    我觉得正确的用法应该大概是这样,客户端提交用户名和密码向服务端申请 JWT,JWT 的 payload 中只包含一个令牌和一个过期时间,后续请求都把 JWT 传到服务端,服务端验证通过 JWT 后就通过 payload 中的令牌找到通过认证的用户即可。至于一次交互内之前产生的临时数据或者说状态,应该存放在客户端的本地存储中,只要发起请求时,把服务端需要的参数从本地存储中拿出来放到请求中即可。
    mitu9527
        19
    mitu9527  
    OP
       2020-12-24 09:11:28 +08:00
    @airfling 嗯,这个肯定没错,不过有些人认为还可以把比如用户 id 、用户权限等会话数据放到 token 中,虽然很方便,但这时候 token 就变成客户端会话了,我觉得这种做法不合适。
    mitu9527
        20
    mitu9527  
    OP
       2020-12-24 09:16:08 +08:00
    @lihongming 你说的这种用法,如果只是登陆状态这一种状态,我觉得没问题,但是如果把 token 中塞满以前保存在服务端会话中的那些会话数据呢?就比如说用户权限数据,以前经常是保存在服务端会话中的,如果现在把它挪到客户端合适么?
    mitu9527
        21
    mitu9527  
    OP
       2020-12-24 09:26:38 +08:00
    @ysc3839 那就是说,你认为只要服务端无会话状态,比如把会话状态存在客户端,就算 REST 的“无状态”了,对么?

    我个人的想法是,这些会话数据由服务端产生,最终也是由服务端读取使用,本质上还是服务端在用,只不过被存放在客户端上了而已。

    如果客户端自己把交互过程中的状态存在自己的本地存储上,然后在向服务端发送请求时,自己从本地存储中选用服务端需要的状态,再和请求一起发给服务端,这才是真正的“无状态”。客户端自己存放,自己选取使用,服务端只做接收方。
    mitu9527
        22
    mitu9527  
    OP
       2020-12-24 09:29:45 +08:00
    @Bromine0x23 这两段实际上我都看了,只不过觉得意思是一样的,所以我就引用了一段。所以你的意思是把交互状态放到“客户端会话”中算是“无状态”了?
    wangritian
        23
    wangritian  
       2020-12-24 09:33:38 +08:00
    有时间不如研究会话安全
    mitu9527
        24
    mitu9527  
    OP
       2020-12-24 09:41:56 +08:00
    @fengchang 我只是觉得,客户端会话的状态是有服务端生成,最后也是由服务端读取使用,这种不能算“无状态”。

    我觉得客户端自主生成状态,然后放在自己的本地存储中,然后再根据下次调用的 api 需要什么数据,再从本地存储中取出来,这才是“无状态”,服务端根本就不知道“状态”为何物,只知道客户端会把我要的数据都发给我,我直接用就行了。

    服务端不知道“状态”为何物 != 服务端不在乎“状态”存在哪里。
    whileFalse
        25
    whileFalse  
       2020-12-24 09:43:14 +08:00
    @mitu9527
    > 我个人反对把用户 ID 和用户权限放在 JWT 里面,因为这时候 JWT 本质上就是我说的客户端会话而已
    > 我觉得正确的用法应该大概是这样,客户端提交用户名和密码向服务端申请 JWT,JWT 的 payload 中只包含一个令牌和一个过期时间,后续请求都把 JWT 传到服务端,服务端验证通过 JWT 后就通过 payload 中的令牌找到通过认证的用户即可

    没明白你这个令牌什么意思。我觉得他要不是包含用户 ID,要不就是个 session key 。如果它就是 session key,那为什么不直接用 session,何必弄 JWT 。

    你还是没理解 JWT 和 Session 的区别。Session 的好处是可以随时修改随时吊销。JWT 的好处是可以离线验证,因此微服务不需要总是和中央权限系统交互。
    PetterZhu
        26
    PetterZhu  
       2020-12-24 09:45:30 +08:00
    http 是短链接,tcp 没有一直保持连接,无状态应该是指这种的无状态吧, 怎么扯到 cookie,session 呢?
    mitu9527
        27
    mitu9527  
    OP
       2020-12-24 09:46:03 +08:00
    @wangritian 我现在认为如果要遵循 REST,会话数据就不应该由服务端生成,然后放在客户端,然后还绞尽脑汁想着怎么才能不泄露、篡改、伪造和重放。
    kop1989
        28
    kop1989  
       2020-12-24 09:48:39 +08:00
    @mitu9527 #24
    从我的理解来看。

    1 、“状态标识”是必然要存的。
    2 、“状态标识”不存在“被调用方”(绝大多数情况的服务器端),就一定存在调用方(绝大多数情况的客户端)。
    3 、服务器端其实是知晓,且需要处理“状态标识”的,否则你相同客户端调用两个 api,逻辑怎么贯通?

    综合上文 1 、2 、3 。服务器端不存“状态标志” == 无状态 == REST
    msg7086
        29
    msg7086  
       2020-12-24 09:54:12 +08:00
    想法反了。

    可以这么理解:
    恰恰是因为客户端保存了会话,所以才能做到无状态。
    如果客户端不保存会话,那么请求就无法做到无状态。

    比如说你要做一个在线商城,有购物车组件。
    如果购物车存在服务器端,那么当你有很多服务器的时候,CDN 派发到另一个服务器,那你购物车就空了,因为你的会话在另一台服务器上不可用。如果是客户端会话,那么换了一台服务器以后,也可以直接从客户端发来的会话里读取数据。

    当然,你也可以把购物车存在中央存储区里(例如数据库或者内存数据集群等),但是这样又会增加服务器间的通讯量,以及把更多的压力放在一个单一服务(数据库)上。

    无状态的话,客户会话是由应用程序服务端处理的,而应用程序服务器是可以大量横向扩展的,人多的服务你堆个一万台机器上去就结了。

    数据库你咋堆服务器。
    mitu9527
        30
    mitu9527  
    OP
       2020-12-24 09:55:20 +08:00
    @kop1989 问题就是,现在我认为在 REST 下,两次请求就不应该用会话这个概念来贯通,应该是是毫无关系才对。
    mitu9527
        31
    mitu9527  
    OP
       2020-12-24 10:00:49 +08:00
    @msg7086 你说的我基本上都明白,但我的意思是“状态”不应该由服务端生成(做决定),服务端每次只返回数据给客户端就可以了(服务端根本不在乎之前请求发生过什么),客户端接收到数据之后,要自己决定要把什么“状态”缓存在客户端本地缓存中,然后下次发起 api 调用时,自己决定该从客户端本地存储中读取哪些必须数据并和请求一起发给服务端。不是服务端决定该把什么“状态”缓存在客户端。
    kop1989
        32
    kop1989  
       2020-12-24 10:03:36 +08:00
    @mitu9527 #30

    毫无关系理论上可行,但性价比太低。

    毫无关系就需要客户端提供一切当前状态的业务信息,供服务器端来进行逻辑判断。
    这就极大的加剧了每个接口服务器端和客户端之间的信息吞吐量。

    而且也加剧了程序的开发复杂度和耦合度。

    举个例子,你考试写名字 or 身份证号就可以了。就是因为你(客户端)和学校(服务器端)之间,通过考试报名实现了预设信息沟通(登录)。

    否则你每参加一科考试,都需要填写巨量的个人身份信息。这样非常的不环保,也增加了你填错的可能性。更严重一点会造成你当场的考卷作废。
    msg7086
        33
    msg7086  
       2020-12-24 10:03:54 +08:00
    @mitu9527 会话数据不是缓存。
    甚至很多时候这个会话是对客户端加密的,只有服务器能读取其中的内容。
    对客户端来说,会话就只是一坨他看不懂的东西而已。
    mitu9527
        34
    mitu9527  
    OP
       2020-12-24 10:09:22 +08:00
    @msg7086 那就是了,完全是服务端在把控的东西(只不过存在客户端),还能说服务端“无状态”么?
    Vegetable
        35
    Vegetable  
       2020-12-24 10:13:01 +08:00
    你这是在说“我的定义和你不一样,所以你错了”

    你干脆说 stateless 并不应该翻译成无状态算了,因为了 less 是少,而不是无。
    wangritian
        36
    wangritian  
       2020-12-24 10:16:50 +08:00
    @mitu9527 抱歉对 REST 不是很了解,抛开定义不谈,业务接口不可能无状态对吧,你不能让用户干什么都提交账号密码。下一步就是客户端保存 sessionId 和完整加密数据之间的博弈了,所以我觉得无状态是怎么定义的不重要,讨论最佳安全策略是最重要的。如果一定要讨论,我认为是 REST 本身设计就是无状态的,每个请求各自独立,不像 tcp 或是 NAT 那样需要依赖前次请求保存的状态;所谓状态是作为 REST 的用户在业务层面加上去的,跟协议本身其实无关。
    SjwNo1
        37
    SjwNo1  
       2020-12-24 10:18:15 +08:00
    很多我们用 JWT 发挥了和 Session 一样的功效,JWT 不能随时吊销,两者都需要 db 支持,因此有人说在这方面没有真正意义上的无状态
    ysc3839
        38
    ysc3839  
       2020-12-24 10:26:11 +08:00 via Android
    @mitu9527 是的。按我的理解,REST 的这种无状态还是会允许使用数据库的,那数据库中的数据也可以认为是一种“状态”,这就跟“无状态”矛盾了。所以这里的无状态应该理解为没有 session 。
    suenlai
        39
    suenlai  
       2020-12-24 10:33:23 +08:00
    @GeruzoniAnsasu 我也是这么理解的.
    msg7086
        40
    msg7086  
       2020-12-24 10:34:08 +08:00 via Android
    @mitu9527 当然是。
    还是那句话,恰恰因为服务器把数据存在了客户端才让他成为了无状态服务器。

    就像素鸡,本质上是豆制品。你突然冒出一句,连鸡都没有,还能说这种食材叫素鸡吗!就比较尴尬了。人家就是这么定义的,你觉不觉得不影响别人定义。
    acmore
        41
    acmore  
       2020-12-24 10:51:46 +08:00
    孔子说过,只写 Code 不读论文会迷茫,只读论文不写 Code 是有害的。实践一下就能明白为什么会这样了。
    以及你先前以为的无状态是哪哪都不存状态,这从数学上就是不可能的,一定要有一个状态。只不过服务端的状态牵制了很多东西,所以放在客户端干净利索。
    no1xsyzy
        42
    no1xsyzy  
       2020-12-24 10:55:44 +08:00
    @mitu9527 就好像说 serverless 是不是真的没有服务器了?
    stateless 是为了服务水平扩展设计的概念。stateless 就是说你可以把服务器任意部署吊销,但只要有一个实例存在就不会导致服务发生故障。
    icyalala
        43
    icyalala  
       2020-12-24 11:02:52 +08:00
    人家说的 stateless-server 是有上下文含义的。
    你非要把 state 含义扩大化,然后把人家批判一番。。
    mitu9527
        44
    mitu9527  
    OP
       2020-12-24 11:02:55 +08:00
    @kop1989 我当然知道要有状态,我也知道放在服务端不对,应该放在客户端,但是我觉得这些状态不应该由服务端生成和读取,应该由客户端自己生成本次交互过程的状态并存在本地存储,然后客户端发起请求时,从本地存储中取出来直接告知服务端,而不是“我把交互状态都发给你了,你自己看着选用吧”。
    mitu9527
        45
    mitu9527  
    OP
       2020-12-24 11:05:31 +08:00
    @acmore 状态放在客户端没毛病,但是状态不应该“由服务端生成,然后放在客户端”,应该“由客户端生成,然后放在客户端”。
    mitu9527
        46
    mitu9527  
    OP
       2020-12-24 11:07:51 +08:00
    @icyalala 我没有否定,也不敢轻易否定,但是确实有质疑,质疑一下没毛病吧。
    libook
        47
    libook  
       2020-12-24 11:10:22 +08:00   ❤️ 5
    “In computing, a stateless protocol is a communications protocol in which no session information is retained by the receiver, usually a server.”——Wikipedia: Stateless protocol

    首先,“无状态”是个简称,实际指的是“Server 无状态”,即在抽象意义上,Server 在处理请求的时候不依赖 Server 当前的状态。
    举个依赖 Server 当前状态的例子就是传统的 Session 模式:用户的登录会话信息保存在 Server 上,Client 上仅保存 Session ID,Client 发送携带 Session ID 的请求到 Server,Server 读取本地的 Session 内容来确定用户当前的状态,再处理请求。
    而无状态就是 Server 在接收到请求之后不需要读取自己在用户之前的请求所留下来的 Session 数据,仅凭借 Client 请求中携带的信息就可以完成用户状态的判断。

    拿生活举例说明,比如一家理发店:
    有状态模式每位理发师会记住自己服务过的每位顾客的喜好,每来一位顾客,都要问其以前是哪位理发师为其服务的,然后看那位理发师是否能继续为这位顾客理发,如果那位理发师抽不开身,就可以把顾客的喜好情况讲给另一位空闲的理发师,让这位理发师来为顾客理发;
    无状态模式就是理发师不需要记住顾客的喜好,每位顾客来了就可以分配一位空闲的理发师,然后需要顾客把需求交代清楚,理发师按照需求进行此次理发服务。

    无状态好处有很多,主要的几点:
    1. Client 的请求对 Server 一视同仁,理论上此服务集群的任何一个 Server 随时都有能力正确处理 Client 的请求。
    2. Server 对 Client 的请求一视同仁,处理每个请求不需要参考同一用户前几次发了什么次请求,仅考虑当前请求包含了哪些信息。
    3. 服务集群不需要考虑是否要同步各个 Server 的状态,因为 Server 无状态可同步。
    4. 负载均衡不需要考虑引导请求到集群中特定的 Server,可以放心地按照实际负载分布来分发请求。
    进一步产生的好处为:
    1. 服务器程序设计简单,每个请求的处理过程是独立的。不会遇到请求与请求之间的逻辑关系而导致程序复杂。由此接口设计模式可以很简单、清晰。
    2. 服务集群性能是和机器数量等比相关的,性能不够加机器就行,不会因为加机器而产生额外的开销。

    当然,无状态设计也会有一些代价,比如每个请求都需要携带较多的状态数据,而 Server 可能也需要每次收到请求都执行一遍对请求中状态数据的解析。

    系统架构上的概念都是抽象意义上的,比如无状态概念里的“Server”指的不是我们一般说的物理意义上的服务器计算机,而是在系统架构上处理业务请求的那部分程序,这其中甚至都不包括底层数据库,但可能包括或不包括实际实施中使用的一些中间件。
    所以在尝试理解系统架构概念的时候,要先理解概念里的每个名词究竟代表的是什么,再看实际实施过程中,这个名词指的是整个系统的哪些部分。
    acmore
        48
    acmore  
       2020-12-24 11:13:42 +08:00
    @mitu9527 考虑这样的场景,如果状态里保存的有这个账户的权限信息,且这信息是要用非对称算法加密的。那么如果客户端生成状态就意味着权限的控制和私钥都要交给客户端,这样是不可以的。最终的控制还是要由服务端完成,客户端只是走个过场。
    mitu9527
        49
    mitu9527  
    OP
       2020-12-24 11:17:08 +08:00
    @libook 我和你的观点整体上是类似的。
    libook
        50
    libook  
       2020-12-24 11:37:24 +08:00   ❤️ 1
    @mitu9527 在主题内容中,你其实太纠结于“无状态”本身的含义了,但 REST 的作者可不是这么想的,你看论文的前几个章节,他只是在以往的 API 设计方法中遇到了一些痛点,然后希望借助“无状态”的思路来解决这些痛点而已,实现纯粹的“无状态”本身并不是 REST 所讨论的核心问题。
    就像我们现在在程序上所说的“异步”,实际上是为“同步”需求服务的,我希望多个业务逻辑能同步执行,所以需要技术栈具备异步处理的能力,即在一个业务正在处理的过程中可以同时响应另一个业务(有什么活就做什么活,不等着)。在生活中,“我一边打电话一边敲代码”这种在语言表达上不能被称为“异步”(虽然职业病会首先想到“异步”),而是我在同步做两件事情(我有异步做事的能力,可以随时在想下一行代码和下一句回答之间切换)。
    lihongming
        51
    lihongming  
       2020-12-24 11:43:55 +08:00 via iPhone   ❤️ 1
    @mitu9527 客户端应该保存哪些数据并没有一个标准,甚至都没有最佳实践,看你自己的需要和取舍。

    就 AWS 的身份验证服务( Cognito )所使用的 JWT 来看,它并没有把权限放到客户端,但放了用户的角色和用户组,你可以参考一下。
    mitu9527
        52
    mitu9527  
    OP
       2020-12-24 12:25:39 +08:00
    @lihongming 你说到 AWS 的情况,我猜测可能是 AWS 引入了 RBAC 模型,并且角色与会话绑定了,才会引发不得不这么做。

    比如,我有一个账户,即属于运营人员角色,又属于某种管理人员角色,但运行时角色只能二选一,即角色运行时互斥,那就要这么做了。此时,如果我有两个浏览器,都用这个账号登陆,一个浏览器登陆后选择运营人员,一个浏览器登陆后选择管理人员,那用这种把 JWT 当作客户端会话的方法就很合适。

    但是反过来,不在 JWT 中放入角色,也可以轻松实现同样的目的。比如服务端通过一个接口返回可选的角色,客户端展现,然后用户选择,客户端再把用户的选择(即所谓的“状态”)保存在客户端本地存储中,后面每次请求带上该角色(状态)即可。

    这两种方法,都把状态存在了客户端而不是服务端,这是是没问题的。但区别是一个是服务端发起的保存和读取操作,另外一个是客户端自己发起保存和读取操作。很类似,但我个人确实觉得服务端发起和保存和读取操作,比较别扭,可能是我思维有点固化,没转变过来吧。
    Bromine0x23
        53
    Bromine0x23  
       2020-12-24 12:48:26 +08:00
    @mitu9527 我的意思是作者在论文中所说的 Stateless 指的是仅服务端无状态的 client-stateless-server (CSS) 风格,而不是从字面理解的服务端和客户端都无状态。要从实际定义理解而不是望文生义
    zliea
        54
    zliea  
       2020-12-24 12:50:49 +08:00
    核心概念#47 说的很明白

    用户 /认证信息、组织、角色在 REST 里都是资源。
    但在实际情况中,而且这些在业务系统中并不完全适合放在 url 里进行传输,因此放在 header(cookie)里。
    zliea
        55
    zliea  
       2020-12-24 12:58:39 +08:00
    @ztechstack 而且在实际架构中,甚至可以将认证的步骤提前,放在网关等外部层去处理。
    tomoya92
        56
    tomoya92  
       2020-12-24 13:59:37 +08:00
    @Jooooooooo #1 面试官喜欢问
    hantsy
        57
    hantsy  
       2020-12-24 14:20:53 +08:00
    对于用户这一块,很多 IDP 算是有状态的,因为它有管理在线用户的功能,提供了 /logout 去删除 token 之类的。
    charlie21
        58
    charlie21  
       2020-12-24 14:34:59 +08:00
    呃你看见真实情况觉得真实情况把理论给否定了,可理论根本不是你理解的那样 。。
    dzdh
        59
    dzdh  
       2020-12-24 14:37:04 +08:00
    你把无状态理解成不间断的状态保持
    lihongming
        60
    lihongming  
       2020-12-24 14:37:51 +08:00 via iPhone   ❤️ 1
    @mitu9527 嗯,你对 AWS 的分析正好证明了我说的话——没有标准,看你的需要和取舍

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

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

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

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

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

    在用户做选择的时候一个请求发给服务端,告诉服务端客户选了哪个角色当作自己的全局默认角色不就可以了,服务端把这种角色保存起来就可以了,而且这种数据也不应该保存在服务端会话中,这应该保存在数据库中吧。
    wzwb
        70
    wzwb  
       2020-12-24 16:28:42 +08:00
    @mitu9527 我看了你说的后一种情况,我感觉你是没理解这里说的无状态是什么意思。你后面讲的这种情况就是 OpaqueToken 或者 sessionId 的用法,这才是这个上下文里面说的有状态的情况。如果别人发一个用户标识来,你为了验证参数是否合法,不可避免的要和数据库、缓存或者认证服务器交互查询出来用户信息,而 jwt 就是为了减少这部分的查询设计的,可以在服务端离线验证减少不必要的查询。
    eason1874
        71
    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
        72
    mitu9527  
    OP
       2020-12-24 16:38:42 +08:00
    @wzwb 大家公认的 Authentication: Basic username:password 这种是 RESTful 的,这种没去根据用户名和密码查询么?为了不在每次请求中都传输账户和密码,所以发明了令牌代替用户名和密码来做认证,所以有啥问题?

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

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

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

    算了算了,没啥好讨论的了。
    wzwb
        76
    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
        77
    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
        78
    mitu9527  
    OP
       2020-12-24 17:12:23 +08:00
    @wzwb 这是客户端发送的请求头部啊,这有服务端什么事么?你真的知道这个是什么么?
    mitu9527
        79
    mitu9527  
    OP
       2020-12-24 17:15:11 +08:00
    @wzwb 服务端存的是资源和资源的状态,你却在纠结服务端数据库里面有保存数据,就是中心化,就是有状态。哪个人敢站出来说无状态就是不能在数据库里面存数据?你这是开始扯皮了???
    ZSeptember
        80
    ZSeptember  
       2020-12-24 17:31:43 +08:00
    不说其他,就说 JWT 无状态,就是说服务器不需要从其他任何中心化的存储中去取数据就能校验 token 的身份。。只要 token 就能校验,所以才有我说的问题。
    wzwb
        81
    wzwb  
       2020-12-24 17:32:11 +08:00
    @mitu9527 算了算了,不说了。如果你能明白 Basic username:password = OpaqueToken = sessionId = 等于你说的 jwt 里存用户 id,都是一种用户标识,都需要再次查询才能获取用户信息,你就知道你这种方案只是重新发明了一遍服务端会话,本质上是一种东西。
    mitu9527
        82
    mitu9527  
    OP
       2020-12-24 17:42:45 +08:00
    @ZSeptember 今天回答的问题太多,算了,就这样吧,没必要再讨论了,恐怕也讨论不出给所以然来。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2620 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 43ms · UTC 15:52 · PVG 23:52 · LAX 08:52 · JFK 11:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.