资讯详情

JWT Token 到底是什么?原理、结构、攻击面与面试考点全解析

📅 2026/9/20 17:23:27 | 华诺云谱 👁 阅读
JWT Token 到底是什么?原理、结构、攻击面与面试考点全解析
全网最透彻JWT Token 到底是什么原理结构流程图面试考点在接手过几个涉及分布式登录、单点登录和移动端鉴权的项目之后我对JWT的态度经历了三个阶段第一阶段的印象是JWT就是一段带着点号的长字符串解析出来放Redis里用第二阶段是这玩意儿签名机制不能乱改改错一个算法选项整个系统就被打穿第三阶段则是能不用JWT做状态管理就别硬用这套东西设计对了好用设计错了就是给攻击者递刀。这篇文章既然定调全网最透彻那就不整虚的直接从底层讲清楚JWT到底是什么、三段字符串各自承担什么职责、一次完整登录认证流程怎么走、攻击者常用的绕过方式是哪些、Token失效和续签在工程上怎么落地最后把面试里出现频率最高的几个考点一起串一遍。无论你是后端开发、前端开发还是正在准备面试的求职者这篇文章的目标只有一个看完之后你能用自己的话把JWT讲明白并且遇到实际项目问题时知道怎么选、怎么配、怎么防。1. 为什么要有 JWT从 Session 认证的困境说起1.1 Session 时代服务器扛着所有状态在没有JWT这类Token方案之前主流Web应用做登录认证的方式是Session。它的工作逻辑很简单用户提交账号密码服务器验证通过后在内存里创建一个Session对象生成一个随机的Session ID通过Cookie把这个ID写回浏览器同时把Session对象存在服务器自己的内存里。后续用户每次请求都带上Cookie中的Session ID服务器收到后拿这个ID去内存里查对应的Session对象查到了就认为请求已认证。这套机制在单体应用时代没有问题逻辑直白、服务端可控性强想踢人直接删Session就行。但它有一个天生的隐患用户的身份状态完全绑定在服务端的存储上。假设这台服务器的内存里存了一万个用户的状态它就是一万个用户在线状态的事实载体用户只要一断线或者服务器重启这些状态可能就没了。这里我多说一句Session并不等于只能存内存生产中也可以把Session存在Redis里实现持久化但核心没变服务端必须维护一份会话状态的存储且每次请求都要查一次。1.2 分布式与跨端场景下Session 为什么撑不住当你从单体架构转向多实例部署Session的第一个问题就暴露出来了负载均衡把请求分发到不同服务器节点用户第一次登录落在节点ASession存在A的内存里第二次请求被分发到节点BB的内存里没有这个Session用户就被判定为未登录。业界常见的解决办法是Session黏滞Sticky Session或者引入独立的Session中心比如用Redis集中存储Session。这两个方案都可行但都引入了额外的基础设施和维护成本。跨端场景则更加明显。如果你是做移动App从手机原生客户端访问接口客户端不一定方便处理Cookie你可能要把Session ID塞在请求头里然后手动保证每次请求都传。一旦你的业务有多个子域、微服务之间内部调用每个服务要判断这个请求是谁发起的如果全部依赖一个集中的Session服务那这个服务就成了整个系统的单点瓶颈。此时你会自然产生一个念头服务端既然不想维护状态能不能把用户的身份信息经过签名后直接发给客户端让客户端保存服务端通过验证签名来确认请求可信1.3 Token 化认证的底层思路把状态交给客户端这个念头就是JWT的设计出发点。JWT全称是JSON Web Token它是定义在RFC 7519里的一套规范说人话就是服务端把用户的身份信息user id、角色、过期时间等经过序列化用密钥签名后生成一段字符串这段字符串交给客户端保存客户端下次请求时把它带回来服务端只要验证签名没被篡改、过期时间没过就信这个Token里携带的身份信息不再需要去Session存储里查询任何东西。这就是JWT最核心的无状态Stateless特性。注意这里的无状态指的是服务端不需要保存会话状态而不是说这个Token不需要过期时间。恰恰相反无状态是把双刃剑服务端确实不用查库了但一旦签发出去的Token没有过期的概念就相当于交给客户端一张永久的通行证想收回都难。这也是我见过很多项目踩坑的地方——把无状态理解成不会失效结果Token签发出来两年不过期用户删除账号之后原来的Token照样能调用所有接口。2. JWT 的结构拆解三段字符串各管什么一个标准JWT长这样eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c用肉眼很难直接读出什么但经过Base64解码后它分三段用点号连接Header头Payload载荷Signature签名下面分别拆开讲。2.1 Header 与 Payload一眼看懂的明文部分Header通常包含两个字段{ alg: HS256, typ: JWT }typ表示这是一个JWTalg表示签名用的算法。这部分经过Base64Url编码后就是Token的第一段。这里有个极其重要的概念JWT的Header和Payload只是Base64Url编码不是加密。任何人都能在线解码看到里面的内容。所以绝对不要把密码、手机号、身份证号这类敏感信息直接塞进Payload。我处理过一起真实事故一个项目为了少查一次数据库把用户手机号明文放进Payload结果Token在日志里被打了出去手机号全泄露这个教训希望大家记住。Payload也是JSON结构除了业务自定义字段有几个标准字段需要重点记字段含义说明subSubject主题一般存用户IDissIssuer签发者比如填你的服务名称audAudience接收方用于限定Token只能给哪个客户端用expExpiration Time过期时间戳这是一个绝对时间过了这个时间Token必失效nbfNot Before生效时间早于这个时间的Token不可用iatIssued At签发时间jtiJWT ID唯一标识可用于吊销场景这些标准字段不强制全填但实战中exp几乎必须填不然Token永远不过期aud在多个端共用一套签发时可以防止App端和Web端的Token互相乱用。2.2 SignatureJWT 防篡改的真正核心Signature是JWT防篡改能力的来源。它的计算方式如下HMACSHA256( base64UrlEncode(Header) . base64UrlEncode(Payload), secret )也就是说把前两段编码后的字符串用点号拼接然后用Header里声明的算法拿密钥去对拼接结果做签名得到的签名字节再做Base64Url编码就是第三段。以HMAC SHA-256算法为例整个过程的关键在于密钥是服务端独有的客户端不知道Header和Payload只要被改动一个字符重新计算出的签名就和原签名不一致验签时服务端用同样的密钥、同样的算法重算签名比对是否一致不一致直接拒绝。所以JWT如何防止数据被篡改这个问题的标准答案是Payload的任何修改都会导致签名校验失败因为攻击者没有服务端持有的密钥无法为篡改后的内容生成合法签名。注意这里的签名和加密不是一回事JWT签名只保证数据完整性和来源可信不保证内容机密性内容本身是公开可读的。2.3 Base64Url 与一点字节换算经验JWT使用的不是常规Base64而是Base64Url。两者差别很小标准Base64中的和/在URL里会被特殊对待Base64Url把换成-、把/换成_同时去掉末尾的填充符。这样整段Token可以安全地放在URL、HTTP Header等场景里不用额外转义。实际开发中有一个小坑你拿在线Base64解码工具去解JWT的第一段和第二段时如果工具不支持Base64Url可能解不出来或者结果里有乱码原因就在这个编码差异上别怀疑是自己的Token坏了。字节换算方面可以记两个经验值一个包含用户ID、角色、签发时间、过期时间的Payload编码后整个JWT通常在200~500字节之间。不同浏览器对Cookie单条长度上限大约在4KB所以如果你打算把JWT放Cookie里长度可控但如果塞了很多自定义字段Payload膨胀到1KB以上Cookie就会很紧张。我一般建议Payload里只放必要的业务字段其他信息让服务端用用户ID去查。3. 一次完整登录认证流程从提交账号到请求放行3.1 登录阶段的动作顺序一次用JWT做登录认证的标准流程如下用户通过前端页面或App提交账号密码后端接收请求校验账号密码是否正确可能还会校验验证码、是否被锁定等校验通过后后端生成JWT把用户ID放进sub设置过期时间exp可能还会放入角色、昵称等非敏感字段后端用密钥对Token签名把生成的JWT字符串返回给前端前端收到Token后保存起来Web端一般放localStorage或内存里移动端放在本地存储中后续每次请求前端在HTTP请求头中加上Authorization: Bearer token。流程用文字箭头表示就是这样客户端 ---- 账号密码 ---- 认证服务 认证服务 ---- 校验凭证 ---- 用户存储 认证服务 ---- 校验结果 ---- 用户存储 认证服务 ---- 生成并签名JWT ---- 客户端 客户端 ---- 携带Token请求业务接口 ---- 业务服务 业务服务 ---- 验签查过期时间 ---- 本地校验无远程查询 业务服务 ---- 校验通过解析用户ID ---- 本地 业务服务 ---- 返回业务数据 ---- 客户端这里有一个容易混淆的地方认证服务签发Token之后业务服务验证Token时并不需要调用认证服务接口。业务服务只需要拿到同一个密钥自己验签、自己解析Payload即可。这也是微服务架构里JWT受欢迎的根本原因——每个服务都能本地完成信任校验不需要每次请求都去请求一次认证中心。3.2 后续请求携带 Token 的两种常见姿势前端携带Token最常规的姿势是放在HTTP Header的Authorization字段里GET /api/user/profile HTTP/1.1 Host: api.example.com Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...服务端框架获取Authorization字段去掉Bearer前缀后剩下的就是JWT。这个方式通用性最好不依赖Cookie不会自动被浏览器附带也就天然避开了CSRF跨站请求伪造攻击。另一种姿势是放在Cookie里Set-Cookie: token...; HttpOnly; Secure。优点是可以利用Cookie的HttpOnly属性防止JS读取降低XSS窃取Token的风险缺点是浏览器会在每个同域请求中自动带上Cookie而第三方站点可以通过表单等方式触发请求携带Cookie这让CSRF攻击有了可乘之机必须额外部署CSRF防护。权衡下来API类的应用我倾向于Header方式网页端对安全要求极高时再考虑CookieCSRF Token组合。3.3 服务端验签时到底做了什么服务端收到Token后完整的验证动作有四个格式检查确认Token是三段字符串分割出来并做Base64Url解码签名验证用密钥和Token头里声明的算法对前两段重算签名和第三段比对不一致则拒绝过期校验检查exp字段是否小于当前时间过期则拒绝业务校验根据aud、iss等字段判断这个Token是否适用于当前业务场景必要时还会查用户状态确认账号是否被封禁。很多人以为验签完就可以直接用Payload里的用户ID做业务了其实不对。exp过期只是最基础的一层如果用户主动注销、被管理员封禁、修改了密码要求所有旧Token失效服务端的验签逻辑里还应该有额外的状态查询步骤。这也是我在后文要专门讲失效和续签的原因。4. 防篡改原理与真实攻击面为什么 JWT 会翻车4.1 防篡改的核心链路签名不可伪造回到JWT如何防止数据被篡改这个问题本身。防篡改的前提只有一个签名密钥只有签发方知道。在这个前提下攻击者拿到一个合法Token如果把Payload里的用户ID从10001改成10002Token的长度和内容都发生了变化重算出的HMAC签名必然和原签名不同。攻击者不知道密钥无法计算出一个跟篡改后内容匹配的合法签名所以服务端验签必然失败。这就相当于银行存单上盖了一个防伪章存单上的金额可以涂改但涂改之后章就对不上了柜员一眼就能看出来。这是JWT防篡改最朴素的原理也是它取代某些自创Token方案比如拿一段用户ID直接拼个时间戳的关键原因——很多自创方案根本没有签名保护改个用户ID就能越权访问别人的数据这类漏洞我在接口安全测试中见得太多了。4.2 常见攻击一算法混淆签名算法看似简单实际攻击面非常广。最经典的攻击是算法混淆Algorithm Confusion。JWT的Header里有个alg字段服务端验签时会根据这个字段决定用哪种算法验证。早期很多库的实现存在一个问题验证方直接信任了Token头里声明的算法类型而不去校验算法是否在服务端预期范围内。攻击手法大致是这样的正常情况下签发算法是HS256即对称签名一把密钥既用来签名又用来验签但如果服务端配置了RS256它的密钥是一对——私钥签名公钥验证。HS256和RS256使用完全不同的密钥类型。攻击者拿到一个用RS256签发的合法Token强行把Header里的alg改成HS256同时用服务端的RSA公钥作为HMAC的密钥去重算签名。如果服务端没有强制算法白名单它看到alg是HS256就真的用公钥内容作为HMAC密钥去验签结果发现签名居然对得上成功放行了攻击者伪造的Token。要防这个攻击最直接的做法是服务端在验签时固定期望的算法不允许Token头里自由声明。比如我的项目中会写死只能使用HS256任何alg不是HS256的Token直接拒收。4.3 常见攻击二空签名空签名攻击更简单粗暴。有些JWT库支持alg: none表示不签名。如果服务端没有禁用none攻击者把Header里的算法改成none把签名段置为空只保留篡改后的Header和Payload把Token发给服务端。某些实现不完善的库看到alg: none就直接跳过签名验证攻击者就能任意伪造身份。防法很简单生产环境禁用none算法任何缺少签名段的Token一律拒绝。4.4 密钥泄露最大的现实风险如果说算法混淆和空签名是实现不严谨密钥泄露就是作死。HMAC密钥强度不够、被硬编码在代码仓库里、日志中意外打印都会导致攻击者直接拿到签名密钥。一旦密钥泄露攻击者就可以像服务端一样签发任意身份的Token所有防篡改保护形同虚设。我见过不止一个项目把JWT密钥写在配置文件里然后提交到Git仓库或者用123456jwt-key这种弱密钥。密钥的生成建议用至少256位随机字节生产环境和开发环境分开定期轮换。密钥轮换时要考虑旧Token的兼容窗口一般是把新密钥作为主要验签密钥同时保留旧密钥一段时间用于验证还未过期的老Token。5. Token 失效怎么处理过期、登出与续签方案5.1 过期时间的设计JWT最大的原罪就是不好主动失效。因为服务端不保存Token状态一个签出去的Token只要没到exp它就始终有效。因此过期时间exp的设计非常重要。过期时间设太长比如7天用户账号泄露后风险窗口很大设太短比如15分钟用户体验又很糟糕——正在看文章呢突然接口401了又要重新登录。一个常见的折中方案是访问Token的过期时间设置为15分钟到2小时刷新Token的过期时间设置为7天到30天。这就是双Token方案后文会详细说。5.2 登出即失效的黑名单方案用户主动退出登录时从服务端视角看需要让这个Token立刻作废。既然不能删那就拉黑。具体做法是把要失效的Token的唯一标识jti或者用户ID加签发时间的组合存到一个短期存储里Redis比较合适设置一个过期时间与Token自身的exp相符。服务端每次收到请求验签通过后再去查询这个黑名单如果命中了就拒绝。这个方案确实让JWT不再是严格无状态了但它解决了一个真实问题。如果你们的业务对安全要求高、用户有明确登出诉求黑名单方案是必须的否则退出登录就只是个前端操作Token依然能继续用。黑名单的粒度可以很细比如用户修改密码后把该用户所有已签发Token加入黑名单也可以按用户维度批量处理比如存一个user:id:token_version每次改密码版本号加1JWT里带上这个版本号验证时不匹配就拒绝。5.3 双 Token 刷新机制双Token刷新是目前生产环境最常见的方案。核心是两种TokenAccess Token访问令牌有效期短通常15分钟到2小时用来正常访问业务接口。因为它短暂即使泄露危害窗口也小。Refresh Token刷新令牌有效期长通常7天到30天只能用来调用刷新接口换取新的Access Token。刷新流程如下客户端登录后拿到Access Token和Refresh TokenAccess Token过期后访问业务接口返回401客户端拿着Refresh Token请求认证服务的刷新接口认证服务验证Refresh Token是否合法且未过期必要时查黑名单确认没被吊销认证服务颁发新的Access Token有时也刷新Refresh Token本身客户端用新Access Token继续访问业务接口。这里有几个工程细节值得注意Refresh Token也要有过期时间否则就是隐藏的长久通行证Refresh Token在刷新时尽量轮换即每次刷新都颁发新的Refresh Token旧的作废防止Refresh Token被反复重放刷新接口要加频控不然Refresh Token泄露后攻击者可能无限续签Refresh Token建议采用不透明字符串随机UUID存储在后端而不是继续用JWT。很多团队最终会把Refresh Token改成随机字符串存Redis这样吊销操作非常方便而Access Token继续用JWT来享受无状态验证的好处。这种混合方案是实践里最舒服的形态。6. 高频面试考点6.1 对比题JWT 和 Session 的取舍面试几乎必问JWT和传统Session有什么区别。答这道题要抓主线状态存在哪里。Session的状态存在服务端客户端只有Session ID认证逻辑是我拿ID去查我的存储查到就认。JWT的状态存在客户端Token里服务端通过验签确认Token没被篡改就认。顺着这条主线可以延展出优缺点JWT天然适合分布式/微服务场景每个服务都能本地验签不需要共享Session存储JWT天然适合移动端不依赖Cookie机制Session可以随时在服务端踢人下线JWT很难必须借助黑名单、版本号等手段才能实现类似效果Session占用服务端存储并发量大时需要额外存储中间件JWT不占服务端存储但Token里有信息网络传输体积略大。这道题没有标准唯一答案关键是让面试官看出你真的理解两种方案各自适用的场景而不是背结论。6.2 无状态与不可篡改的理解误区还有一个高频陷阱题很多人张口就说JWT是加密的所以安全。这个说法是错的。JWT的Header和Payload都是Base64Url编码任何人都能解码读出内容所谓的防篡改只依赖签名不提供保密性。如果你要确保Data不被第三方看到应该在放进Payload之前自己加密或者干脆不要把敏感数据放进去。所以面试时如果被问到JWT能存密码吗标准回答是不能。Payload是明文可读的密码存进去等于直接暴露。一定要加签名的目的是防止内容被篡改但这个保护不覆盖内容被查看。6.3 其他几个高频考点JWT能实现单点登录吗能。多个系统共用同一套JWT签发和验签密钥用户在A系统登录后拿到Token在B系统直接带Token访问即可。但要注意aud字段的设计避免一个系统的Token被拿到另一个系统里放行大范围使用。Token泄露了怎么办Access Token配合短过期时间减少风险窗口Refresh Token可以通过黑名单吊销再进一步可以给关键接口做IP/设备指纹绑定。JWT续签的常见方案三种一是每次都签发超长过期Token不推荐安全差二是刷新接口用Refresh Token换新Access Token推荐三是通过滑动过期策略在Token快过期时自动续期适用于网页应用。最后再分享一个实战心得如果你在做一个普通的单体管理后台用Session可能比JWT更省心因为踢人、登出、封禁这些操作天然好做。不要因为JWT听起来高大上就强行引入。如果你的系统已经有多套微服务、有移动端、有跨域SSO需求那JWT这套方案能帮你省掉很多会话同步的麻烦。而不管你最终选哪种上面的防篡改逻辑、算法白名单、密钥管理、失效策略都是绕不开的基本功。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。