前端登录必懂:JWT vs Session 全解析(含实战代码+避坑指南)
做前端开发,登录功能是绕不开的核心模块,而 JWT 和 Session 作为两种最主流的登录认证机制,几乎是前端面试和项目开发的“必考题”。
很多新手会困惑:两者到底有什么区别?项目里该怎么选?为什么现在大部分前后端分离项目都用 JWT?
今天就从「定义→原理→实战→优缺点→避坑」,一步步把这两种机制讲透,既有理论又有可直接复用的代码,新手也能轻松看懂、快速上手。
一、先明确核心:两种机制的本质区别
不管是 JWT 还是 Session,核心目的都是「确认用户身份,维持登录状态」,但两者的实现思路完全相反:
- Session:服务器存状态,客户端只存一个“身份编号”(SessionId)
- JWT:客户端存状态,服务器不存任何用户信息,只负责验证
一句话总结:Session 是“服务器记账”,JWT 是“客户端带身份证”。下面我们逐个拆解细节。
二、JWT 详解:前端主流的无状态认证方案
2.1 什么是 JWT?
JWT(JSON Web Token),是一种轻量级、自包含、跨语言的令牌(Token),用于在网络应用中安全传递身份信息,核心作用是「用户认证」和「信息交换」。
简单理解:用户登录成功后,后端会给前端发一张“加密身份证”(JWT),前端每次请求接口时都带着这张“身份证”,后端通过验证“身份证”的合法性,就能认出用户,无需反复查询数据库。
2.2 JWT 核心登录流程(必记)
这是面试高频考点,也是项目实战的核心逻辑,用流程图+文字拆解,一眼看懂:
拆解成步骤,更清晰:
- 用户在前端输入登录信息(如用户名+密码),发起登录请求;
- 后端接收请求,验证用户名密码的合法性;
- 验证通过后,后端生成 JWT 令牌,返回给前端;
- 前端将 JWT 存储在 localStorage/sessionStorage/Cookie 中;
- 后续前端发起任何需要登录权限的请求时,都在请求头中携带 JWT;
- 后端接收请求后,验证 JWT 的签名和过期时间,合法则放行并返回数据,不合法则拒绝(如返回 401 未授权)。
核心亮点:服务器不存储任何用户状态,所有身份信息都加密在 JWT 中,减轻服务器压力,适配分布式、微服务架构。
2.3 JWT 结构:三段式字符串(面试必背)
JWT 不是加密字符串,而是一段用 “.” 分隔的明文+签名组合,共三部分:Header.Payload.Signature,每一部分都有明确作用,缺一不可。
① Header(头部)
作用:描述 JWT 的「加密算法」和「令牌类型」,格式为 JSON,然后通过 Base64URL 编码(注意:是 Base64URL,不是普通 Base64,避免特殊字符影响传输)。
{
"alg": "HS256", // 签名算法:常用 HMAC-SHA256(对称加密),也可用 RS256(非对称加密)
"typ": "JWT" // 令牌类型,固定为 JWT
}
② Payload(负载/数据)
作用:存放需要传递的用户身份信息(如用户 ID、用户名、角色),格式也是 JSON,同样通过 Base64URL 编码。
重点避坑:Payload 只是「编码」,不是「加密」!任何人拿到 JWT 后,都能通过 Base64URL 解码看到里面的内容,所以 绝对不能存放密码、密钥等敏感信息!
常用标准字段(推荐使用,规范且通用):
- iss:签发人(如后端服务域名);
- exp:过期时间(必填,时间戳格式,避免 Token 永久有效);
- sub:主题(通常是用户 ID);
- iat:签发时间。
{
"userId": 10086, // 用户唯一标识(核心信息)
"username": "zhangsan", // 用户名(非敏感信息)
"role": "admin", // 用户角色(用于权限控制)
"exp": 1743206400 // 过期时间(必填,这里是示例时间戳)
}
③ Signature(签名)
作用:防止 JWT 被篡改,是 JWT 安全的核心,由后端通过固定算法生成,只有服务器知道密钥。
生成公式(以 HS256 算法为例):
Signature = HMACSHA256(
Base64URL(Header) + "." + Base64URL(Payload),
服务器密钥(secret)
)
原理:后端验证时,会重新计算签名,如果计算结果和 JWT 中的 Signature 不一致,说明 Token 被篡改,直接拒绝请求。
注意:服务器密钥(secret)必须复杂且保密,绝对不能硬编码在前端,否则会导致 JWT 被伪造,整个系统都不安全。
2.4 前端实战:JWT 的具体使用(可直接复用)
结合前端常用的 Fetch 和 Axios,整理了最实用的代码片段,直接复制到项目中即可使用。
① 登录成功后存储 Token
// 登录请求(Fetch 版本)
async function login(username, password) {
try {
const res = await fetch("/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json", // 必须指定请求头
},
body: JSON.stringify({ username, password }),
});
const data = await res.json();
// 登录成功,存储 JWT(推荐 localStorage,适配多页面共享)
if (data.token) {
localStorage.setItem("token", data.token);
// 可选:存储 refreshToken(用于无感刷新)
if (data.refreshToken) {
localStorage.setItem("refreshToken", data.refreshToken);
}
// 跳转到首页
window.location.href = "/home";
}
} catch (err) {
console.error("登录失败:", err);
alert("用户名或密码错误");
}
}
② 请求拦截器:自动携带 Token
实际项目中,我们不会每次请求都手动添加 Token,而是用 Axios 拦截器统一处理,更高效。
// 引入 Axios(需先安装:npm install axios)
import axios from "axios";
// 创建 Axios 实例
const request = axios.create({
baseURL: "/api",
timeout: 5000,
});
// 请求拦截器:每次请求自动携带 Token
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) {
// 规范格式:Bearer + 空格 + Token(后端需对应解析)
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(err) => {
return Promise.reject(err);
}
);
// 响应拦截器:处理 Token 过期(可选,优化用户体验)
request.interceptors.response.use(
(res) => res.data,
(err) => {
// Token 过期(后端返回 401)
if (err.response?.status === 401) {
// 清除过期 Token,跳转到登录页
localStorage.removeItem("token");
localStorage.removeItem("refreshToken");
window.location.href = "/login";
alert("登录已过期,请重新登录");
}
return Promise.reject(err);
}
);
export default request;
③ 退出登录:清除 Token
function logout() {
// 清除存储的 Token
localStorage.removeItem("token");
localStorage.removeItem("refreshToken");
// 跳转到登录页
window.location.href = "/login";
// 可选:清除其他用户相关信息(如用户信息缓存)
localStorage.removeItem("userInfo");
}
三、Session 详解:传统项目的有状态认证方案
3.1 什么是 Session?
Session(会话),是服务器为每个访问的浏览器用户,单独创建的一块「存储区域」,用于保存用户的登录状态、临时信息等。
简单理解:用户第一次访问服务器时,服务器会给用户建一个“专属档案”(Session),然后给这个档案分配一个唯一编号(SessionId),再把编号发给浏览器;后续浏览器每次请求,都带着这个编号,服务器通过编号找到对应的“档案”,就能认出用户。
核心亮点:用户真实数据都存在服务器,客户端只存一个 SessionId,安全性更高(相对 JWT 而言,不易被篡改)。
3.2 Session 核心登录流程
- 用户第一次访问服务器(或发起登录请求);
- 服务器验证用户身份(如用户名密码),验证通过后,创建一个 Session,生成唯一的 SessionId;
- 服务器通过
Set-Cookie响应头,将 SessionId 发送给浏览器; - 浏览器自动保存这个 Cookie(Cookie 名称通常是
JSESSIONID或connect.sid); - 之后浏览器每次发起请求,都会自动携带这个 Cookie(SessionId);
- 服务器接收请求后,通过 Cookie 中的 SessionId,找到对应的 Session,确认用户身份,合法则放行。
3.3 Session 的存储位置与结构
① 存储位置
- 默认:服务器内存(开发环境常用,缺点:服务器重启后 Session 丢失);
- 生产环境:通常存在 Redis、MongoDB 或数据库中(持久化存储,支持分布式共享)。
② 结构(简单理解)
服务器中的 Session 本质是一个键值对集合,键是 SessionId,值是用户相关信息,大致结构如下:
sessions: {
"abc123": { userId: 1001, username: "zhangsan", loginAt: 1690000000, expires: 1690086400 },
"def456": { userId: 1002, username: "lisi", loginAt: 1690000100, expires: 1690086500 },
// 更多用户 Session...
}
浏览器端只存储 SessionId(如 abc123),不存储任何用户真实信息。
3.4 Session 的关键依赖:Cookie
Session 强依赖 Cookie,因为 SessionId 是通过 Cookie 传递的——如果用户浏览器禁用了 Cookie,Session 将无法正常工作。
补充:早期有“URL 重写”方案(将 SessionId 拼在 URL 后面,如 http://xxx.com?sid=abc123),但这种方式不安全(URL 可能被泄露)、不优雅,现在几乎不用。
3.5 Session 安全机制(面试高频)
Session 的安全性,主要依赖 Cookie 的安全属性,常用配置如下(后端设置):
- HttpOnly:禁止 JS 读取 Cookie,防止 XSS 攻击(避免 SessionId 被恶意脚本窃取);
- Secure:只在 HTTPS 协议下传输 Cookie,防止 HTTP 协议下被截获;
- SameSite:限制 Cookie 跨域传递,防止 CSRF 攻击;
- 过期时间:服务器会给 Session 设置过期时间,超时后自动销毁,避免 Session 长期有效。
四、JWT vs Session 终极对比(项目选型必看)
很多新手纠结“该用 JWT 还是 Session”,其实核心看项目架构——前后端分离、微服务、移动端项目,优先用 JWT;传统单体项目、管理后台,可用 Session。
下面用表格对比两者的核心差异,面试直接背就能用:
| 对比维度 | JWT | Session |
|---|---|---|
| 状态存储 | 客户端存储,无状态 | 服务器存储,有状态 |
| 数据位置 | 客户端(JWT 中) | 服务器(内存/Redis/数据库) |
| 传输方式 | 请求头携带(Authorization: Bearer Token) | Cookie 自动携带(SessionId) |
| 跨域支持 | 支持,无跨域限制 | 不友好,Cookie 跨域限制严格 |
| 分布式/集群支持 | 天然支持,无需额外配置 | 需做 Session 共享(如 Redis),配置复杂 |
| 主动销毁 | 无法主动销毁,只能等过期 | 支持,后端可直接删除 Session |
| 安全性 | 依赖签名和 HTTPS,Payload 可解码 | 较高,数据在服务器,依赖 Cookie 安全属性 |
| 适用场景 | 前后端分离、微服务、APP、小程序 | 传统单体项目、管理后台、内部系统 |
五、JWT 与 Session 优缺点详解(避坑重点)
5.1 JWT 优缺点
优点
- 无状态:服务器不存储会话信息,减轻服务器压力,支持高并发;
- 跨域友好:不依赖 Cookie,支持前后端分离、跨域项目;
- 通用性强:支持 Web、APP、小程序等所有前端场景;
- 自包含:一次性携带所有需要的用户信息,无需反复查数据库。
缺点
- 无法主动作废:一旦签发,在过期前一直有效(解决方案:用短过期时间 + Refresh Token 无感刷新);
- Payload 可解码:不能存放密码、密钥等敏感信息;
- 体积较大:比 Cookie 大,每次请求都携带,会占用少量流量;
- 密钥风险:一旦服务器密钥泄露,攻击者可伪造 JWT,整个系统不安全。
JWT 安全优化方案(必做)
- 设置短过期时间(如 1-2 小时),配合 Refresh Token 实现无感刷新;
- 使用 HTTPS 协议,防止 JWT 在传输过程中被截获;
- Payload 只存非敏感信息(如 userId、角色),绝对不存密码;
- 服务器密钥要复杂(如随机字符串+加密),绝不泄露、不硬编码在前端;
- 前端存储:优先存在 HttpOnly Cookie 中(防 XSS),而非 localStorage。
5.2 Session 优缺点
优点
- 安全性高:真实用户数据在服务器,客户端只存 SessionId,不易被篡改;
- 可主动销毁:支持退出登录、强制下线、踢人等场景,灵活可控;
- 天然支持状态管理:适合需要维护用户临时状态的场景(如购物车);
- 前端无需额外操作:浏览器自动携带 Cookie,开发成本低。
缺点
- 服务器有状态:用户量增大时,服务器存储压力增大,影响性能;
- 分布式/集群困难:多服务器部署时,Session 无法共享,需额外配置 Redis 等中间件;
- 跨域麻烦:Cookie 跨域限制严格,不适合前后端分离、跨域项目;
- 依赖 Cookie:用户禁用 Cookie 后,Session 无法使用。
六、常见误区澄清(面试避坑)
- 误区 1:用了 JWT 就不能用 Session? 答:不是绝对,但没必要。两者解决的是同一个问题(登录认证),混用会增加复杂度,项目中一般二选一。
- 误区 2:JWT 比 Session 更安全? 答:不一定。JWT 若密钥泄露、Payload 存敏感信息,安全性会很差;Session 配置好 Cookie 安全属性,安全性更高。
- 误区 3:JWT 必须存在 localStorage 中? 答:不是。更安全的方式是存在 HttpOnly Cookie 中,防止 XSS 攻击;localStorage 只是使用更方便。
- 误区 4:Session 只能存在服务器内存中? 答:不是。生产环境中,为了持久化和分布式共享,通常存在 Redis 中。
七、总结:项目中该怎么选?
一句话选型建议,直接套用:
- 如果是 前后端分离、微服务、APP、小程序 → 优先用 JWT(无状态、跨域友好);
- 如果是 传统单体项目、管理后台、内部系统 → 可用 Session(开发简单、安全性高);
- 如果需要 强制下线、踢人 等场景 → 优先用 Session(可主动销毁);
- 如果追求 高并发、分布式部署 → 优先用 JWT。
最后补充:现在前端主流项目(尤其是前后端分离),几乎都用 JWT,所以掌握 JWT 的原理、实战和安全优化,是前端开发者的必备技能。
如果觉得本文对你有帮助,欢迎点赞、收藏,关注我,后续分享更多前端实战干货和面试技巧~