上一章我们聊了 Cookie + Session 如何统治 Web 黄金十年(1998–2012)。但进入 2010 年代,移动 App、SPA(Single Page Application)、前后端分离、微服务这些新事物像潮水一样涌来,把 Session 模式逼到了墙角。
于是,Token 开始登场。从最原始的“随便传个字符串”到 2015 年正式成为 RFC 的 JSON Web Token (JWT),前端第一次真正开始“接管”登录状态。这是一个从混乱到标准化的黎明期,也是前端工程师从“表单提交仔”变成“状态管理大师”的起点。
1. Session 模式在 2012–2014 年暴露的致命伤
- 跨域携带麻烦:浏览器默认不跨域带 Cookie(CORS + withCredentials 配对很麻烦),移动端 / 原生 App 更不可能靠 Cookie
- 服务器有状态:Session 存在内存/文件/Redis,集群时要么粘性 session(负载均衡痛苦),要么全量 Session 共享(Redis 成为瓶颈)
- 移动 + API 优先:iOS/Android App 需要纯 JSON API,Cookie 不是首选
- SPA 崛起:AngularJS(2010–2012 爆火)、Backbone、Ember 等框架让页面不刷新,前端必须自己管理“已登录”状态
工程师们开始尝试最原始的方案:简单 Token。
2. 简单 Token 时代(2012–2014):随便传个字符串
典型做法(伪代码):
后端登录成功后:
// PHP / Node / Java 随便写
const token = uuid.v4(); // 或 md5(userId + salt + timestamp)
redis.set(`token:${token}`, JSON.stringify({ userId: 123, expire: ts + 7*86400 }), 'EX', 7*86400);
res.json({ token });
前端(jQuery / AngularJS 早期):
// 存 localStorage
localStorage.setItem('auth_token', response.token);
// 每个请求手动加 header
$.ajax({
url: '/api/user',
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('auth_token') }
});
优点:简单、无状态(校验时查 Redis 就行)、跨域友好。
缺点:
- 完全不透明:前端不知道过期时间、权限等
- 无法防篡改(明文或简单 hash)
- 登出/强制下线?必须后端维护黑名单
- 安全性低:泄露即永久有效(除非短有效期 + 刷新机制)
这个阶段很多中小项目、内部系统、早期移动端 API 都这么干。代表:2012–2014 年的大量 RESTful API 项目。
3. JWT 横空出世:2010 年草案 → 2015 年 RFC 7519
JWT 的故事其实从 2010 年底就开始了:
- 2010 年 12 月:第一个 Internet-Draft 出现(draft-jones-json-web-token-01)
- 2011–2014:多次迭代,主要是 Mike Jones(微软)、John Bradley、Nat Sakimura 等人在 IETF OAuth 工作组推动
- 2013 年:Auth0 等公司开始大力宣传和实现早期草案版本(jwt.io 网站雏形出现)
- 2015 年 5 月:正式发布 RFC 7519(JSON Web Token),同时配套 RFC 7515 (JWS)、7516 (JWE)、7517 (JWK)、7518 (JWA)
JWT 结构一目了然(三段 Base64Url + . 分隔):
header.payload.signature
- Header:算法、类型(通常
{"alg":"HS256","typ":"JWT"}) - Payload:Claims(标准字段如 sub, iat, exp, iss, aud + 自定义字段)
- Signature:用密钥对 header+payload 签名(HS256 对称、RS256 非对称)
典型 JWT 示例(jwt.io 上调试神器):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
2015 年后,jwt.io 网站上线 + 大量库爆发(jsonwebtoken Node、jjwt Java、PyJWT Python、angular-jwt 等),JWT 迅速成为事实标准。
4. JWT 为什么在 2015–2018 年爆火?(SPA + 微服务完美契合)
- 无状态:服务端不存 Session,校验只看签名 + exp + iss/aud → 天生适合微服务、Serverless、CDN 边缘验证
- 自包含:Payload 带用户信息、权限、过期时间,前端可直接解析(不需每次问后端“你是谁”)
- 跨域 / 多端友好:Bearer Token 放 Authorization header,移动端、Postman、浏览器全通用
- 前端存储灵活:localStorage / sessionStorage / memory / cookie(httpOnly 也可,但少用)
- 生态爆发:2015–2016 年 Angular 1.x + angular-jwt、React + redux、Vue 早期项目大量采用
典型前端使用(2016–2018 年主流写法):
// axios 拦截器(最经典)
import axios from 'axios';
axios.interceptors.request.use(config => {
const token = localStorage.getItem('jwt');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 登录后存储
function loginSuccess(res) {
localStorage.setItem('jwt', res.data.token);
// 或用 vuex / redux 存(推荐,方便管理)
}
5. 早期 JWT 的两大存储之争(2015–2018)
| 存储位置 | 优点 | 缺点 / 风险 | 主流选择时期 |
|---|---|---|---|
| localStorage | 简单、跨标签页共享、持久 | XSS 可窃取(document 可读) | 2015–2017 大量用 |
| sessionStorage | 标签页隔离、不持久 | 刷新丢失、仍 XSS 风险 | 少用 |
| HttpOnly Cookie | 防 XSS(JS 读不到)、浏览器自动带 | 跨域麻烦、CSRF 风险、SameSite 未普及 | 保守派 / ToB 系统 |
| 内存(vuex/redux) | 只在当前页面、结合 refresh token | 刷新丢失、需配合持久化插件 | 2017–2018 渐主流 |
结论:2015–2017 年 localStorage + JWT 是最流行组合,被吐槽最多的是 XSS 风险(但当时很多项目没太在意)。
6. 早期 JWT 的安全坑与教训(2015–2018 高发)
- alg: none 漏洞(2015 年发现,多库未过滤)
- RS256 公钥混淆攻击(2016–2017 爆出 alg confusion)
- 弱密钥 HS256(2017 前常见)
- 不校验 iss / aud / exp(很多项目只验签名)
- 永不过期或超长 exp(常见安全事故源)
缓解:用成熟库(jsonwebtoken v8+、auth0 的库)、强制 alg 白名单、短有效期 + refresh token。
7. 小结:2012–2018 是 Token 时代的“青春期”
- 2012–2014:简单 Token 试水,无标准、无库
- 2015:JWT RFC 诞生,jwt.io 上线,爆发起点
- 2016–2018:SPA + 前后端分离全面拥抱 JWT,成为中后台、移动 API、微服务标配
但 JWT 也不是银弹:
- 无法主动失效(除非黑名单 / 短有效期)
- Payload 可读(Base64 不是加密)
- XSS / 泄露风险永久存在