直接回答:前端鉴权要解决的是"HTTP 本身不记人"这件事。传统服务端渲染的站点用 Cookie + Session 最省事,前后端分离并且要跨域调 API 时用 Token / JWT 更顺手;两者的差别不在"谁更安全",而在于状态放在服务端还是客户端——放服务端能随时踢人,放客户端要靠过期时间和黑名单兜底。
一切要从"HTTP 是无状态的"说起
HTTP 请求之间互相不认识,服务器处理完一个请求就把你忘了。所以"登录过"这件事必须由外部携带信息来证明,无非两种办法:
- 服务器给你一个编号,真正的状态存在服务端(Session);
- 服务器给你一张自证的凭据,状态内容就写在凭据里(Token / JWT)。
至于凭据放在哪、怎么传,浏览器给的标准答案就是 Cookie。
Cookie:浏览器最原始的凭证容器
Cookie 是服务器通过 Set-Cookie 写进浏览器的一小段键值对,之后每次请求浏览器都会自动带上。它的几个关键属性值得记牢:
| 属性 | 作用 |
|---|---|
Domain / Path |
哪些域名和路径下会带上这个 Cookie |
Expires / Max-Age |
会话 Cookie 还是持久 Cookie |
HttpOnly |
禁止 JS 读取,防 XSS 偷凭据 |
Secure |
只在 HTTPS 下发送 |
SameSite |
Lax / Strict / None,直接决定能不能防 CSRF |
两个常被忽略的点:
- Cookie 在跨域请求里默认不发,要用
withCredentials(前端)配合Access-Control-Allow-Credentials和不能是*的Access-Control-Allow-Origin(后端); SameSite=None必须同时带Secure,否则浏览器直接丢弃,这是"本地开发好好的、上线就登录不上"的经典原因。
Session:状态留在服务端
流程很短:登录成功后服务端生成一个 sessionId(通常是个随机字符串),把用户信息存进服务端的内存、Redis 或数据库,再把 sessionId 写进 Cookie。
优点:
- 用户信息在服务端,改权限、强制下线、查看在线用户都容易;
- 浏览器自动携带,前端几乎不用写代码。
代价:
- 服务端要有存储,多实例部署得做共享(所以常见组合是"Session 存 Redis");
- 移动端、第三方调用不方便——不是浏览器就没有 Cookie;
- 跨域场景要额外处理凭证和 CORS。
Token:把凭据给客户端保管
Token 的思路是把凭据交给客户端,请求时放在 Authorization 头里:
1 | Authorization: Bearer <token> |
好处是不再依赖 Cookie,所以 App、小程序、第三方 API 调用都能用同一套;坏处是浏览器不会自动带,也没有自动过期,全得自己管。
JWT:Token 的一种具体格式
JWT 不是"另一种鉴权方案",它只是把 Token 定义成三段式、能被服务端验签的字符串:
1 | header.payload.signature |
header:算法和类型;payload:真正的内容(用户 id、过期时间exp、签发者iss等);signature:前两段加上密钥算出来的签名。
最需要记住的三句话:
- payload 只是 Base64 编码,不是加密。 任何人都能解开看到内容,所以里面不能放敏感信息;
- 签名保证的是"没被篡改",不是"不可解读"。 服务端用同一把密钥重新算一遍签名就能验证真伪;
- JWT 一旦签发,在过期前一直有效。 想立刻作废只能靠额外的黑名单或者换密钥,所以
exp不要设太短也不好设太长——通常配一个有效期短的 access token 加一个有效期长的 refresh token。
refresh token:怎么让用户不天天登录
典型做法是发两个凭据:
- access token:有效期几分钟到几小时,请求业务接口用;
- refresh token:有效期几天到几个月,只能用来换新的 access token。
access token 过期时,前端拿 refresh token 去换一对新的。这样即使 access token 被截获,窗口期也很短;refresh token 可以存在服务端并支持主动失效,等于把"踢人"的能力补回来了。
前端实现上要注意:并发请求同时触发续期时要做串行处理,否则会拿着同一个 refresh token 换好几次,有些实现会把旧 token 直接作废,导致用户被挤下线。
单点登录:分两种难度
一级域名相同:共享 Cookie 就够了
如果站点是 a.example.com 和 b.example.com,把 Cookie 的 Domain 设成 .example.com,两个站点就能共享同一个会话 Cookie,登录态天然打通。这也是最简单的"看起来能单点登录"的做法。
一级域名不同:需要独立的认证中心
域名都不同,Cookie 共享不了,只能有一个独立的认证服务,主流有三条路:
- CAS:未登录访问业务系统 → 重定向到 CAS → 登录后在地址栏带一个
ticket跳回业务系统 → 业务系统拿 ticket 到 CAS 后台换取用户信息; - OAuth 2.0:本质是授权协议,常用于第三方登录,流程是授权码换 access token,再拿 token 取用户信息;
- OIDC:在 OAuth 2.0 之上补了身份层,返回
id_token(就是一个 JWT),现在新项目大多直接选它。
不管哪种,业务系统都要有一份本地会话,否则每个请求都去认证中心校验一次,认证中心会成为瓶颈和单点故障。
怎么选
| 场景 | 建议 |
|---|---|
| 传统的服务端渲染站点 | Cookie + Session(存 Redis),最省事 |
| 前后端分离、同域部署 | Cookie + Session 也可以,不必为了"潮"上 JWT |
| 前后端分离、跨域,或者要给 App / 第三方调用 | Token + JWT,配 refresh token |
| 多个系统要做统一登录 | 独立认证中心 + OIDC,业务系统保留本地会话 |
最后三条安全底线,跟选哪种无关:
- 凭据放 Cookie 就加
HttpOnly+Secure+ 合适的SameSite,能挡掉大半 XSS 窃取和 CSRF; - 用 localStorage 存 token 时,必须自己防 XSS,因为 JS 能读到的东西 XSS 也能读到;
- 鉴权一律在服务端做,前端路由守卫只是体验优化,不是安全措施。
本文整理自我自己早先收集的一份鉴权资料,按自己的理解重新组织并补充了实践中的注意点,文字由 AI 协助改写后经我复核。细节以各协议的正式规范和所用框架的文档为准。
这篇笔记整理自我自己的实践记录,如果做法有出入,或者你踩过别的坑,欢迎到留言板一起聊聊。