「登录后由服务器交给你的"带签名的通行证"」——这就是 JWT。本文讲解它的原理以及安全使用的关键要点(不会写出攻击手法)。
原理(三个部分)
JWT 是用 . 把 头部.载荷.签名 三部分连接而成的字符串。头部和载荷只是用 base64url 编码(并非加密),只有签名是用密钥(或共享密钥)生成的。
① 头部
签名算法等元信息。任何人都能读
② 载荷
用户 ID、有效期等主张。任何人都能读=不要放机密
③ 签名
用密钥生成。保证未被篡改
内容「只是可读」这一点至关重要。所以载荷不是放密码或个人信息的地方。JWT 的价值在于:服务器通过校验签名,就能确认「这个令牌是我签发的、且未被篡改」。
安全使用的防御措施
必须校验签名,并固定 alg(最重要)
在服务器端每次都校验签名。把允许接受的签名算法(alg)固定为期望的值,并拒绝 alg:none(无签名)。不要轻信令牌头部里写的 alg。
不要把机密放进载荷
要以"内容人人都能解码"为前提。密码、个人信息、API 密钥等机密不要放进去。只放标识符或权限等即便泄露也不致命的主张。
把有效期设短,并准备失效手段
访问用令牌要设为短命。过长的有效期会延长令牌被盗后的受害时间。如果设计上需要失效,就把短命的访问令牌+服务器端管理的刷新/会话搭配使用。
使用强密钥,并存放在不会被盗的地方
签名密钥要足够强,不要复用,并保管在服务器端的安全位置。令牌本身也要连同保管与传输一起守护,比如不被 XSS 窃取、用带安全属性的 Cookie 传输(→ 注意经由 XSS 的窃取)。
本站观点:JWT 并非「万能的会话」
JWT 常被用于「保存登录状态」,但它有一个弱点——不擅长失效。只要签名正确且在有效期内,服务器基本上就会信任它,因此很难让登出或失效立即生效。本站的立场是:「不要把 JWT 当作长命的万能会话」。把短命的访问令牌,与服务器端可撤销的刷新/会话组合起来,就能兼顾 JWT 的优点(校验轻量)和失效的便利性。选对用途才是正解。
盲点:「能解码」不等于「正确」
JWT 的内容是可读的,所以只要贴进 JWT 解码器,任何人都能查看头部和载荷。但是解码(读取内容)与校验(确认真伪)完全是两回事。能解码并不能保证任何正确性。判定令牌真伪的,永远是服务器端的签名校验。调试时查看内容很方便,但请不要误以为「能读出来就有效」。
接下来阅读
- 工具:JWT 解码器 / 检查(查看内容、检查 alg:none 或是否过期)
- 术语:XSS 是什么(窃取令牌的典型路径)/ CSRF 是什么
- 入门:如何选择双因素认证(MFA)
FAQ
QJWT 的内容是加密的吗?
不是。标准 JWT 的头部和载荷并非加密,只是用 base64url 做了「编码」,任何持有令牌的人都能读到里面的内容。所以绝不能把密码、个人信息等机密放进载荷。JWT 保护的不是「内容的保密性」,而是「未被篡改(靠签名保证的完整性)」。
QJWT 中最危险的设置是什么?
不校验签名,或者接受「alg:none(无签名)」的实现。一旦允许,攻击者就能让自己随意改写内容的伪造令牌通过。防御方法是:在服务器端必须校验签名,并把允许接受的签名算法(alg)固定为期望的值。
Q解码和校验一样吗?
不一样。解码只是读取内容,任何人都能做(用 JWT 解码器即可查看)。校验则是服务器用密钥/公钥确认签名是否正确、是否在有效期内、签发者和受众是否正确的处理过程。「能解码=就是正确的令牌」是错误的。判定真伪的永远是服务器端的校验。