JWT 签发完整指南:HS256 / RS256 / ES256 选哪个
JWT 是互联网最常用的无状态身份验证标准。本文讲清楚三种签名算法的原理、适用场景、payload 设计与撤销难题,以及何时选哪个。
JWT(JSON Web Token)是无状态身份认证的业界标准,从微信小程序到企业级 OAuth2,处处可见。但很多开发者拿着三种签名算法(HS256 / RS256 / ES256)却搞不清什么时候该用哪个——HS256 用错了容易被冒签,RS256 用对了能支撑多方验证。本指南从三段结构讲起,逐一分析算法权衡与安全性,再到 payload 设计、过期策略、撤销方案,让你能自信地选出适合项目的方案。
JWT 的三段结构
JWT 由三段 Base64URL 编码的部分用 . 连接:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header(首段):{"alg":"HS256","typ":"JWT"} 指定签名算法和文档类型。解码后就是这个 JSON。
Payload(中段):{"sub":"1234567890","name":"John Doe","iat":1516239022} 是实际数据(claims)。注意:Base64 只是编码不是加密,任何人都能解码看到内容,所以不要放密码或敏感信息。
Signature(末段):用私钥或对称密钥对「header.payload」字符串签名,防止数据被篡改。只要内容变一个字符,签名就失效。
HS256:对称加密,单方控制
HS256 = HMAC with SHA-256,用同一个密钥(secret)既签名又验证。
签发流程:
``
token = base64(header) + '.'+ base64(payload) + '.' + hmac_sha256(base64(header) + '.' + base64(payload), secret)
``
验证流程:重新计算签名,比对是否相同。
优点: - 实现最简单,一行代码搞定 - 性能最快(HMAC 比 RSA 快 10 倍) - 密钥小,token 体积小
缺点: - 对称密钥,签发和验证端都要拥有同一个 secret - 适合单体架构或内部系统,不适合多方验证(如开放平台) - secret 泄露后,攻击者可伪造任何 token
典型场景:单体 Node.js 服务自己签发自己验证的 token,或内部微服务间通信。
RS256:非对称加密,多方验证
RS256 = RSA Signature with SHA-256,用私钥(private key)签名,用公钥(public key)验证。
签发流程:使用私钥签名,发放 token 和公钥。
验证流程:用公钥验证,无需私钥。
优点: - 公钥可安全公开(GitHub、谷歌等大厂都这样做) - 支持多方验证:中心签发 token,分布式服务都能用公钥验证 - 私钥泄露风险更低(通常存在专门的密钥服务器) - OAuth2 标准推荐
缺点: - 密钥更复杂(2048 位 RSA 至少 1700 字节) - 签名与验证性能比 HS256 慢 - 初期配置复杂
典型场景:OAuth2 授权服务器签发 token,各个资源服务器都能验证。或公有云 API(AWS、Azure)。
ES256 与其他算法
ES256 = ECDSA with SHA-256(椭圆曲线),介于 HS256 和 RS256 之间。
- 性能接近 HS256(快)
- 密钥比 RSA 小(256 位 ECDSA = 2048 位 RSA 的安全强度)
- 非对称,支持多方验证
实际应用:Apple、Google、微信等新一代服务用 ES256 代替 RS256。初创项目倾向 ES256,老项目沿用 RS256。
选择指南: - 单体服务或内网 → HS256(快、简单) - 分布式/开放平台 → RS256 或 ES256(多方验证) - 新项目优先 → ES256(性能与安全的最佳平衡)
常见 Claims 与设计原则
JWT payload 建议包含这些 standard claims(rfc7519):
iss(issuer):签发者,如https://auth.myapp.comsub(subject):主体,通常是用户 IDaud(audience):受众,指定哪个服务能用这个 token,如api.myapp.comexp(expiration time):过期时间,Unix 时间戳(秒)iat(issued at):签发时间nbf(not before):生效时间,通常 = iat(可用来延迟生效)jti(JWT ID):唯一 ID,用于追踪和撤销
设计原则: - 不放敏感数据:Base64 随意解码,token 里别放密码、身份证、银行卡 - 保持精简:每个 claim 增加 token 体积,HTTP header 大小有限制 - 用 aud 做访问控制:不同服务验证时检查 aud 是否匹配,防止 token 串用 - 用 jti 追踪:调试和安全审计时需要唯一身份
过期策略:access token + refresh token
单用一个长期 token 不安全(泄露影响大)。业界标准做法是双 token 体系:
Access Token: - 短期(15 分钟 ~ 1 小时) - 用于访问 API - 泄露损失相对小 - 过期后服务器直接拒绝,无需查库
Refresh Token: - 长期(7 天 ~ 30 天) - 仅用于刷新获取新 access token - 存在数据库,可追踪和撤销 - 泄露后在数据库删除即可阻止进一步续期
- 登录成功,返回 access token + refresh token
- 用 access token 调 API
- Access token 过期,用 refresh token 申请新 access token
- Refresh token 过期或被撤销,需重新登录
这样既享受无状态的便利,又能在需要时快速撤销(delete from db)。
JWT 撤销难题的三个方案
JWT 最大问题:签发后无法立即作废(无状态 = 无法查库)。三种常用方案:
方案 1:短期 token(推荐) 设 access token 15 分钟过期,攻击者获得 token 也只能用 15 分钟。用户登出时撤销 refresh token。
方案 2:黑名单数据库 用户登出或修改密码时,把 token 的 jti 存进黑名单表。验证时先查黑名单,这样破坏了"无状态"但仍比 session 更轻。
方案 3:改用 Session 如果撤销需求极高(如金融),就别用 JWT,用 session 结合 Redis,每次都查库验证。损失无状态的便利,但获得完全控制。
实战建议:互联网产品选方案 1(短期 access token),政府/金融/医疗选方案 2 或 3。
常见问题
HS256 的 secret 应该多长?
至少 32 字符(256 bits)。用加密随机生成,不要是弱密码或日期。Node.js:crypto.randomBytes(32).toString('hex')。
如何防止 token 被 XSS 偷走?
Access token 存 httpOnly cookie(脚本无法访问),refresh token 存 secure cookie。这样 XSS 攻击也伤害有限。
Token 在哪里放传输?
通常放 Authorization header:`Authorization: Bearer <token>`。不要放 URL 参数(日志和浏览器历史会记录,比 header 不安全)。
验证 JWT 时还需要检查什么?
除了签名有效,还要验证:(1) exp 是否过期,(2) aud 是否匹配当前服务,(3) iss 是否来自可信来源,(4) jti 是否在黑名单(如用了方案 2)。