首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python 学习手册
Python Cookbook
Python 基础教程
Python Sites
PyPI - Python Package Index
http://www.simple-is-better.com/
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
V2EX  ›  Python

JWT 服务端怎么理解不需要存储 session

  •  
  •   Lateautumn · 64 天前 · 3685 次点击
    这是一个创建于 64 天前的主题,其中的信息可能已经有所发展或是发生改变。

    接触了一下 jwt,很多说法说服务端是无状态的,我这里有个疑问,当客户端是发送 token 过来 ,服务端怎么验证这个 token 的真实性,以及是哪个用户,如果把用户信息放在 payload 里面的话可以,那怎么去验证这个呢,不是还得去数据库查吗,如果不验证怎么知道是哪个用户?

    64 回复  |  直到 2019-02-23 20:13:56 +08:00
        1
    rayingecho   64 天前
    楼主没看仔细啊, JWT 除了前两段 header, payload 之外, 还有第三段: 签名
    服务端签发 JWT 时, 会用自己的密钥(对称 /非对称皆可)生成一个签名, 收到 token 时只需要验签通过即认为合法, 而验签是一个无状态的过程.
        2
    Sparetire   64 天前 via Android   ♥ 9
    你去游泳,他给你一个保险柜钥匙,你下次拿钥匙开保险柜,只认钥匙不认人
        3
    Lateautumn   64 天前
    @rayingecho 了解了,刚才又多找了写资料,我可以这么理解,就是使用签名来作为验证,具体用户可以根据 payload 里面的信息来找相关的用户
        4
    Lateautumn   64 天前
    @Sparetire 了解老哥,很形象
        5
    agagega   64 天前 via iPhone
    这样的优点也会带来一个缺点:签发出去的 Token 没法撤销
        6
    Esioner   64 天前 via iPhone
    @agagega 可以设置过期啊
        7
    runningman   64 天前 via iPhone
    @agagega 所以自己还得加一层 redis 存起来
        8
    xyz132   64 天前
    你可以把过期时间戳直接放在 JWT 里面
        9
    fleam   64 天前 via Android
    密匙比对
        10
    glaucus   64 天前
    借此想问一下各位老哥,可以再 payload 里放置过期时间来判断 Token 是否失效,但如果我要主动让用户 Token 失效,比如用户修改了密码需要重新登录,这种情况怎做呢?
        11
    90d0n   64 天前
    @glaucus #10 token 存 redis 里
        12
    lsongiu   64 天前
    其实我觉得如果 token 存 redis 里,为什么不直接用 uuid 呢
        13
    wizardoz   64 天前
    JWT 有签名机制,也就是说你把认证服务器的公钥配置在业务服务器,然后检验 JWT 是否为认证服务器签发的。如果确认了 JWT 确实为认证服务器签发,那么 jwt 的 payload 中携带的用户名、用户权限等信息都是可信任的。
        14
    chaleaochexist   64 天前
    @glaucus 增加黑名单.在做一次对比. 黑名单可以放到数据库里也可以放 redis.
        15
    lizhenda   64 天前
    业内普遍做法是放在 redis 里面,既可以实现单点登录又可以做设置密码重新生成
        16
    groot   64 天前
    @glaucus 我是给每个用户单独的 secret key,修改密码重置 secret key,token 也就失效了
        17
    rayingecho   64 天前 via iPad   ♥ 1
    @lsongiu
    jwt 对 redis 是弱依赖,redis 挂掉只是被踢出的 token 可以正常使用,但不会影响正常的 token。
    sessiom 共享对 redis 是强依赖,redis 挂了用户就无法鉴权
        18
    tabris17   64 天前
    当你用了 JWT 会发现有各种问题,然后还必须使用服务器端来维护 token,最后就变成和普通的 session 一样了
        19
    tabris17   64 天前
    @rayingecho 『被踢出的 token 可以正常使用』就已经是一个很大的安全隐患了,在强安全系统里是完全不可接受的,所以 token 必须在服务器端 redis 里验证一遍,这又跟 session 完全没区别了
        20
    libook   64 天前   ♥ 1
    JTW 实现无状态的思路是把用户会话完全交给客户端来记录,所以需要服务端修改用户的会话状态的话,JWT 这种机制就不适用。

    不过拿 JWT 来当 Cookie/Session 来用也未必不好,比如能比较方便得解决 Cookie 在各应用平台上实现的难题。

    没有银弹,每种设计思路都有适合或不适合的场景,如果想最大程度享受无状态带来的好处,又希望有改密码踢出登录这种功能的话,可以把用户密码的 Hash 也存在 JWT 的 Payload 里,然后如果有人修改了密码就把这个用户 ID 和密码 Hash 放到各个服务的黑名单里,比如 Token 失效时限是 7 天,那么这个黑名单里的项目也可以在加入 7 天后自动移除,这样在预期极少数人修改密码的情况下可以做到尽可能无状态。这个方案稍微复杂一点的就是如何在弹性伸缩的服务集群中有效广播黑名单。

    都是要看实际情况来设计的。
        21
    rayingecho   64 天前 via iPad
    @tabris17 你说得没错,但有两点可供权衡:
    1: jwt 的 payload 大部分不需要存储在 redis 里,因为可以用签名来验证,真正需要的只有一个 uuid ;而 session 共享要全都存储。存储成本小
    2: jwt 只需要判断一 exist,session 共享需要 get。带宽压力小
    当然这里不是说 session 共享不行,jwt 也有很多问题,比如更新 payload 字段需要重新签发,浏览器不会自动发送 Authorization header
    另外,我认为不存在绝对的不可接受(也不存在完美的架构),存在的只有针对自身场景的合理权衡,有时候需要两害相权取其轻
        22
    tabris17   64 天前
    @rayingecho 恩,JWT 适用的场景只有一次性鉴权,不是身份认证,而是操作的令牌
        23
    yamedie   64 天前
    前端进来插句嘴, 如果按照#2 所说的那样, 只认钥匙(token)不认人(userId), 应该如何解决水平权限问题? 即: 正常登录有合法 token 的用户, 如何防止他调用接口查阅其他用户的隐私数据? token 要和 userId 进行关联是吗, 那这样还能认为是无状态吗?
        24
    d5   64 天前 via iPhone
    @yamedie #23 比如可以在 jwt token 中新增 path 字段,规定该 token 仅仅能够访问的白黑名单
        25
    loading   64 天前
    其实就是只认 token 那个字符串,如何关联这个 token 就看你了。
        26
    rayingecho   64 天前 via iPad
    @tabris17
    “只有一次性”有点绝对了,jwt 只是一个简单的标准,不是一个完整的方案,只要能正确理解并应用,放到 app 端身份认证或者 oauth 授权(可持续使用)体系里都可以工作的很好。其实我想讲的还是根据具体场景去权衡,尽量避免 pre-judge
        27
    yamedie   64 天前
    @d5 不, 可能我没描述清楚. 比如 userId=1 的用户正常登录得到了 token=11111, 他调用"我的账户"接口, 但篡改了 post 请求里的 userId->2, token 合法, 接口合法, 如何防止他看到 2 号用户的个人信息? 如果有无数个类似的接口, 如何鉴别 token 和传入的 userId 是否对应同一个用户?
        28
    yamedie   64 天前
    @yamedie 如何"优雅地"鉴别 token 和传入的 userId 是否对应同一个用户?
        29
    yamedie   64 天前
    @yamedie 也许不传入 userId 是最优雅的....
        30
    fakeshadow   64 天前   ♥ 1
    @yamedie jwt 的 payload 里放入 userId,用户的 post 进来再从 payload 里读。
        31
    buhi   64 天前
    为什么我修改了密码就要重新登录? 不是很奇怪吗... 老子前几秒才输入了一遍密码 又让我输入一遍
        32
    rayingecho   64 天前 via iPad
    @yamedie
    其实 #2 说的是一个宽泛的 token 比喻,所有的 token 都可以为获得某种权限的钥匙

    但太泛化就会失去细节,jwt 更适合比喻为一纸身份证明,我在上面写上你是叫张三,你允许使用健身房和游泳池(这些叫 payload ),然后啪敲一个大公章(签名)盖住这些字

    到时候无论谁拿着这张证明( jwt )过来,我都认为他是张三并且可使用健身房游泳池。假如这个人心眼坏,把张三加一竖改成了“张王”,那这一竖就会涂在公章上破坏了签名,这纸证明就无效了
        33
    123s   64 天前
    只是带个 token 而已,里面会带个 id,访问的时候会查有没有过期
        34
    yamedie   64 天前
    @rayingecho 我明白了, 我司的后端用了 jwt token, 同时在定义"我的订单", "我的账户"这类接口时, 还要求前端传入 userId, 并未在 token 里关联 userId, 造成水平权限漏洞
        35
    fundebug   64 天前
        36
    est   64 天前
    jwt 跟 cookie 里放一个巨大的 base64 字符串是一样的作用

    无非就多了个签名姿势而已。。。
        37
    honeycomb   64 天前 via Android
    @glaucus redis 里存一份和 token 一一对应的信息。
    token 里的非标准字段可以存放一些能帮助你达到目的的额外信息?
        38
    smallthing   64 天前
    其实就是个高级 cookie 明白了?
        39
    passerbytiny   64 天前   ♥ 1
    首要要了解令牌、认证、授权,然后再看下文。注意,在中文中认证、授权有时候是名词,有时候是动词,需根据上下文判断,它们在英文术语中动词和名词是不同的单词。

    先说结论,JWT 不能替代 Session,只能替代 Session 的部分功能,即认证。

    JWT 是作为令牌设计的。如果只是入门使用,那么设计的是低级令牌,有这些特点:一、需要预先申请;二、不可伪造,但可复制;三、令牌是唯一认证手段,见牌如见人;四、有有效期,但不可收回;五、可以负载其它信息,包括授权。低级令牌有这些漏洞:可以被复制;可以被冒用;不可回收。另外还有一个大漏洞,直接在令牌上放授权,不过一般没人这么干,所以不讨论这个漏洞了。如果你回用 JWT,那么这些漏洞都可以解决。

    关于可以被复制的漏洞,最简单的解决方案就是负载中包含客户端 IP,认证时增加额外认证“令牌中 IP=客户端 IP ”。大部分情况下客户端 IP 是不可复制的,如果需要更高安全度,那么就增加更多的不可复制信息,比如 APP 上的设备识别号。作为对比,Session 方式同样有该漏洞,而且复制更简单,只要复制 Header 就够了。而 Session 方式是不能独自解决该漏洞的。

    关于可以被冒用的漏洞,这个漏洞单靠 JWT 或 Session 都是无法解决的,只能通过二次验证、行为分析等其它方式去解决。

    关于不可回收的漏洞,有两种解决方式,一种是 JWT 认证+辅助认证方式,一种是纯 JWT 认证方式,前者更有效和更简单但有性能损耗。JWT 认证+辅助认证方式是这样,使用 Redis 或数据库维护一份以发出的令牌为键,以用户 ID 为索引,以有效性为值的表,在认证令牌的同时,认证令牌的有效性。如果不考虑性能的话,还可以更简单,辅助认证方式改成直接去数据库查询该令牌代表的用户是否还有效。纯 JWT 认证方式,就是令牌的有效期设置的特别短,但每次认证后都向客户端发放新的令牌。作为对比,第二种认证方式等同于 Session 方式,第一种认证方式优于第二种方式和 Session 方式。

    最后,如果深入研究 Session,你就会发现,在认证阶段,Session 本质上也是令牌。但是 Session 不只具有认证的功能,它还有会话存储的功能,这点 JWT 是没有的,所以可以结合使用 JWT 和 Session。比如用 JWT 做认证,用 Session 保存授权,当然还是建议用 JWT 做认证,用 Redis 等缓存来保存授权。
        40
    jadeity   64 天前
    @buhi 当你密码有泄漏风险的时候就不这么想了。
        41
    yim7   64 天前
    现在用 jwt 存会话只是跟风
        42
    Kaiv2   64 天前
    安全性要求不高的场景,使用 JWT 确实方便。
        43
    abcbuzhiming   64 天前
    jwt 这东西无非就是把状态存储在客户端而已,如果你要灵活的保持随时踢出客户端的能力,JWT 就不合适,而且 JWT 的有效时间不能设太长,否则不安全,JWT 一旦签发就不能延期,也是一个问题
        44
    skypyb   64 天前
    https://github.com/skypyb/JwtBuilder-CryptographicComponent
    我写的 JWT 建造工具,了解一下?:手动狗头
        45
    br00k   64 天前
    @abcbuzhiming 延期可以使用 refresh_token。
        46
    blue0125   64 天前
    @rayingecho #32 那请问下,session 的 更新机制,一般会在 jwt 实现么?每次接口返回新的 jwt,更新过期时间?
        47
    GTim   64 天前
    @passerbytiny 已经很全面了
        48
    gz911122   64 天前
    @skypyb 对比 jjwt 的优点是啥?
        49
    lolizeppelin   64 天前   ♥ 1
    本质上是认为在不知道服务端加密 key 的情况下你无法伪造出合法 token,所以服务端只用对 token 和服务端的加密 key 进行运算就能判断 token 所带信息的真伪

    和 session 对比最大优点是 token 的校验不需要读取存储设备,也不需要重新鉴权,高并发下优势很大
    缺点是不能提前过期 token 以及在 token 过期时间内改变 token 的内容.

    建议看下 fernet token 的的做法, 理解下 fernet token 为什么要轮询换加密
        50
    rayingecho   64 天前
    @blue0125
    假如是可信的 client, 可以随着 acess token (jwt) 一起下发一个 refresh token
    假如是不可信的 client, jwt 过期后(假如 payload 里设置了过期时间的话) 由 client 自己来做重新登录
        51
    rayingecho   64 天前
    @passerbytiny
    请教一下, 在令牌里放授权会有什么问题? 之前某个系统的设计中我选择直接将 authority 存储到 jwt payload 增大网关层吞吐量
        52
    lolizeppelin   64 天前
    @rayingecho
    就是无法撤销无法更新呗,表面上你可以通知客户端换 token,但这只对正常处理的客户端有效啊,别人非要保留之前的 token 继续拿来用,服务端也认为是合法的

    比较好的做法是不重要授权信息直接存 token 的 playload,重要的还是得存在服务端每次校验

    或者 xx 次操作后重新读取授权信息
        53
    skypyb   64 天前
    @gz911122 没有优势,只是能用 ( 硬是要说,就是小吧...
        54
    passerbytiny   64 天前
    @rayingecho #47 JWT 是弱加密,容易被人反向工程。当然你这个方式是为性能优化的,没啥问题。
        55
    rayingecho   64 天前
    @lolizeppelin
    是的, jwt 本身无法撤销无法更新. 我在实践中采取的办法是增加一个校验层(这里会引入状态, 不过权衡在 #21 说过了).
    简化后的大体逻辑是每个 jwt 的 payload 里加上一个 uuid, 通过 redis 做黑名单, 假如某个 uuid 在 redis 里存在, 表明这个 jwt 已经被撤销了
    重新授权, 修改密码, 多端登录等场景都会触发撤销
        56
    rayingecho   64 天前
    @passerbytiny
    理解了.
    不过我之前的认知是对不可信 client 发放的 jwt 只要用 RSA256 并且直接拒绝在 header 中声明其他所有加密算法的 token 应该是足够安全的, 这里面有什么门道吗?
        57
    passerbytiny   64 天前
    @rayingecho #52 我又特意去看了一下,JWT 的 Header 和 Payload 是 Base64Url 编码,即明文,你要是放授权那就相当于把授权配置明文公布出去了。如果别人猜出来了签名密钥,原来只能伪造认证,现在还能伪造授权。

    到这里我要纠正我之前的结论了,因为 Payload 是明文的,所以单靠 JWT 自身,是不能完美解决可以被复制的漏洞的。
        58
    rayingecho   64 天前
    @passerbytiny
    是的, 反向出密钥就 GG 了, 不过非对称加密无法(在合理时间内)破解是 https 和区块链的基石啊...
        59
    lolizeppelin   64 天前
    @rayingecho

    token 就是要避免 io,你这个做法只是 io 的数据量少了,但是 io 还存在,而且全压在 redis 上

    如果你授权数据量少请求次数频繁的话, 授权内容放 token 还是放 redis 里 io 机几乎不多(都开销再网络上了)
        60
    lolizeppelin   64 天前
    @passerbytiny

    Payload 加密不加密都可以的, 取服务端 token 密钥或密钥前 xx 位用来加密 Payload 就完事

    服务端密钥不被反向出来才是最重要的,所以有了 fernet token 的做法避免长时间的用一个 key 去生成 token 降低密钥被解出来
        61
    ily433664   64 天前
    @yamedie token 是经过加密的,并不能直接篡改里面的信息
        62
    abcbuzhiming   64 天前
    @br00k 你那叫啥延期,你那叫生成一个新的 token 取代旧的
        63
    cai314494687   63 天前
    JSON Web Token - 在 Web 应用间安全地传递信息
    http://blog.leapoahead.com/2015/09/06/understanding-jwt/index.html
        64
    mingyun   59 天前
    看看之前的帖子 用简短的语言解释 JWT 和 session 的区别 https://www.v2ex.com/t/496310#reply53
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   3920 人在线   最高记录 5043   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 22ms · UTC 08:21 · PVG 16:21 · LAX 01:21 · JFK 04:21
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1