安全 · 阅读约 4 分钟 ·

JWT 里有什么,以及为什么“解码”不等于“验证”

JSON Web Token 看起来像一串乱码,但任何人都能读出内容。本文介绍它三个部分里装着什么,以及会导致安全漏洞的那个错误。

JWT(JSON Web Token)就是让你在很多 Web 应用和 API 中保持登录的那串长字符。它看起来像是加密的,但大多数情况下并没有加密:任何拿到令牌的人都能读出里面的内容。

用点分隔的三个部分

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiJ9.3q2-7w…
└ header ──────────┘ └ payload ───────┘ └ signature

header 说明令牌的签名方式,例如 HS256 或 RS256。payload 里是各种声明(claims):用户是谁(sub)、令牌何时签发(iat)、何时过期(exp),以及应用加入的其他信息,比如角色。两部分都只是用 base64url 编码的 JSON,这是编码,不是加密。

关键在签名

第三部分是对前两部分的签名,用密钥或私钥生成。只有持有正确密钥的服务器才能验证它。只要有人改动 payload 里的一个字符,比如把角色从 user 改成 admin,签名就对不上了。

解码不等于验证

能正常解码的令牌只告诉你它声称了什么,而不是这些声称是否属实。你的后端必须用密钥验证签名,拒绝 exp 已过期的令牌,并且只接受预期的算法,绝不接受“none”。JWT 库会替你完成这些;跳过这一步是典型的安全漏洞。

实用规则

不要把密码之类的机密放进 payload,因为它是可读的。过期时间要短。令牌有效期间要像对待密码一样对待它:任何复制了它的人在过期前都能使用它。

令牌在浏览器里存放在哪里

存在 localStorage 里的东西,页面上任何脚本都能读取,所以一个跨站脚本(XSS)漏洞就可能泄露令牌。HttpOnly Cookie 根本无法被 JavaScript 读取,这就消除了这个风险;再配合 Secure 和 SameSite 属性以及跨站请求伪造(CSRF)防护。没有完美的方案,但对会话令牌来说,HttpOnly Cookie 是更安全的默认选择。

过期时间与刷新令牌

JWT 本身无法被“注销”:除非服务器维护一份吊销列表,否则它会一直有效,直到 exp 时间。所以访问令牌的有效期通常以分钟计,而不是以天计。一个有效期更长、保管更谨慎、并由服务器校验的刷新令牌,用来在旧的访问令牌过期时换取新的。

常见声明一览

iss  签发者      谁创建了令牌
sub  主体        令牌涉及的用户或客户端
aud  受众        哪个服务应当接受它
exp  过期时间    超过这个 Unix 时间即失效
nbf  生效时间    早于这个 Unix 时间无效
iat  签发时间    创建时的 Unix 时间

时间类声明是以秒为单位的 Unix 时间戳。服务器除了 exp 还必须检查 aud:为一个服务签发的有效令牌,不应被另一个服务接受。

读取令牌

4Dev 的 JWT 解码器会把令牌拆成各个部分,并以易读的 JSON 显示 header 和 payload。它只解码,不检查签名。令牌会发送到 4Dev 服务器进行解码,不会被记录或保存;但仍请不要粘贴生产环境中有效的令牌,并吊销任何你认为已泄露的令牌。

打开 JWT 解码器

LinkedInXFacebook