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 令牌的完整生命周期

我画了一张图,帮你把令牌从出生到销毁的全过程串起来:

用户令牌生命周期流程图 用户令牌生命周期 ① 用户登录 ② 服务端签发令牌 ③ 客户端存储令牌 ④ 请求时携带令牌 ⑤ 服务端验证签名 ⑥ 返回资源 / 拒绝访问 关键说明 • 令牌由服务端签发 • 客户端负责存储和携带 • 服务端只验签名,不存状态 常见存储位置 • localStorage / sessionStorage • Cookie(HttpOnly + Secure) 验证失败原因 • 签名不匹配(被篡改) • 令牌已过期(exp)

这张图把令牌的六个关键步骤串起来了。你注意看,服务端只在第②步和第⑤步参与,中间客户端怎么存、怎么传,服务端一概不管。这就是无状态的核心——服务端不需要维护会话表。

1.5 避坑指南

最后,我分享几个我在项目中踩过的坑:

  • 令牌别存太大:Payload 里别塞太多东西。我见过有人把整个用户对象扔进去,一个 JWT 好几 KB,每次请求都带着,网络开销不小。只放必要字段,比如 user_id、role、exp 就够了。
  • 签名算法别用 HS256 做跨服务:HS256 是对称算法,所有服务共享同一个密钥。一旦一个服务被攻破,密钥就泄露了。微服务架构下我建议用 RS256,每个服务只持有公钥,私钥由认证中心保管。
  • 别忘了校验 exp:我遇到过有人签了 JWT 但没设过期时间,结果令牌永久有效。这跟把家门钥匙留给陌生人有什么区别?
  • 黑名单机制要备着:虽然 Token 是无状态的,但用户注销、密码修改时,你还是得让旧令牌失效。我习惯在 Redis 里维护一个短期的黑名单,配合短过期时间使用。

我的建议:刚开始用 Token 时,先别搞太复杂。Access Token 设 15 分钟过期,Refresh Token 设 7 天。等跑顺了再考虑更细粒度的权限控制、令牌轮换等高级特性。步子迈大了,容易扯着蛋。