完全指南)
JWTJSON Web Token是现代 Web 应用中最广泛使用的身份验证与信息交换标准之一。本文从原理到实践系统性地介绍 JWT 的方方面面。一、什么是 JWTJWTJSON Web Token是一种开放标准RFC 7519定义了一种紧凑且自包含的方式以 JSON 对象的形式在各方之间安全地传输信息。由于数字签名的存在这些信息是可验证且可信的。两种核心使用场景授权Authorization这是 JWT 最常见的用途。用户登录后每个后续请求都携带 JWT允许用户访问令牌许可的路由、服务和资源。SSOSingle Sign-On大量使用 JWT因为其开销很小且可跨域使用。信息交换Information ExchangeJWT 是在各方之间安全传输信息的好方法。由于可以对 JWT 签名例如使用公/私钥对你可以确认发送者的身份。此外由于签名是用 header 和 payload 计算的还可以验证内容未被篡改。二、JWT 的结构JWT 由三部分组成用点号.分隔Header.Payload.Signature一个真实的 JWT 看起来像这样eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ### 2.1 Header头部 Header 通常由两部分组成令牌的类型typ即 JWT以及所使用的签名算法alg例如 HMAC SHA256 或 RSA。 json { alg: HS256, typ: JWT }然后对该 JSON 进行 Base64URL 编码形成 JWT 的第一部分。2.2 Payload载荷Payload 包含声明Claims。声明是关于实体通常是用户及附加数据的陈述。JWT 定义了三种类型的声明注册声明Registered Claims——预定义的、非强制性但推荐使用的声明集字段全称说明issIssuer签发方subSubject主题通常为用户 IDaudAudience接收方expExpiration Time过期时间Unix 时间戳nbfNot Before生效时间iatIssued At签发时间jtiJWT IDJWT 唯一标识可防重放公共声明Public Claims——由使用者自由定义但应在 IANA JSON Web Token Registry 中注册或使用带命名空间的 URI以避免冲突。私有声明Private Claims——通信双方约定的自定义字段例如role: admin或permissions: [read, write]。{sub:1234567890,name:Alice,role:admin,iat:1516239022,exp:1516242622}重要Payload 是经过 Base64URL 编码的而非加密的。任何持有令牌的人都可以解码并读取内容。绝对不要将密码、敏感个人信息等放入 Payload。2.3 Signature签名签名用于验证消息在传输过程中未被篡改对于使用私钥签名的令牌还可以验证 JWT 的发送者身份。以 HS256 算法为例签名的计算方式如下HMACSHA256( base64UrlEncode(header) . base64UrlEncode(payload), secret )三、JWT 的工作流程整体流程分为两个阶段认证阶段一次性和授权阶段每次请求。在实际 HTTP 请求中JWT 通常放在Authorization请求头中格式为Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...四、签名算法详解JWT 支持多种签名算法选择哪种取决于你的安全需求和架构模式。4.1 对称算法HMAC使用同一个密钥进行签名和验证。HS256 → HMAC SHA-256 HS384 → HMAC SHA-384 HS512 → HMAC SHA-512适用场景签发方与验证方是同一个服务例如单体应用或内部微服务之间的信任通信。风险所有需要验证 JWT 的服务都必须持有密钥一旦泄露所有令牌都可伪造。4.2 非对称算法RSA / ECDSA使用私钥签名公钥验证。RS256 → RSA SHA-256最常用 RS384 / RS512 ES256 → ECDSA P-256更小的密钥更快的计算 ES384 / ES512适用场景Auth Server 持有私钥签发令牌各 Resource Server 只需公钥即可验证无需信任链扩散。这是微服务架构中的推荐方案。私钥Auth Server 独有→ 签名 JWT 公钥所有 Resource Server 共享→ 验证签名五、实战代码示例5.1 Node.jsjsonwebtokenconstjwtrequire(jsonwebtoken);// 签发 JWTfunctionissueToken(userId,role){constpayload{sub:userId,role:role,iat:Math.floor(Date.now()/1000),};returnjwt.sign(payload,process.env.JWT_SECRET,{algorithm:HS256,expiresIn:2h,// 过期时间issuer:my-app,audience:my-api,});}// 验证 JWTfunctionverifyToken(token){try{constdecodedjwt.verify(token,process.env.JWT_SECRET,{algorithms:[HS256],// 白名单防止 alg:none 攻击issuer:my-app,audience:my-api,});return{ok:true,payload:decoded};}catch(err){return{ok:false,error:err.message};}}// Express 中间件示例functionauthMiddleware(req,res,next){constauthHeaderreq.headers.authorization;if(!authHeader?.startsWith(Bearer )){returnres.status(401).json({error:Missing token});}consttokenauthHeader.slice(7);constresultverifyToken(token);if(!result.ok){returnres.status(401).json({error:result.error});}req.userresult.payload;// 注入用户信息next();}5.2 PythonPyJWTimportjwtimportosfromdatetimeimportdatetime,timedelta,timezone SECRET_KEYos.environ[JWT_SECRET]ALGORITHMHS256# 签发 JWTdefcreate_token(user_id:str,role:str)-str:payload{sub:user_id,role:role,iat:datetime.now(timezone.utc),exp:datetime.now(timezone.utc)timedelta(hours2),iss:my-app,}returnjwt.encode(payload,SECRET_KEY,algorithmALGORITHM)# 验证 JWTdefverify_token(token:str)-dict:try:payloadjwt.decode(token,SECRET_KEY,algorithms[ALGORITHM],# 白名单issuermy-app,)return{ok:True,payload:payload}exceptjwt.ExpiredSignatureError:return{ok:False,error:Token expired}exceptjwt.InvalidTokenErrorase:return{ok:False,error:str(e)}六、安全最佳实践这是 JWT 开发中最容易出错的地方。以下问题在真实漏洞报告中反复出现。6.1 算法混淆攻击alg:none危险代码// ❌ 危险信任 Header 中的 alg 字段constdecodedjwt.verify(token,secret);攻击者可以将 Header 中的alg改为none然后发送一个没有签名的令牌部分库会错误地接受它。正确做法// ✅ 安全在验证时显式指定允许的算法白名单constdecodedjwt.verify(token,secret,{algorithms:[HS256]});6.2 RS256/HS256 混淆攻击若服务端使用 RS256攻击者可以将alg改为HS256并用服务端的公钥通常是公开的作为 HMAC 密钥来签名伪造令牌。始终在验证时硬编码期望的算法而不是读取令牌 Header 中的算法。6.3 密钥强度HMAC 密钥应至少 256 位32 字节且随机生成绝不使用短字符串或常见词组# 生成强随机密钥node-econsole.log(require(crypto).randomBytes(64).toString(hex))6.4 过期时间exp永远要设置过期时间。Access Token 建议 15 分钟到 2 小时Refresh Token 可以更长7 到 30 天。jwt.sign(payload,secret,{expiresIn:15m});// 短期令牌6.5 令牌存储存储位置XSS 风险CSRF 风险推荐度localStorage高JS 可读无❌ 不推荐sessionStorage高JS 可读无❌ 不推荐内存JS 变量低无✅ 推荐HttpOnly Cookie无JS 不可读需配合 CSRF Token✅ 推荐6.6 不可撤销性问题与解决方案JWT 的一大设计特点是无状态——服务端不存储令牌因此无法主动使其失效。这在以下场景下成为问题用户主动登出账号被封禁权限变更需立即生效常用解决方案方案一短过期时间 Refresh Token 机制 Access Token → 15 分钟过期泄露影响窗口小 Refresh Token → 存储在服务端支持主动吊销 方案二Token 黑名单Blocklist Redis 存储已吊销的 jti验证时检查牺牲部分无状态性 方案三Token 版本号 用户表中存储 token_version每次登出自增 JWT 中携带版本号验证时与数据库比对七、JWT vs Session如何选择维度JWTSession状态无状态服务端不存储有状态服务端存储 Session水平扩展天然支持无需共享存储需 Session 共享Redis 等令牌吊销困难需额外机制简单直接删除 Session跨域/跨服务天然支持Authorization Header受 Cookie 同源限制网络开销每次请求携带令牌较大Cookie 较小微服务/SSO非常适合较难实现即时权限变更需要额外设计立即生效选择建议微服务架构、需要 SSOSingle Sign-On的场景、无状态 API倾向 JWT传统 Web 应用、需要立即吊销会话、更严格的安全控制倾向 Session 或两者结合使用 JWT 作为 Refresh Token 的凭证Session 作为短期授权。八、Refresh Token 模式生产环境中通常结合使用短期 Access Token 和长期 Refresh Token登录成功 ↓ 返回 access_tokenJWT15 分钟过期 refresh_token不透明随机字符串30 天存储在 HttpOnly Cookie 访问资源携带 access_token ↓ 若 access_token 过期401 ↓ 用 refresh_token 换取新 access_tokenPOST /auth/refresh → Auth Server 验证 refresh_token查库 → 若有效颁发新 access_token可选Rotation同时更新 refresh_token ↓ 继续使用新 access_tokenRefresh Token Rotation轮换是一种重要的安全机制每次使用 Refresh Token 时同时使旧令牌失效并颁发新令牌若检测到旧令牌被重复使用则认为令牌已被盗立即吊销所有相关令牌。九、常见误区总结误区一JWT 是加密的。JWT 默认只做签名JWS不加密。Payload 是 Base64URL 编码任何人都可以解码读取。若需加密使用 JWEJSON Web Encryption。误区二JWT 比 Session 更安全。JWT 和 Session 各有优劣安全性取决于正确实现不是 JWT 本身更安全。误区三在 JWT 中存储密码或敏感数据。绝不这样做。误区四JWT 可以无限使用。一定要设置合理的过期时间并考虑令牌吊销机制。误区五alg: none被某些库接受是小事。这是一个严重的安全漏洞一定要在验证时白名单指定算法。JWT 凭借其无状态、自包含、跨语言的特性成为了现代 API 认证的事实标准。但使用得当的关键在于理解它的设计边界JWT 解决了验证信息的真实性与完整性的问题而令牌吊销、存储安全等问题需要在架构层面补充解决。掌握这些才能将 JWT 用好、用安全。