身份认证基础:Token机制、Session与Cookie、JWT结构解析
聊身份防篡改之前,得先把地基打牢。你想想看,系统怎么知道「你是谁」?靠的就是身份认证。而身份认证的核心,说白了就是三个东西:凭证怎么存、凭证怎么传、凭证怎么验。
我这些年做安全审计,见过太多因为基础概念混淆导致的漏洞。有人把Session和Token当成一回事,有人觉得JWT就是加密的,还有人直接把用户ID塞Cookie里……嗯,这些坑我都踩过。今天咱们就把这三个东西彻底捋清楚。
2.1 从Cookie到Session:有状态的认证时代
早期的Web应用,认证逻辑很简单。你输入用户名密码,服务器验证通过,就在响应头里Set-Cookie,浏览器自动存下来。下次请求带上这个Cookie,服务器就知道你是谁了。
但这里有个问题——Cookie是存在客户端的。用户完全可以篡改自己浏览器里的Cookie值。比如把user_id=123改成user_id=1,如果服务端没校验,你就变成管理员了。
所以后来引入了Session。服务端不再把用户数据直接塞Cookie里,而是只存一个随机生成的Session ID。真正的用户信息存在服务器内存或Redis里。
核心区别一句话:
- Cookie是客户端存储,数据在用户手里,容易被篡改。
- Session是服务端存储,客户端只拿一把「钥匙」(Session ID),数据安全由服务端保障。
我在一个老项目中见过这样的代码:直接把用户角色存在Cookie里,前端传过来就信。结果测试同学用浏览器开发者工具改了role字段,直接拿到了管理员权限。那次之后,我定了个规矩——任何客户端传来的身份信息,都必须视为不可信。
2.2 Token机制:无状态认证的崛起
Session方案有个硬伤:服务端要存状态。微服务架构下,每个服务都得去中心化的Session存储查数据,性能开销大,扩展也麻烦。
Token机制就是来解决这个问题的。Token是一段自包含的凭证,服务端不需要存任何会话信息。你拿着Token来,我验一下签名就知道你是谁、有什么权限。
我习惯把Token比作「通行证」。你进游乐园买票,工作人员给你手上盖个荧光章。下次你玩过山车,他拿紫光灯照一下,看到章就放行。他不需要查数据库里有没有你的记录,因为章本身就能证明你买过票。
Token的核心优势:
- 无状态:服务端不需要存储会话,天然适合分布式系统。
- 跨域友好:Token可以放在Authorization头里,不受Cookie同源策略限制。
- 自包含:用户信息、权限、过期时间都编码在Token里,一次验签全搞定。
但Token也不是银弹。我记得有一次帮客户做安全评估,他们的Token过期时间设成了30天,而且没有刷新机制。结果一个员工的Token泄露了,攻击者拿着这个Token用了整整一个月……所以后面我会专门讲安全刷新,这是防篡改的关键一环。
2.3 JWT结构解析:拆开看看里面是什么
市面上最常见的Token格式就是JWT(JSON Web Token)。它长这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
三个部分用点号分隔。咱们一个一个拆。
2.3.1 Header(头部)
第一部分是Header,Base64编码后的JSON。里面声明了签名算法和Token类型。
{
"alg": "HS256",
"typ": "JWT"
}
alg字段指定签名算法,常见的有HS256(对称)、RS256(非对称)。typ字段固定是JWT。
我曾经踩过的坑:有些库默认支持alg: none,即不签名。攻击者把alg改成none,服务端如果没校验签名,就能伪造任意JWT。所以生产环境一定要禁用none算法,并且强制校验签名。
2.3.2 Payload(载荷)
第二部分是Payload,存放实际数据。标准字段叫Claims,分三类:
| 类型 | 字段 | 说明 |
|---|---|---|
| 注册声明 | iss, sub, aud, exp, nbf, iat, jti | 标准定义,推荐使用 |
| 公共声明 | 自定义字段(需避免冲突) | 在IANA注册或使用命名空间 |
| 私有声明 | 自定义字段(双方约定) | 不要放敏感信息 |
常用的注册声明:
- iss(Issuer):签发者,比如你的应用名称。
- sub(Subject):主题,通常是用户ID。
- exp(Expiration Time):过期时间,UNIX时间戳。
- iat(Issued At):签发时间。
- jti(JWT ID):唯一标识,防重放攻击。
重要提醒:Payload只是Base64编码,不是加密的。任何人都可以解码看到里面的内容。所以绝对不要放密码、手机号、身份证号等敏感信息。我见过有人把用户密码哈希放JWT里,这跟把密码写在脑门上没区别。
2.3.3 Signature(签名)
第三部分是签名,用来验证Token有没有被篡改。生成过程:
signature = HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
服务端用密钥对前两部分做签名,客户端拿到JWT后,服务端再用同样的密钥重新算一遍签名。如果一致,说明数据没被改过。
这里有个关键点:签名只能防篡改,不能防窥探。因为Payload是明文编码的,谁都能读。如果你需要数据保密,得用JWE(JSON Web Encryption),那是另一个话题了。
2.4 一张图看懂JWT认证流程
下面这张图展示了JWT从签发到验证的完整链路:
2.5 三者的对比与选型建议
最后做个总结对比,方便你根据场景选型:
| 特性 | Cookie + Session | Token(含JWT) |
|---|---|---|
| 状态存储 | 服务端存储 | 客户端存储(自包含) |
| 扩展性 | 差,需要共享Session存储 | 好,天然支持分布式 |
| 跨域支持 | 受限,受同源策略影响 | 好,通过Header传递 |
| 安全性 | Session ID泄露风险 | Token泄露风险,需配合刷新机制 |
| 性能 | 每次请求查存储 | 验签即可,但签名计算有开销 |
| 典型场景 | 传统单体应用 | 微服务、移动端、单页应用 |
我的个人建议:
- 如果是传统服务端渲染的项目,用户量不大,Session方案够用,别过度设计。
- 如果是前后端分离、微服务架构,或者有移动端,优先选JWT。
- 不管选哪种,HTTPS是底线。明文传输的Token和Session ID,跟裸奔没区别。