1. 为什么会有"多次请求共享数据"的需求?
因为 HTTP协议是无状态的 。也就是说,你买完东西(请求1)付了钱,服务器把货给你后,下一秒服务器就会把你忘得一干二净。当你再次敲门(请求2)时,服务器根本不认识你是谁,也不知道你刚才是否已经付过钱。
所以,为了让服务器"认出"你,必须在多次请求之间传递一个"信物"。
2. 两种"共享数据"的经典方案
方案一:用"存根"共享(传统的Session)
- 做法:你在服务器(后台)有个专属抽屉(Session空间),里面放着你的"姓名、会员等级、购物车"。
- 信物 :服务器只给你一把小钥匙(Session ID),你把它挂在脖子上(Cookie)。
- 每次请求 :你掏出钥匙,服务器拿着钥匙去后台的抽屉里查数据。
- 共享的本质 :数据共享发生在服务器内存里。多次请求之间靠的是"钥匙编号"来关联同一个抽屉。
方案二:用"复印件"共享(JWT令牌)
- 做法:服务器不再给你准备专属抽屉了。
- 信物 :服务器把你的"姓名、会员等级、过期时间"写在一张纸上,并且盖上一个无法伪造的钢印(数字签名),然后把这张纸(JWT)直接给你。
- 每次请求:你只需要把这张纸**完整地复印一份(令牌本身)**递交给服务器。
- 共享的本质 :数据共享发生在客户端携带的这张纸上 。服务器收到后,撕开钢印验证没被篡改,直接读取纸上的内容,根本不用去后台查任何东西。
3. 为什么"将共享数据存储在令牌当中"是可行的?(关键)
你可能会疑惑:数据存在客户端,服务器怎么敢相信?"存"在令牌里和"写"在便签上完全不同,因为令牌具有"自证清白"的能力。
之所以可行,取决于三个硬核机制:
- 防篡改性(核心命脉) :JWT包含一个签名(Signature)。签名是由
头部+载荷+密钥经过加密算法算出来的。如果黑客把你令牌里的"普通会员"改成"超级管理员",服务器重新计算签名时发现对不上,直接拒绝请求。所以,数据虽然存在外面,但无人能伪造。 - 省去"查库"的IO开销 :在Session方案中,每次请求都要去Redis或数据库读取一次用户数据(这是网络IO,很慢)。而JWT方案把数据编码在令牌里,服务器只需要消耗一点点CPU算力去验签,直接从令牌字符串里把用户ID和昵称抠出来用,速度极快。
- 天然的水平扩展 :如果你有10台服务器,使用Session时,第1台服务器存的抽屉,第2台找不到。必须用Redis共享。而使用JWT时,每台服务器手里都有"验伪钢印(密钥)",谁都能独立解析这张纸,无需共享存储,这就是无状态的优势。
4. 但是,请注意一个"数据大小"的陷阱
既然数据能存在令牌里,是不是把所有共享数据都塞进JWT就完事了?
绝对不行!
因为每次请求,浏览器都会把完整的JWT令牌放在HTTP头里发过来。如果你把"用户头像Base64、收货地址列表、历史浏览记录(几千字节)"全塞进令牌,那么:
- 用户发一个"点赞"请求,本来只传
50字节,现在附带5KB的令牌。 - 100万并发用户,上行带宽会被瞬间占满,服务器解析长字符串的CPU开销也会爆炸。
正确的做法是:令牌里只存"最小必要唯一标识" (比如 user_id 和 role)。至于用户的昵称、头像等经常变动的"大块数据",服务器拿到 user_id 后,去缓存(Redis)里拿。这叫 "JWT做身份认证 + Redis做数据缓存" 的组合拳。
总结一句话
Session 是"去后台档案室查档案"(共享的是服务器空间),JWT是"随身携带经公证的护照复印件"(共享的是客户端手中的加密凭证)。
之所以能存在令牌里,是因为数字签名给了服务器"无需查询即可信任"的能力 。但请记住:存身份标识(ID)可以,存业务大对象(Object)是灾难。