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

session? cookie?登录?还在分不清楚?

  •  
  •   zzzzzzggggggg · 2020-02-28 22:12:37 +08:00 · 3326 次点击
    这是一个创建于 1729 天前的主题,其中的信息可能已经有所发展或是发生改变。

    之前的一篇文章,发出来跟大家交流一下,有误导的部分还请评论在下面,大家互相进步

    还记得在上家公司做全干工程师的时候,基本从页面写到运维,当时做登录这块的时候,被 session、cookie、token 各种概念差点整蒙圈了,上网查询相关概念,发现很多人都是类似的疑惑,比如:

    来了新公司之后,前端很少接触 HTTP 请求之后的事情,而且登录相关的 SDK 封装的很好,所以这篇文章就简单的学习记录一下。

    为什么会有登录这回事

    首先这是因为HTTP是无状态的协议,所谓无状态就是在两次请求之间服务器并不会保存任何的数据,相当于你和一个人说一句话之后他就把你忘掉了。所以,登录就是用某种方法让服务器在多次请求之间能够识别出你,而不是每次发请求都得带上用户名密码这样的识别身份的信息。 从登录成功到登出的这个过程,服务器一直维护了一个可以识别出用户信息的数据结构,广义上来说,这个过程就叫做 session,也就是保持了一个会话。

    常见的两种登录

    忽然想到一点,看了网上很多问题,我觉得大家应该区分两个概念:广义的 session狭义的 session

    **广义的 session:**广义的 session 就是从登录成功到登出的过程,在这个过程中客户端和服务器端维持了保持登录的状态,至于具体怎么维持住这种登录的状态,没有要求。

    **狭义的 session:**狭义的 session 就是登录成功后,服务器端存储了一些必须的用户信息,这部分存在服务器端的用户信息就叫做 session,也就是接下来要说的第一种登录的实现方式。

    服务器 session+客户端 sessionId

    先用图来看:

    详细说的说一下,这里面主要是这么几个过程:

    1. 客户端带着用户名和密码去访问 /login 接口,服务器端收到后校验用户名和密码,校验正确就会在服务器端存储一个 sessionId 和 session 的映射关系

    2. 服务器端返回 response,并且将 sessionId 以 set-cookie 的方式种在客户端,这样一来,sessionId 就存在了客户端。这里要注意的是,将 sessionId 存在 cookie 并不是一种强制的方案,而是大家一般都这么做,而且发请求的时候符合 domain 和 path 的时候,会自动带上 cookie,省去了手动塞的过程。

    3. 客户端发起非登录请求时,服务端通过 cookie 中的 sessionId 找到对应的 session 来知道此次请求是谁发出的。

    token

    前面说到 sessionId 的方式本质是把用户状态信息维护在 server 端,token 的方式就是把用户的状态信息加密成一串 token 传给前端,然后每次发请求时把 token 带上,传回给服务器端;服务器端收到请求之后,解析 token 并且验证相关信息;

    所以跟第一种登录方式最本质的区别是:通过解析 token 的计算时间换取了 session 的存储空间

    业界通用的加密方式是 jwt (json web token),jwt 的具体格式如图:

    简单的介绍一下 jwt,它主要由 3 部分组成:

    header 头部 { "alg": "HS256", "typ": "JWT" } payload 负载 { "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "exp": 1555341649998 } signature 签名

    header 里面描述加密算法和 token 的类型,类型一般都是 JWT ;

    payload 里面放的是用户的信息,也就是第一种登录方式中需要维护在服务器端 session 中的信息;

    signature 是对前两部分的签名,也可以理解为加密;实现需要一个密钥( secret ),这个 secret 只有服务器才知道,然后使用 header 里面的算法按照如下方法来加密:

    HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

    总之,最后的 jwt = base64url(header) + "." + base64url(payload) + "." + signature

    jwt 可以放在 response 中返回,也可以放在 cookie 中返回,这都是具体的返回方式,并不重要。

    客户端发起请求时,官方推荐放在 HTTP header 中:

    Authorization: Bearer <token></token>

    这样子确实也可以解决 cookie 跨域的问题,不过具体放在哪儿还是根据业务场景来定,并没有一定之规。

    两种登录方案存在的问题

    session 方式

    1. session 方式由于会在服务器端维护 session 信息,单机还好说,如果是多机的话,服务器之间需要同步 session 信息,服务横向扩展不方便。

    2. session 数量随着登录用户的增多而增多,存储会增加很多。

    3. session+cookie 里面存 sessionId 的方式可能会有 csrf 攻击的问题,常见的方式是使用csrf_token来解决

    jwt 方式

    1. jwt 的过期时间需要结合业务做设置,而且 jwt 一旦派发出去,后端无法强行使其作废

    后话

    理清概念,一身轻松

    --------------------------------------------

    鉴于有人说我太明显,我就不放二维码了 欢迎关注我的公众号:fe-yagushou

    16 条回复    2020-03-01 21:34:32 +08:00
    zzzzzzggggggg
        1
    zzzzzzggggggg  
    OP
       2020-02-28 22:13:23 +08:00
    欢迎交流和斧正
    gwy15
        2
    gwy15  
       2020-02-28 22:18:46 +08:00
    JWT 当然是可以作废的,校验的时候对单个用户要求全部 iat 大于阈值就可以 O(1) 作废用户全部当前 JWT。如果要作废单个 JWT 需要维护黑名单,利用 redis 维护带过期时间的哈希表也可以 O(1) 但是要 O(n) 储存空间。
    freakxx
        3
    freakxx  
       2020-02-28 22:22:53 +08:00
    这个问题有类似的:
    https://v2ex.com/t/641834
    zzzzzzggggggg
        4
    zzzzzzggggggg  
    OP
       2020-02-28 22:29:38 +08:00
    @gwy15 嗯嗯是的可以做到;不过我感觉要求 iat 大于阈值这个操作,如果后台强制某用户此时此刻就下线的话,这个阈值就得改一下,如果是 session-cookie 的形式的话,把它 session 清掉就行了;作废单个 jwt 确实可以使用黑名单的方式,不过就是需要点存储
    zzzzzzggggggg
        5
    zzzzzzggggggg  
    OP
       2020-02-28 22:32:09 +08:00
    @freakxx 嗯嗯,确实比较类似,不过他是在提问
    encro
        6
    encro  
       2020-02-29 17:11:01 +08:00
    一,session 多服务器共享?
    1,可以采用阿里云 lbs 或者 nginx 保持会话;
    2,也可以客户端加密 cookie 存 session id,然后服务端 session 存在 redis ;
    3,采用单点登录系统+自动跳转等方案来实现;

    二,JWT 不能强制作废?
    1, jwt 没有做服务端验证码?既然做了,肯定可以作废啊。


    他们主要不同,看名字就知道了,
    session 意思是会话,会话是通过 cookie 来保持的,而 cookie 收到 domain 限制。
    JWT 名字里面重点在于 token,是一种认证方式,通过传递 token 保持认证有效性。

    他们的原理导致实用场景不一样。
    一般来说,做 web 前后端不分离,使用 session 方便,
    如果前后端分离,比如前后端分离的 web、app、第三方程序调用那么采用 jwt 方案会更合适。
    poorcai
        7
    poorcai  
       2020-02-29 17:36:04 +08:00
    图片挂了
    zzzzzzggggggg
        8
    zzzzzzggggggg  
    OP
       2020-02-29 21:13:48 +08:00
    @encro 关于 jwt 作废这个我有点异议,和你讨论一下,因为 jwt 里面的信息都是在生成的时候编码好的,如果要主动作废的话,据我了解可以用配黑名单的方式;个人觉得 session 和 jwt 的使用跟是否前后端分离关系不到,不过确实是 APP、第三方调用的时候用 jwt 会更合适
    zzzzzzggggggg
        9
    zzzzzzggggggg  
    OP
       2020-02-29 21:14:03 +08:00
    @poorcai 我也发现了。。不知道咋搞
    JamesMackerel
        10
    JamesMackerel  
       2020-03-01 09:49:19 +08:00 via iPhone
    @encro 很多人对单点登录自动跳转有误解,认为它可以管理接入服务的 session。实际上单点登录并不管理甚至不关心你的 session。单点登录的职责是认证用户,并且把用户的身份提供给接入方,用户的登录态还是要服务自己管理。
    encro
        11
    encro  
       2020-03-01 09:51:09 +08:00
    @zzzzzzggggggg

    jwt 不用作废,只是 jwt 之后,肯定还对了一个用户吧,其实和 session 一样的,你不是要去删除这个 session_id 或者这个 token,而是收到 session 和 token 之后,服务端必须验证他们有效性,这里加一个判断他们是否还真实有效,比如用户状态已经改为删除,这时候就需要返回 token 无效,不需要再做什么黑名单。


    前面分析了,jwt 和 session 其实没有区别(加密后的 Session id 就等于 token ),只是 session id 依赖 cookie,所以必须基于浏览器或者模拟 cookiejar,cookie 有 domain 限制,需要在一个 domain 下才能使用,且历史原因,大量 web 程序都为 session 提供了很多解决方案,比如脚本语言都提供了全局变量,session 钩子类或方法,所以做不跨域的 web 时候,可能使用 session 更加方便。而 jwt 无需依赖 cookie,所以没有域名要求,所以对于前后端分离的处于两个域的,客户端不是 web 的,用 jwt 更加方便。
    JamesMackerel
        12
    JamesMackerel  
       2020-03-01 09:55:18 +08:00 via iPhone   ❤️ 1
    另外,楼主的文章里的说法:用 jwt 可以防止 csrf。csrf 本质上是用户不在你的网站,却能向你的网站发送请求,并且这个请求中还能带上他的凭据而导致的问题。所以要解决这个问题,要么你验证请求是不是你自己的网站上发来的(验证 referer ),要么你想个办法不要让浏览器自动把用户凭据带到请求里。

    把 jwt 放在心里 authorization 里的操作一般写在 js 里,而不是利用浏览器自动携带 cookie 的行为,所以天然避免了 csrf。但是如果有人把 jwt 扔到 cookie 里,而且还没做 csrf token,那么其实还是会有 csrf 的问题。
    JamesMackerel
        13
    JamesMackerel  
       2020-03-01 09:57:09 +08:00 via iPhone
    @JamesMackerel 放在心里-> 放在
    encro
        14
    encro  
       2020-03-01 09:58:46 +08:00
    很多成熟的框架,为 session 和 jwt 提供了中间件,
    你加载哪个就支持那种方式,
    你加载两个就同时支持两种方式。

    我所知道的有 Django REST framework, Yii, gin 等
    encro
        15
    encro  
       2020-03-01 10:37:13 +08:00
    @JamesMackerel
    是的,单点登录只是完成了登录,对于多个子系统实现了统一账户认证,这里面明线只传递一个认证身份钥匙,往往还需要一条服务器对服务器的暗线来传递用户数据。

    @zzzzzzggggggg
    某人跟你借东西,
    你给了家里钥匙让他自己去拿,这时候钥匙就是 key,所有人拿了钥匙都能去你家拿东西;
    如果你装了人脸识别电子锁,你给他远程开通一个有时间限制的人像识别,这个时候就是 jwt ;
    虽然有了人脸识别电子锁,但是不能防止他拿走其他东西,也不能防止他已经被挟持(抓包)了;
    你不让他去拿,你自己拿给他,这时需要鉴权或者 RPC。
    zzzzzzggggggg
        16
    zzzzzzggggggg  
    OP
       2020-03-01 21:34:32 +08:00
    @JamesMackerel 嗯嗯,老铁说的对; csrf 关键还是解决浏览器自动带用户凭证的问题
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2742 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 05:43 · PVG 13:43 · LAX 21:43 · JFK 00:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.