为什么我不用 JWT 做登录

532 阅读5分钟

在现代 Web 程序中,JSON Web Tokens (JWT) 常常被用作身份验证和授权的标准解决方案。现在 JWT 真是越来越火热,看到很多项目都使用了 JWT,但是这篇文章将讲解一下,我为什么不用 JWT 做登录。

JWT 的组成

JWT 由三部分组成,头部、载荷和签名。

头部(Header)

头部包含两部分,令牌的类型(typ)和所使用的签名算法(alg)。 示例:

{
  "typ": "JWT",
  "alg": "HS256"
}

头部通常被编码为 Base64Url 字符串,以形成 JWT 的第一部分。

载荷(Payload)

载荷部分包含了声明(Claims),声明是关于实体(通常是用户)和其他数据的语句。声明可以分为三种类型:

  • 注册声明(Registered Claims):这些是预定义的声明,目的是提供一组基本的声明以便在不同的应用之间共享。常见的注册声明包括:
    • iss(发行者):签发 JWT 的主体。
    • sub(主题):JWT 所针对的主体(通常是用户的 ID)。
    • aud(观众):接收 JWT 的主体。
    • exp(过期时间):JWT 的过期时间戳。
    • nbf(生效时间):在该时间之前,JWT 不可用。
    • iat(签发时间):JWT 的签发时间。
    • jti(JWT ID):用于标识该 JWT 的唯一标识符。
  • 公共声明(Public Claims):这些是可以自定义的声明,应该避免冲突。可以在 IANA JSON Web Token Registry 中注册。
  • 私有声明(Private Claims):这些是双方之间自定义的声明,不应该在其他地方使用。

示例:

{
  "sub": "1",
  "name": "X"
}

签名(Signature)

为了生成签名部分,需要将编码后的头部和有效载荷结合,并使用指定的算法进行签名。签名的目的是验证消息在传输过程中未被篡改,并且确认发送者的身份。

例如,如果使用 HMAC SHA256 算法,签名的生成过程如下:

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

先说优点

说说我认为的 JWT 的优点:

  • 无状态:所有的用户信息和验证信息都被编码在 JWT 中,服务器只需验证签名,而无需存储任何会话信息。
  • 可扩展:JWT 的声明可以自定义,这使得 JWT 可以适应各种不同的应用场景。
  • 安全性:JWT 使用签名算法来验证消息的完整性和真实性,这使得 JWT 在传输过程中不会被篡改。

我为什么不用来登录

我觉得很别扭,很纠结,很矛盾,最终决定不会使用 JWT 来做登录。

理由如下:

  1. 都知道 JWT 是无状态的,但是登录这件事情它本身就是有状态的。
  2. 在使用 JWT 时,一旦 token 被颁发,它在过期之间都是有效的,如果用户信息、状态变更了,比如退出登录,我需要有额外的操作来不接受该 JWT。
  3. JWT 的体积往往是比实际存储的信息要大的多,增大在网络上传输时的开销。
  4. 在编码 JWT 和解码 JWT 时,都是有点耗时的,而且密钥不能泄漏。

常见的解决方案

说起常见的解决方案,更是让我感到别扭,纠结,矛盾。

我们就用 UUID 对比 JWT。redis 和 MySQL 统称为中间表。

1、主动注销 JWT 问题

上面说到,一旦 JWT 颁发,它在过期之间都是有效的。

有人说,如果要注销这个 JWT,那我把这个 JWT 拉黑,存入中间表,然后在每次校验 JWT 时,都去中间表查询是否拉黑。

既然还是每次要查中间表,我为什么不直接用 UUID 呢?每次查中间表中是否存在这个 UUID 就行了,注销只需把这个 UUID 从中间表中删除。

还有人说,双 token 方案,一个有效期短,一个有效期长,这样刷新 token 时如果注销了就拿不到新的token了。

对于这种,第一,在刷新 token 前,还是短 token 还是可用,第二,刷新 token 还是要去中间表查用户状态,第三,复杂度也提高了。

还有人说,注销后把密钥更换,这样之前的 JWT 都不认了。问题是,这是一个用户一个密钥吗?如果是,每次都要查对应密钥;如果不是,一个用户注销其他用户也跟着注销吗?

2、禁用 cookie、移动端没有 cookie 问题

对于这个问题,JWT 怎么存,怎么传,UUID 也一样就行。

3、JWT 安全问题

一般都使用 HTTPS,除非用户自己泄漏, JWT 是很难被拿到的。但是密钥是可能泄漏的,即使双密钥。当然觉得公司不会那么容易泄漏。

这就引发一个新的问题,如果是 Redis 密码泄漏,改密码就可以了,而且密码通常会定期更新,降低泄漏风险。如果是 JWT 密钥泄漏,只能改密钥了,但是之前的 JWT 就失效了,用户就得重新登录。

我认为 JWT 的使用场景

JWT 是无状态的,就很适合一次性的无状态场景。

比如,你通过邮箱注册了一个网站,它可能会给你发一封邮件,邮件中包含一个验证链接,点击链接后,你就注册成功了。这个链接中应该包含:本次注册的用户、过期时间(比如一天没点这个链接失效)、不能篡改(防止通过链接激活别的账户),这种场景就非常适合 JWT。

总结

JWT 在某些场景下非常有效,但在登录过程中其无状态特性与有状态需求之间的矛盾使其不够理想。对于这些,我纠结了非常长的时间,看了公司里很多系统没有找到几个 JWT 存储。翻了很多网站,没有找到一个是用 JWT 保存登录信息的。

总之,你用我推荐,我用我不用。