切换语言
切换主题

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有效就行,不用查数据库。

0次
JWT 数据库查询
后续请求无需查库
每次
Session 数据库查询
每个请求都要查库
15分钟
JWT 推荐过期时间
搭配 refresh token
4KB
Cookie 大小限制
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
    },
  },
})

几个注意事项:

  1. 一定要设置NEXTAUTH_SECRET(Auth.js v5改名为AUTH_SECRET了)。可以用npx auth secret生成一个安全的密钥。这个密钥用来给JWT签名和加密,千万别泄露了。

  2. 滚动会话:如果你想让用户活跃时自动续期,可以配置updateAge

session: {
  strategy: "jwt",
  maxAge: 30 * 24 * 60 * 60,
  updateAge: 24 * 60 * 60, // 每24小时刷新一次
}

这样用户只要在24小时内有活动,Session就会自动续期,不会突然登出。

  1. 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的性能问题

最大的问题是每次请求都要查数据库。如果你的应用流量很大,这可能成为瓶颈。

有几个优化方法:

  1. 用Redis存Session:比传统数据库快很多,而且支持自动过期。

  2. Session信息最小化:只存用户ID,其他信息需要时再查。

  3. 合理设置过期时间:过期的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[]
}

性能优化技巧

  1. sessionTokenexpires字段加索引,查询快很多。

  2. 定期清理过期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的控制能力。不过实现起来复杂一点,适合对安全和用户体验都有高要求的场景。

我的建议

如果你还是不确定,我的建议是:

  1. 默认用JWT:大多数场景下JWT够用,配置简单,性能好。

  2. 遇到这些情况换Session

    • 需要多端登录限制
    • 需要立即踢人
    • 金融/医疗等高安全场景
    • 密码修改后要立即失效所有登录
  3. 别过度设计:如果项目不大,先用简单的方案,等真遇到瓶颈再优化。我见过太多项目一开始就搞混合方案,结果代码复杂度爆炸,实际上根本用不到那些功能。

会话过期处理最佳实践

选好了方案,还得处理好会话过期的问题。这个地方搞不好,用户体验会很差。

JWT过期处理

JWT过期最让人头疼的是:用户正在填表单,突然提交时提示”登录已过期”,填的内容全丢了。这种体验太糟了。

解决方案:Refresh Token + 无感刷新

思路是这样的:

  1. 给用户发两个token:

    • Access Token:有效期15分钟,用于API调用
    • Refresh Token:有效期30天,用于获取新的Access Token
  2. 前端在Access Token快过期时(比如还剩2分钟),自动用Refresh Token去换新的Access Token

  3. 用户完全无感知,不会突然被登出

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 是无状态的,用户信息编码在 token 中,服务器验证签名即可,无需查数据库。Session 是有状态的,用户信息存在数据库,每次请求都要查库验证。JWT 适合 Serverless/高并发,Session 适合需要即时控制的场景。
什么时候应该用 JWT,什么时候用 Session?
用 JWT:Serverless/边缘计算、高并发场景、微服务架构、内容网站/博客。用 Session:需要即时控制(多端登录限制、强制下线)、高安全要求(金融/医疗)、传统单体应用。默认建议用 JWT,遇到需要即时控制的场景再换 Session。
JWT 无法即时撤销怎么办?
JWT 一旦发出在过期前都有效,无法直接撤销。解决方案:1) 设置短过期时间(15分钟)+ refresh token,2) 使用黑名单(但会失去无状态优势),3) 改用 Session 或混合方案。大多数场景下短过期时间 + refresh token 足够。
Session 性能问题怎么优化?
Session 每次请求都要查数据库,可能成为瓶颈。优化方法:1) 用 Redis 存 Session(比数据库快很多),2) Session 信息最小化(只存用户ID),3) 给 sessionToken 和 expires 字段加索引,4) 定期清理过期 Session。
NextAuth.js 怎么配置 JWT 和 Session?
JWT 配置:session: { strategy: "jwt", maxAge: 30天 },在 callbacks.jwt 中添加用户信息。Session 配置:使用 PrismaAdapter,session: { strategy: "database" },需要创建 Session 表。Edge Runtime 部署需要拆分配置。
会话过期怎么处理?用户体验如何优化?
JWT:使用 Access Token(15分钟)+ Refresh Token(30天)实现无感刷新,前端自动在过期前刷新。Session:使用滚动会话(updateAge),用户有活动就延长会话。避免用户填表单时突然登出导致数据丢失。
2025年 JWT 和 Session 有什么新趋势?
混合方案成为主流:JWT 做 API 认证(快)+ 数据库记录 Session(可控)。Edge Runtime 对 JWT 更友好,适合 middleware 认证。Passkey/WebAuthn 正在普及,NextAuth.js v5 已支持。建议根据实际需求选择,别过度设计。

14 分钟阅读 · 发布于: 2025年12月19日 · 修改于: 2026年1月22日

评论

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

相关文章