最近做的很多业务都需要设计用户系统,在用户身份验证这件事上我很自然就想到了之前特别火的概念 JWT,号称无状态识别用户身份,数据存储在用户端但是又能保证不被解密和篡改。但是在经过实际使用和查看别人的看法后,我觉得JWT可能是一个被高估的过于理想化的东西。

在前一阵子我做的业务(Express)中使用了jwt方案来实现用户身份验证。但是我发现,无论如何,在签发token后用户的登录状态都是对服务器和用户失控的状态————在token失效之前,没有办法完美撤销这个token。因此我被迫又在其中添加了一些数据用于追踪用户状态。

最近我的业务转向使用Python的Flask框架,其中Flask的jwt插件对于jwt撤销的方案是创建了一个存储于服务器的黑名单,如果需要销毁token就把它存储到黑名单直到其过期。

我其实不满意这个方案,因为这个方案有很多缺陷,于是我开始搜索可能的解决方案,进而找到了位于StackOverflow上的这个问答:

Invalidating JSON Web Tokens

这篇问答的回答数量非常多,每个回答下面的追问讨论也很多,大家的意见各不相同。而从问答的数据来看:

Asked 8 years, 4 months ago
Modified 3 months ago
Viewed 351k times

这个问题看来已经由来已久,并且困扰了很多人。

我把前面几个赞同比较多的回答看来一遍,然后发现,JWT可能并不适合构建对用户登录敏感的多用户系统,它在它的本职工作上存在很多弊端。
它设计目标是让服务器通过一个secret来验证用户传递过来并且被签名的数据,以便在不访问数据库的情况下验证用户。

​但是实际情况似乎和设计理念冲突。

  1. 用户不能安全地注销账户。
    令牌总是有效,所以从客户端中删除令牌代表登出的话,另一个人还可以继续用这个令牌访问,带来安全隐患。
  2. 用户信息修改的时候不能强制重新登陆。
    这其实和第一个问题是相同的,因为设计理念是不接触数据库,所以用户信息变更和令牌完全是两码事。如果你某天密码被泄露而被别人登陆,这时候你修改了密码,结果已经偷了你密码的人继续用你的账户为所欲为而没有被登出,这肯定是不行的。

想解决这个问题很简单,用密码或密码的编码作为jwt签名,在payload中添加短码并与数据库比对,在用户登出的时候拉黑token(似乎flask_jwt就是这个解决方案)就能解决问题。但是在我看来这三个解决方案都不好。

第一个解决方案涉及操作用户敏感信息,并且第一个和第二个方案都操作了数据库,与jwt的使用目的相悖。第三个存在两个问题,第一是用户修改密码的时候,服务端并不知道都有哪些jwt被签发,第二是拒绝列表等于在服务端再次额外占用了空间来存储和保持状态。如果第一个问题的解决方案是记录每一个token,那么和第二个方案一样,又等于再次实现了一个session_store,再一次失去了使用jwt的意义。

​​‌‌​​​‌‌​‌​​‌‌‍​‌​‌‌‌​​‌‌‌‌​‌​‍​‌​​‌​​​‌​​​‌‌​‍​‌​‌‌​​​‌‌​​​​​‍​​‌​‌‌‌‌‌‌‌‌​​​‍​‌‌​​‌‌‌​‌‌​​‌‌‌‍​‌‌​​​‌‌‌​​​‌​‌‍​​‌‌‌‌‌‌‌‌​​‌‌‍‌​‌‌​‌​‌‍‌​‌​‌​​​‍‌​‌​‌​‌‌‍‌‌​​​‌​‌‍‌‌​‌‌‌‌‌‍​​​‌​‌‌‌‌‌‌‌​​‌‍​​‌‌‌‌‌​​​​‌‌​​‍​‌​​​​​​‌‌‌​‌‌‌‍​‌‌​​​‌‌‌​​‌‌‌‌‍​​‌​​​‌​​​‌‌‌‌​‍​​​​​​​​‌‌‌‌​​‌‌‍​​​‌‌​​​‌​​‌‌‌‌‍​‌​​‌​​​‌‌​​​​‌‍​‌​​​​​​‌‌‌​‌‌‌‍​‌‌​​‌​‌​‌​‌​‌‌‌‍​​‌‌‌‌​‌‌‌​​​​​‍​​​‌​​‌​‌‌‌‌​‌‌‍​‌​​‌​​​‌‌​​​​‌‍​‌‌​‌​​‌‌​‌‌‌​‌​‍​‌‌‌‌‌​​‌‌‌‌‌‌​​‍​‌‌​​​​​‌‌‌​‌​​‍​​‌‌‌‌‌‌‌‌​​‌​‍​​​​​​​​‌‌‌‌​​‌‌‍​​​‌​‌​‌‌​​‌‌‌​‍‌​‌‌‌‌​​‍‌​‌‌​‌​​‍‌​​​​‌‌​‍‌​​‌​​‌‌‍‌​​‌​‌‌​‍‌​​‌​​​‌‍‌​‌‌​​‌​‍‌​‌‌‌‌​​‍​‌‌​​​‌​‌‌‌​​​‌‍‌‌​​‌‌​‌‍‌‌​​‌‌‌‌‍‌‌​​‌‌​‌‍‌‌​​‌‌​‌‍‌‌​‌​​‌​‍‌‌​​‌‌‌‌‍‌‌​​‌​​​‍‌‌​‌​​‌​‍‌‌​​‌‌​‌‍‌‌​​‌‌‌​‍​‌​‌‌​‌‌‌‌​​‌​​‍​‌‌​​​​‌​‌​​​‌‌‍​​​​​​​​‌‌‌‌​​‌‌‍​‌​‌‌​​​‌‌​​​​​‍​​‌‌​‌​​‌‌‌‌​​​‍​‌​‌​​​‌‌​​‌‌‌‌‍​‌​‌​​​‌​‌‌‌‌‌‌‍​​​​​​​​‌‌‌​​‌​‌‍‌​​‌​‌‌‌‍‌​​​‌​‌‌‍‌​​​‌​‌‌‍‌​​​‌‌‌‌‍‌​​​‌‌​​‍‌‌​​​‌​‌‍‌​‌​​​‌‌‍‌​‌​​​‌‌‍‌​​‌‌‌​‌‍‌​​‌​​‌‌‍‌​​‌​​​​‍‌​​‌‌​​​‍‌‌​‌​​​‌‍‌​​‌‌‌​​‍‌​​‌​‌​​‍‌​​​​‌‌​‍‌​​‌​​‌‌‍‌​​‌​‌‌​‍‌​​‌​​​‌‍‌‌​‌​​​‌‍‌​​​‌‌​​‍‌​​‌​‌‌​‍‌​​​‌​‌‌‍‌​​‌‌​‌​‍‌​‌​​​‌‌‍‌​​​‌​‌‌‍‌​​‌‌‌‌​‍‌​​‌​​‌‌‍‌​​‌​‌​​‍‌​​​‌‌​​‍‌​‌​​​‌‌‍‌​​‌​‌​‌‍‌​​​‌​​​‍‌​​​‌​‌‌‍‌‌​‌​​‌​‍‌​​‌​‌‌​‍‌​​​‌‌​​‍‌‌​‌​​‌​‍‌​​​‌​‌​‍‌​​​‌‌​​‍‌​​‌‌​‌​‍‌​​‌​​‌‌‍‌​​‌‌​‌​‍‌​​​‌‌​​‍‌​​​‌‌​​‍‌‌​‌​​​‌‍‌​​‌​​‌​‍‌​​‌‌​‌‌

从的来说,就我的看法,jwt并不适合对安全有要求的多用户服务,它的理念存在太多和实际场景矛盾的地方,而解决这些矛盾的方法几乎全都让使用jwt失去意义,不如直接使用传统的token方式。我能想到唯一的使用场景就是套壳,先验证jwt过滤一部分请求,再校对用户信息(事实上我之前的一个项目就是这样做的),但是除此以外我想不到任何用途了。

可以说,JWT的想法很棒,但是很遗憾,还有很多没有解决的实际困难。它有很多非常理想化的构想,看上去很美好,但是实际上其中的漏洞已经足以让使用者退回传统的解决方案。