1、令牌基础概念:什么是用户令牌、令牌的组成结构、令牌与 Session 的区别
各位同学好,我是老张。今天咱们开始聊用户令牌在设备安全中的运用。第一节课,我打算先把地基打牢——什么是用户令牌,它长什么样,跟传统的 Session 到底有什么不同。
说实话,我见过不少团队,一上来就撸 JWT,结果 Token 怎么签的、怎么验的、过期了怎么办,全是一笔糊涂账。最后线上出了安全问题,还得回头补基础。所以咱们别急,先把概念吃透。
1.1 什么是用户令牌
用户令牌,说白了就是一张「数字通行证」。你登录成功后,服务器给你发一个凭证,你拿着它去访问资源,服务器一看令牌有效,就放行。
我习惯把它比作酒店房卡。你办入住时前台给你一张房卡,这张卡里写好了你的房号、入住时间、权限(比如能不能去健身房)。你进房间、去餐厅、刷电梯,都靠这张卡。服务器不需要每次都查你的入住登记表,只要验一下卡里的签名是真的就行。
核心要点:令牌是客户端持有的、可被服务端独立验证的身份凭证。它不依赖服务端存储会话状态。
令牌有几个关键特征:
- 自包含:用户信息、权限、有效期都写在令牌里
- 可验证:服务端通过签名或加密确保令牌未被篡改
- 无状态:服务端不需要保存令牌,验完就丢
- 有生命周期:过期就失效,不能无限期使用
你想想看,如果每次请求都要去数据库查一遍用户信息,那高并发场景下服务器早扛不住了。令牌就是用来解决这个问题的。
1.2 令牌的组成结构
现在最常用的令牌格式是 JWT(JSON Web Token)。我拿一个真实的 JWT 来拆解:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
它由三部分组成,用点号分隔。我一个个说。
Header(头部)
头部是个 JSON 对象,描述令牌的元数据。最常见的就是签名算法和令牌类型。
{
"alg": "HS256",
"typ": "JWT"
}
alg 字段告诉服务端:这个令牌是用什么算法签的。HS256 是 HMAC-SHA256,RS256 是 RSA-SHA256。我建议生产环境优先用 RS256,因为公钥私钥分离,更安全。我在项目中遇到过有人用 none 算法,结果被攻击者伪造令牌直接提权……嗯,这是个血泪教训。
Payload(载荷)
载荷是令牌的核心,里面放着你想要传递的声明(claims)。分三类:
- 注册声明:标准字段,比如
iss(签发者)、sub(主题)、exp(过期时间)、iat(签发时间) - 公共声明:自定义字段,但建议在 IANA 注册或使用命名空间避免冲突
- 私有声明:你和你的服务端约定的字段,比如
role、user_id
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622,
"role": "admin"
}
注意:Payload 是 Base64 编码,不是加密!任何人拿到令牌都能解码看到里面的内容。千万不要把密码、信用卡号等敏感信息放进去。我曾经见过有人把数据库连接字符串写在 JWT 里……那场面,不忍直视。
Signature(签名)
签名是令牌的防伪标识。它的生成过程是这样的:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
服务端用密钥对头部和载荷进行签名,然后把签名附在最后。客户端拿到令牌后,服务端用同样的密钥重新算一遍签名,如果一致,说明令牌没被改过。
我习惯把签名比作信封上的火漆印章。你拆开信封再封回去,印章肯定对不上。签名也是一样,只要有人改了 header 或 payload 里的任何一个字符,签名验证就会失败。
小技巧:签名用的密钥一定要妥善保管。我建议用环境变量或密钥管理服务(KMS)来存储,千万别硬编码在代码里。GitHub 上搜一下 "jwt secret hardcoded",你能看到无数血案。
1.3 令牌与 Session 的区别
这个问题我面试时必问。很多人答不上来,或者答得模棱两可。咱们直接上表格对比:
| 对比维度 | Session | Token(JWT) |
|---|---|---|
| 存储位置 | 服务端内存/Redis/数据库 | 客户端(浏览器、App) |
| 验证方式 | 查 Session ID 对应的存储 | 验签名,不查存储 |
| 扩展性 | 水平扩展需共享 Session 存储 | 天然支持水平扩展 |
| 跨域支持 | 受 Cookie 同源策略限制 | 可通过 HTTP Header 自由传递 |
| 安全性 | Session ID 泄露风险 | Token 泄露风险,但可设置短过期 |
| 服务端状态 | 有状态 | 无状态 |
| 主动失效 | 直接删 Session 即可 | 需维护黑名单或缩短过期时间 |
我举个例子你就明白了。Session 就像你去图书馆借书,管理员在笔记本上记下「张三借了《算法导论》」。下次你来还书,管理员翻笔记本找到你的记录。如果管理员换人了,或者图书馆开了分馆,笔记本还得共享。
Token 呢,就像你办了一张会员卡,卡上写着「张三,VIP,有效期到2025年」。你去任何一家分店,店员只要验一下卡上的防伪标识,就知道你是不是 VIP,不需要打电话回总店查。
所以什么时候用 Session,什么时候用 Token?
- Session 适合:传统服务端渲染的 Web 应用,用户量不大,服务实例少
- Token 适合:分布式系统、微服务、移动端、单页应用(SPA)、需要跨域的场景
我个人习惯是:新项目一律用 Token。原因很简单——扩展性好,前后端分离是趋势,而且 Token 的生态已经很成熟了。但如果你做的是内部管理系统,用户就几百人,用 Session 也没毛病,别为了用 Token 而用 Token。
1.4 令牌的完整生命周期
我画了一张图,帮你把令牌从出生到销毁的全过程串起来:
这张图把令牌的六个关键步骤串起来了。你注意看,服务端只在第②步和第⑤步参与,中间客户端怎么存、怎么传,服务端一概不管。这就是无状态的核心——服务端不需要维护会话表。
1.5 避坑指南
最后,我分享几个我在项目中踩过的坑:
- 令牌别存太大:Payload 里别塞太多东西。我见过有人把整个用户对象扔进去,一个 JWT 好几 KB,每次请求都带着,网络开销不小。只放必要字段,比如 user_id、role、exp 就够了。
- 签名算法别用 HS256 做跨服务:HS256 是对称算法,所有服务共享同一个密钥。一旦一个服务被攻破,密钥就泄露了。微服务架构下我建议用 RS256,每个服务只持有公钥,私钥由认证中心保管。
- 别忘了校验 exp:我遇到过有人签了 JWT 但没设过期时间,结果令牌永久有效。这跟把家门钥匙留给陌生人有什么区别?
- 黑名单机制要备着:虽然 Token 是无状态的,但用户注销、密码修改时,你还是得让旧令牌失效。我习惯在 Redis 里维护一个短期的黑名单,配合短过期时间使用。
我的建议:刚开始用 Token 时,先别搞太复杂。Access Token 设 15 分钟过期,Refresh Token 设 7 天。等跑顺了再考虑更细粒度的权限控制、令牌轮换等高级特性。步子迈大了,容易扯着蛋。