JWT还是Session?别再纠结了,看完这篇你就懂了

凌晨三点,我盯着电脑屏幕,光标在session: { strategy: "jwt" }和session: { strategy: "database" }之间来回闪烁。项目要上线了,可我还在纠结到底该用JWT还是Session。翻了一圈文档,看到的都是”各有优劣”、“根据场景选择”这种正确但没啥用的建议。
你可能也遇到过类似的困惑:做技术选型时,两个方案都看起来不错,但就是不知道哪个更适合自己的项目。说实话,我第一次遇到这个问题时也很懵,后来踩了不少坑才算搞明白。今天就和你聊聊JWT和Session这两种会话策略,看完你就知道该怎么选了。
先搞清楚它们是什么
说到JWT和Session,很多人都能背出定义,但真要解释清楚还挺难的。我喜欢用一个比喻:
Session就像健身房的会员卡。你办卡时,健身房在他们的系统里记录了你的会员信息。每次你去健身,只需要刷卡(提供一个Session ID),前台就能从系统里查到你是谁、会员到期时间、还剩多少次课程。所有重要信息都存在健身房的数据库里。
JWT就像一张身份证。你的所有信息(姓名、生日、住址等)都印在证件上。每次需要证明身份时,你就把身份证拿出来,对方看一眼就知道你是谁了,不需要再去什么系统里查。
这样一来,它们的工作方式就很清楚了:
Session方式:用户登录后,服务器生成一个Session ID,把用户信息存在数据库里,然后把Session ID通过Cookie发给浏览器。之后每次请求,浏览器带上这个Session ID,服务器就去数据库里查对应的用户信息。
JWT方式:用户登录后,服务器把用户信息打包成一个加密的token(JWT),直接发给浏览器。之后每次请求,浏览器带上这个JWT,服务器只需要验证这个token有效就行,不用查数据库。
JWT深度剖析
为什么JWT这么受欢迎?
老实说,JWT在开发者圈子里挺火的,尤其是做Serverless和微服务的团队。原因很简单:不用操心数据库。
我有个朋友做了个电商网站,用的是Vercel部署,选的就是JWT。他跟我说,用JWT最大的好处是”省事儿”——不用担心数据库连接数不够,不用操心Session表的性能,部署到边缘节点也没问题。用户登录一次,拿到JWT,之后所有请求都带着这个token,服务器验证一下签名就完事了。
JWT特别适合这几种场景:
1. Serverless / 边缘计算
如果你的项目部署在Vercel、Cloudflare Workers这些平台上,JWT简直是标配。这些平台的函数都是无状态的,每次请求可能在不同的服务器上处理。用Session的话,你还得搞个Redis来共享Session数据,麻烦。JWT就简单多了,token里带着所有信息,哪台服务器处理都一样。
2. 高并发场景
做过秒杀活动的人应该都懂,数据库查询是性能瓶颈。如果用Session,每个请求都要查一次数据库;用JWT的话,只需要在登录时查一次数据库,后续请求都是本地验证,快很多。
3. 微服务架构
当你有多个服务(比如用户服务、订单服务、支付服务)时,用JWT可以让不同服务之间共享认证信息。不需要每个服务都去查用户Session,也不用搞个统一的Session存储。
JWT的坑
听起来挺美好的,但JWT也有它的问题,而且有些坑还挺深的。
最大的坑:无法即时撤销
有次我们发现一个用户账号被盗了,想立即踢掉这个用户的登录状态。如果用的是Session,直接删数据库记录就完事了。但我们用的是JWT,尴尬了——JWT一旦发出去,在过期之前都是有效的,没法撤销。
你可能会说,那我搞个黑名单呗?可以是可以,但这就失去JWT无状态的优势了。每次请求都要查黑名单,这和查Session表有啥区别?
所以用JWT的话,一般会把过期时间设短一点,比如15分钟,然后搭配refresh token使用。这样就算token被盗,最多15分钟就失效了。
Cookie大小限制
JWT会把用户信息都编码到token里,如果你往JWT里塞太多东西(比如用户角色、权限、偏好设置等),token会变得很大。浏览器Cookie有4KB的限制,超了就存不下了。
有个教训:别把什么都往JWT里塞,只放最基础的信息就行,比如用户ID和过期时间。其他信息需要的时候再去查。
NextAuth.js JWT配置实战
如果你用Next.js,NextAuth.js(现在叫Auth.js)是最流行的认证方案。配置JWT其实很简单:
// auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30天
},
callbacks: {
async jwt({ token, user }) {
// 首次登录时,把user信息加到token里
if (user) {
token.id = user.id
token.role = user.role
}
return token
},
async session({ session, token }) {
// 把token里的信息暴露给客户端
if (token) {
session.user.id = token.id
session.user.role = token.role
}
return session
},
},
})几个注意事项:
一定要设置
NEXTAUTH_SECRET(Auth.js v5改名为AUTH_SECRET了)。可以用npx auth secret生成一个安全的密钥。这个密钥用来给JWT签名和加密,千万别泄露了。滚动会话:如果你想让用户活跃时自动续期,可以配置
updateAge:
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60,
updateAge: 24 * 60 * 60, // 每24小时刷新一次
}这样用户只要在24小时内有活动,Session就会自动续期,不会突然登出。
- 2025年的新变化:如果你要部署到Edge Runtime(比如middleware),需要把配置拆分:
// auth.config.ts - 可以在Edge运行
export default {
providers: [GitHub],
session: { strategy: "jwt" },
}
// auth.ts - 包含数据库操作,只在Node.js运行
import { PrismaAdapter } from "@auth/prisma-adapter"
import authConfig from "./auth.config"
export const { handlers, auth } = NextAuth({
...authConfig,
adapter: PrismaAdapter(prisma),
})这是因为Edge Runtime不支持某些Node.js API,拆分后middleware可以用auth.config.ts,服务端路由用完整的auth.ts。
Database Session深度剖析
什么时候必须用Session?
虽然JWT很方便,但有些场景你还是得老老实实用Session。
我之前做过一个后台管理系统,客户是个金融公司。他们的要求很明确:任何时候发现异常登录,必须能立即踢掉用户。这种情况下,JWT就不合适了。
Database Session适合这些场景:
1. 需要即时控制的应用
比如多端登录限制(只允许一个设备登录)、强制下线(管理员踢人)、密码修改后立即失效所有登录等。这些需求用Session很容易实现,用JWT就得搞很多额外的逻辑。
2. 高安全要求的场景
银行、医疗、政府这些行业,每次请求都要验证最新的权限状态。Session可以做到每次请求都去数据库查最新信息,确保权限变更立即生效。
3. 传统单体应用
如果你的应用就是一个传统的单体架构,服务器和数据库在一起,那用Session也没啥负担。查数据库的延迟很低,还能更好地控制会话。
Session的性能问题
最大的问题是每次请求都要查数据库。如果你的应用流量很大,这可能成为瓶颈。
有几个优化方法:
用Redis存Session:比传统数据库快很多,而且支持自动过期。
Session信息最小化:只存用户ID,其他信息需要时再查。
合理设置过期时间:过期的Session要及时清理,不然Session表会越来越大。
Next.js Session配置实战
NextAuth.js默认用JWT,如果你想用Database Session,需要配置一个adapter:
// auth.ts
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
import GitHub from "next-auth/providers/github"
const prisma = new PrismaClient()
export const { handlers, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [GitHub],
session: {
strategy: "database",
maxAge: 30 * 24 * 60 * 60, // 30天
updateAge: 24 * 60 * 60, // 每天更新
},
})Prisma需要创建Session表,运行npx prisma db push会自动创建:
// schema.prisma
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
id String @id @default(cuid())
email String @unique
sessions Session[]
}性能优化技巧:
给
sessionToken和expires字段加索引,查询快很多。定期清理过期Session,可以写个定时任务:
// cleanup-sessions.ts
async function cleanupExpiredSessions() {
await prisma.session.deleteMany({
where: {
expires: {
lt: new Date(),
},
},
})
}
// 每天凌晨3点运行实战决策框架
好了,理论讲了一大堆,到底该怎么选?我总结了一个决策框架,你可以对着自己的项目过一遍:
按项目类型选择
| 项目类型 | 推荐方案 | 理由 |
|---|---|---|
| Serverless应用 | JWT | 无状态,不需要共享Session存储 |
| 传统单体应用 | Session | 数据库就在旁边,查询成本低 |
| 微服务架构 | JWT或混合 | 跨服务认证方便 |
| 内容网站/博客 | JWT | 认证需求简单,JWT够用 |
| SaaS后台 | Session | 需要精细的权限控制 |
按安全要求选择
- 低敏感(博客、论坛、内容站):JWT,设置30天过期
- 中敏感(电商、社交):JWT + 短过期 + refresh token
- 高敏感(金融、医疗、后台):Session,设置短过期 + 滚动会话
真实案例分析
案例1:电商网站 - 选择JWT
我朋友做的那个电商网站,最后选了JWT。理由是:
- 部署在Vercel,Serverless架构,JWT更合适
- 用户量大,减少数据库查询能降低成本
- 会话控制要求不高,用户登出后token慢点失效也能接受
唯一的妥协是把JWT过期时间设成7天,而不是常见的30天。这样即使账号被盗,7天后token也会自动失效。
案例2:OA系统 - 选择Session
另一个项目是公司内部的OA系统,选的是Session:
- 有严格的权限管理,角色变更要立即生效
- 需要限制同时登录设备数
- 内网部署,数据库查询延迟可以忽略
这种场景Session明显更合适。我们还做了个优化,用Redis存Session,响应时间在50ms以内,完全够用。
案例3:混合方案 - Clerk的做法
现在很多认证平台(比如Clerk)用的是混合方案:
- 短期token(JWT):有效期15分钟,用于API调用
- 长期token(Refresh Token):存在数据库,用于刷新短期token
- Session记录:数据库里记录活跃的Session,可以远程撤销
这样既有JWT的性能优势,又有Session的控制能力。不过实现起来复杂一点,适合对安全和用户体验都有高要求的场景。
我的建议
如果你还是不确定,我的建议是:
默认用JWT:大多数场景下JWT够用,配置简单,性能好。
遇到这些情况换Session:
- 需要多端登录限制
- 需要立即踢人
- 金融/医疗等高安全场景
- 密码修改后要立即失效所有登录
别过度设计:如果项目不大,先用简单的方案,等真遇到瓶颈再优化。我见过太多项目一开始就搞混合方案,结果代码复杂度爆炸,实际上根本用不到那些功能。
会话过期处理最佳实践
选好了方案,还得处理好会话过期的问题。这个地方搞不好,用户体验会很差。
JWT过期处理
JWT过期最让人头疼的是:用户正在填表单,突然提交时提示”登录已过期”,填的内容全丢了。这种体验太糟了。
解决方案:Refresh Token + 无感刷新
思路是这样的:
给用户发两个token:
- Access Token:有效期15分钟,用于API调用
- Refresh Token:有效期30天,用于获取新的Access Token
前端在Access Token快过期时(比如还剩2分钟),自动用Refresh Token去换新的Access Token
用户完全无感知,不会突然被登出
NextAuth.js其实内置了这个机制,你只需要配置:
callbacks: {
async jwt({ token, user, account }) {
if (account && user) {
// 首次登录
return {
...token,
accessToken: account.access_token,
accessTokenExpires: Date.now() + account.expires_in * 1000,
refreshToken: account.refresh_token,
}
}
// Access Token还有效
if (Date.now() < token.accessTokenExpires) {
return token
}
// Access Token过期,用Refresh Token刷新
return refreshAccessToken(token)
},
}
async function refreshAccessToken(token) {
try {
const response = await fetch("https://api.example.com/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: process.env.OAUTH_CLIENT_ID,
grant_type: "refresh_token",
refresh_token: token.refreshToken,
}),
})
const refreshedTokens = await response.json()
return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,
}
} catch (error) {
return {
...token,
error: "RefreshAccessTokenError",
}
}
}Session过期处理
Session的过期处理相对简单,但也有讲究。
滚动会话 vs 绝对超时
- 滚动会话:用户有活动就延长会话,适合大多数场景
- 绝对超时:不管用户是否活跃,到时间就强制登出,适合银行这种高安全场景
NextAuth.js的滚动会话配置很简单:
session: {
strategy: "database",
maxAge: 2 * 60 * 60, // 2小时绝对超时
updateAge: 30 * 60, // 30分钟内有活动就更新
}这样配置的话,用户如果30分钟内有操作,会话就会延长;但不管怎样,2小时后都会强制登出。
空闲超时
有些场景你想实现”空闲15分钟后自动登出”,可以在前端加个计时器:
let idleTimer
function resetIdleTimer() {
clearTimeout(idleTimer)
idleTimer = setTimeout(() => {
// 15分钟无操作,登出
signOut()
}, 15 * 60 * 1000)
}
// 监听用户活动
document.addEventListener("mousemove", resetIdleTimer)
document.addEventListener("keypress", resetIdleTimer)2025年的新趋势
最后聊聊今年(2025)的一些新趋势,可能会影响你的选择。
混合方案成为主流
越来越多的团队意识到,JWT和Session不是非此即彼的关系。比如:
- 用JWT做API认证(快)
- 用数据库记录活跃Session(可控)
- 结合两者优势
Clerk、Auth0这些认证平台都是这么做的。如果你不想自己实现,直接用这些服务也挺好。
Edge Runtime的影响
Next.js的middleware现在跑在Edge Runtime上,对JWT更友好。如果你的认证逻辑要放在middleware里(比如保护整个/dashboard路径),JWT会更方便。
Passkey和WebAuthn
虽然不是今天的主题,但值得一提:Passkey(基于WebAuthn)正在普及,很多网站开始支持指纹、Face ID登录,不需要密码。这种场景下,认证流程会更复杂,但用户体验更好。NextAuth.js v5已经支持Passkey了。
最后说两句
写到这里,你应该对JWT和Session有比较清楚的认识了。选哪个真的没有标准答案,关键是理解它们的优劣,结合自己项目的实际情况做决策。
我的经验是:大多数项目用JWT就够了,遇到需要即时控制的场景再考虑Session。别一开始就搞得太复杂,简单的方案往往更可靠。
如果你还在纠结,就先用NextAuth.js的默认配置(JWT),跑起来再说。真遇到问题了,切换到Session也不难,改几行配置的事。
不知道你现在是不是还在凌晨三点纠结技术选型?如果是的话,建议你先睡一觉,第二天再做决定。很多时候,睡一觉起来问题就不是问题了(笑)。
对了,如果你对这篇文章有什么想法,或者遇到了我没提到的场景,欢迎留言讨论。技术这东西,大家一起聊才有意思嘛。
常见问题
JWT 和 Session 的核心区别是什么?
什么时候应该用 JWT,什么时候用 Session?
JWT 无法即时撤销怎么办?
Session 性能问题怎么优化?
NextAuth.js 怎么配置 JWT 和 Session?
会话过期怎么处理?用户体验如何优化?
2025年 JWT 和 Session 有什么新趋势?
14 分钟阅读 · 发布于: 2025年12月19日 · 修改于: 2026年1月22日
相关文章
Next.js 电商实战:购物车与 Stripe 支付完整实现指南

Next.js 电商实战:购物车与 Stripe 支付完整实现指南
Next.js 文件上传完整指南:S3/七牛云预签名URL直传实战

Next.js 文件上传完整指南:S3/七牛云预签名URL直传实战
Next.js 单元测试实战:Jest + React Testing Library 完整配置指南


评论
使用 GitHub 账号登录后即可评论