技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTBR

Session 与 JWT 认证:登录会话机制的详细对比

所属馆藏
Browser
文件格式
Markdown
原始路径
Browser/39-Session 与 JWT 认证:登录会话机制的详细对比
本文目录14 个章节
  1. 一、先给结论
  2. 二、先把几个容易混淆的概念分开
  3. 三、两种方案的共同目标
  4. 四、服务端 Session 的工作原理
  5. 五、JWT 的工作原理
  6. 六、Session 与 JWT 的详细对比
  7. 七、安全性对比:不要只看“服务端有没有存储”
  8. 八、注销、续期与权限变化
  9. 九、扩展性和性能:JWT 不一定天然更快
  10. 十、不同业务场景应该怎么选
  11. 十一、当前项目属于哪种方式
  12. 十二、常见误区
  13. 十三、最终决策清单
  14. 十四、总结

Session 与 JWT 认证:登录会话机制的详细对比

Category(分类): Browser, Security, Authentication Status: 已更新

Session 和 JWT 经常被放在一起比较,但它们并不是完全同一层面的概念:Session 主要描述“登录状态由谁保存和管理”,JWT 主要描述“令牌使用什么格式传递声明”。本文从请求流程、数据存储、扩展性、安全性、注销、过期和实际选型等方面,对两种常见方案进行完整对比。

一、先给结论

如果只想快速做选择,可以先记住下面几句话:

  • 传统网站、SSR 应用、管理后台:优先考虑服务端 Session + HttpOnly Cookie。
  • 浏览器应用:如果没有必须让浏览器直接调用第三方 API 的需求,BFF 或服务端 Session 往往更容易做好安全控制。
  • 移动端、开放 API、微服务之间的调用:短期 Access Token 比较常见,JWT 可以作为 Access Token 的一种格式。
  • 需要立即注销、立即封禁、权限立即生效:服务端 Session 更直接;JWT 需要额外设计撤销或版本机制。
  • JWT 不是“更高级的 Session”,也不是只要使用 JWT 就自动安全。
  • Session 不等于 Cookie,JWT 也不等于 localStorage:存储位置和令牌格式是不同维度的问题。

最核心的区别可以概括为:

服务端 Session:浏览器保存一个会话标识,服务端保存会话状态
JWT:浏览器保存一个自包含的签名令牌,服务端验证令牌即可得到部分状态

这里的“无状态”是相对的。JWT 服务端仍然需要保存签名密钥,也可能查询用户、权限、租户和风控数据;它只是不必为每一个 Access Token 保存一条登录会话记录

二、先把几个容易混淆的概念分开

1. HTTP 无状态

HTTP 请求本身不会自动记住“上一个请求是哪一个用户”。服务端需要通过 Cookie、Session ID、Token、OAuth/OIDC 或其他机制,识别请求对应的主体。

HTTP 无状态描述的是请求上下文不会自动替应用保存,不代表每次请求都必须重新建立 TCP 连接。HTTP/1.1 Keep-Alive、HTTP/2 多路复用和 HTTP/3/QUIC 都可以复用连接。

Cookie 是浏览器提供的存储和传输机制。服务端通过 Set-Cookie 写入 Cookie,浏览器在域名、路径、过期时间、SecureSameSite 等条件允许时,自动在后续请求中添加 Cookie 请求头。

Cookie 中可以保存:

  • 服务端 Session ID;
  • Opaque Token;
  • JWT;
  • 主题、语言等普通偏好设置。

Cookie 本身不代表一定是 Session,也不代表一定安全。

3. Session

Session 是一次登录会话及其服务端状态的抽象。常见的服务端 Session 方案中,浏览器只保存一个随机的、不可预测的 Session ID,用户、过期时间、撤销状态等信息存储在 Redis、数据库或其他 Session Store 中。

严格来说,Session 也可以把少量状态加密或签名后放到 Cookie 中,这种方案的服务端状态较少,不能简单地说“所有 Session 都必须查数据库”。不过本文重点讨论最常见的服务端 Session

4. Token

Token 是“令牌”的泛称,是后续请求中用来证明身份或权限的一段凭证。Token 可以是:

  • 只包含随机字符的 Opaque Token;
  • JWT;
  • OAuth 2.0 Access Token;
  • 某种自定义的签名字符串。

Token 不一定是 JWT。

5. JWT

JWT(JSON Web Token)是一种紧凑、URL-safe 的声明传输格式。最常见的签名 JWT(JWS)包含 Header、Payload 和 Signature 三部分:

base64url(header).base64url(payload).base64url(signature)

JWT 的 Payload 通常可以被解码阅读,签名用于发现篡改,不等于加密。因此不能把密码、身份证号、银行卡号等敏感数据直接放到 Payload 中。

三、两种方案的共同目标

无论使用 Session 还是 JWT,登录系统都要解决以下问题:

  1. 用户提交账号和密码,服务端验证身份;
  2. 服务端创建或签发登录凭证;
  3. 浏览器或客户端保存凭证;
  4. 后续请求携带凭证;
  5. 服务端验证凭证并识别用户;
  6. 服务端根据用户、资源、操作和业务上下文执行授权;
  7. 处理过期、注销、改密、封禁、权限变化和异常登录。

认证只回答“你是谁”,授权还要回答“你能做什么”。无论 Token 中写了什么,最终的资源权限都必须由服务端执行,不能只依赖前端隐藏按钮或路由守卫。

四、服务端 Session 的工作原理

1. 登录流程

典型流程如下:

浏览器                         服务端                         Session Store
  |                              |                                |
  | -- 用户名、密码 ------------> |                                |
  |                              | -- 校验密码和账号状态 --------> |
  |                              | -- 创建 Session --------------> |
  | <--------- Set-Cookie ------- |                                |

具体步骤:

  1. 用户通过 HTTPS 提交用户名和密码;
  2. 服务端校验密码哈希、账号状态、验证码和风险策略;
  3. 服务端生成高熵、不可预测的随机 Session ID;
  4. 服务端将 Session ID 对应的会话记录写入 Redis、数据库等存储;
  5. 服务端通过 Set-Cookie 把 Session ID 返回给浏览器;
  6. 浏览器保存 Cookie。

一个典型的响应头如下:

Set-Cookie: __Host-session=RANDOM_SESSION_ID; Path=/; Max-Age=1800; HttpOnly; Secure; SameSite=Lax

2. 后续访问流程

浏览器                         服务端                         Session Store
  |                              |                                |
  | -- Cookie: session=... ----> |                                |
  |                              | -- 查询并验证 Session --------> |
  |                              | <------- 用户和过期信息 -------- |
  | <--------- 页面或 API ------- |                                |

服务端收到请求后通常会:

  1. 从 Cookie 中取出 Session ID;
  2. 对 Session ID 进行格式和长度检查;
  3. 查询对应的 Session 记录;
  4. 判断会话是否存在、是否过期、是否被撤销;
  5. 关联用户并检查用户是否仍然启用;
  6. 加载必要的权限和租户信息;
  7. 执行资源授权,最后返回页面或 API 响应。

3. Session 表可以保存什么

一个简化的 Session 表可能如下:

字段含义
token_hashSession ID 的哈希值,避免数据库泄露时直接暴露可用凭证
user_id会话所属用户
created_at会话创建时间
last_seen_at最近使用时间,可用于空闲超时
expires_at绝对过期时间
revoked_at撤销时间,或使用单独的状态字段
user_agent可选的设备信息,便于会话管理和审计
ip可选的登录来源信息,不应单独作为身份判断依据

示意数据:

sessions
-----------------------------------------------------------------
 token_hash       user_id   created_at           expires_at
 sha256(...)      42        2026-03-01 10:00:00  2026-03-31 10:00:00

安全实现通常不把原始 Session ID 明文保存到数据库,而是保存哈希:

const sessionId = randomBytes(32).toString('base64url')
const sessionHash = sha256(sessionId)

// 浏览器拿到 sessionId,服务端存储 sessionHash

浏览器持有原始值,服务端根据请求中的原始值计算哈希后查询数据库。即使数据库被读出,攻击者也不能直接拿哈希值当作 Cookie 使用。

4. Session 的过期方式

Session 的过期通常有几种策略:

  • 绝对过期:从登录时刻起固定有效一段时间,例如 30 天;
  • 空闲过期:用户一段时间没有操作后失效,例如 30 分钟;
  • 滑动过期:用户每次访问都延长过期时间,但不能超过绝对上限;
  • 手动撤销:用户退出登录、修改密码、被管理员封禁时立即删除或标记失效。

要注意:Cookie 的过期时间只是浏览器端的保存策略,Session Store 中的 expires_at 才是服务端真正的判断依据。即使 Cookie 还存在,如果服务端的 Session 行已经被删除,用户仍然会被视为未登录。

5. Session 的优点

撤销简单

退出登录时删除当前 Session 记录即可。管理员封禁用户时,可以删除该用户的所有 Session。修改密码时,也可以让所有旧会话失效。

DELETE FROM sessions WHERE token_hash = ?;

-- 让某个用户的所有登录设备下线
DELETE FROM sessions WHERE user_id = ?;

权限变化容易及时生效

服务端每次请求都可以从 Session Store 或权限系统读取最新状态。用户角色被降级后,不需要等待旧 Token 自然过期。

客户端凭证更容易保护

浏览器通常只保存一个没有业务含义的随机 ID。将它放入 HttpOnly Cookie 后,普通 JavaScript 不能直接读取原始值。

适合传统 Web 和 SSR

服务端渲染页面时,可以直接从请求 Cookie 恢复用户会话。模板、页面数据和接口使用同一套认证逻辑,整体模型比较直接。

6. Session 的缺点

需要服务端存储

每个活跃会话都要占用 Redis、数据库或其他存储空间,请求通常也需要一次读取。生产环境不能依赖单机进程内存,否则重启会丢失会话,多实例之间也无法共享登录状态。

需要解决多实例部署

负载均衡后的任意应用实例都必须能够访问同一个 Session Store。常用方案是 Redis 或数据库,而不是把会话只保存在某一台应用服务器的内存中。

需要处理存储故障和性能

Session Store 变慢或不可用会直接影响登录验证。因此需要考虑连接池、超时、缓存、过期淘汰、监控和故障降级策略。

如果认证凭证放在 Cookie 中,浏览器可能自动把它带到跨站请求。需要结合 SameSite、CSRF Token、Origin/Referer 校验和正确的 CORS 策略防御 CSRF。

五、JWT 的工作原理

1. JWT 的结构

一个常见的签名 JWT 如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiJ1c2VyLTQyIiwiYXVkIjoiYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxODkzNDU2MDAwfQ
.
SIGNATURE

解码后大致是:

{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "iss": "https://id.example.com",
  "sub": "user-42",
  "aud": "api.example.com",
  "exp": 1893456000,
  "iat": 1893452400,
  "scope": "article:read"
}

常见声明包括:

  • iss:签发者;
  • sub:主体,一般表示用户或客户端;
  • aud:受众,表示这个令牌发给哪个服务使用;
  • exp:过期时间;
  • iat:签发时间;
  • nbf:在此时间之前不能使用;
  • jti:令牌唯一标识,可用于重放检测或撤销;
  • scope 或自定义权限声明:令牌被允许做什么。

2. JWT 登录和访问流程

客户端                      认证服务                         业务 API
  |                            |                              |
  | -- 用户凭证 -------------> |                              |
  |                            | -- 签发 JWT ----------------> |
  | <--------- JWT ------------ |                              |
  |                            |                              |
  | -- Authorization: Bearer JWT ----------------------------> |
  |                            |                              |-- 验证签名、exp、aud
  | <--------------------------- API 响应 --------------------- |

具体步骤:

  1. 用户提交账号和密码;
  2. 认证服务验证身份;
  3. 认证服务把用户标识、受众、过期时间和必要的权限范围写入 JWT;
  4. 认证服务使用密钥签名;
  5. 客户端在后续请求中携带 JWT;
  6. 业务 API 使用密钥或公钥验证签名;
  7. 业务 API 检查 expissaudnbf、scope 等声明;
  8. 验证通过后,使用 sub 等声明识别主体并执行授权。

令牌可以放在:

Authorization: Bearer ACCESS_TOKEN

也可以放入 HttpOnly Cookie:

Cookie: access_token=JWT_VALUE

JWT 是否放在 Cookie 中,与 JWT 是否“无状态”是两个不同问题。放在 Cookie 中仍然可以是无状态 JWT,但需要额外处理 CSRF。

3. JWT 为什么被称为无状态

传统服务端 Session 的验证方式是:

const session = await sessionStore.get(hash(sessionId))
if (!session || session.expiresAt <= Date.now()) {
  return unauthorized()
}

return findUser(session.userId)

服务端必须查询一条“这个令牌对应哪个用户、是否有效”的记录。

JWT 的验证方式则类似:

const claims = verifySignature(jwt, verificationKey)

if (claims.exp <= now()) {
  return unauthorized()
}

return claims.sub

只要签名密钥在服务端,服务端就可以验证 JWT 的完整性,并从声明中读取主体和有效期,不需要为每个 Access Token 维护一条 Session 记录。因此称为“无状态”。

这里有三个重要边界:

  1. 无状态不是没有任何服务端状态:服务端仍要保存签名密钥,可能还要保存用户、权限、租户和风控数据;
  2. 无状态不是客户端可信:客户端可以解码 Payload,但不能在没有密钥的情况下生成有效签名;
  3. 无状态不是永远无法撤销:可以通过短过期时间、用户会话版本、撤销列表或在线校验实现撤销,只是这些机制会重新引入服务端状态或查询。

4. JWT 的优点

服务间验证方便

多个 API 服务可以使用同一组可信公钥验证 JWT,不必每次都访问中心 Session Store。认证服务负责签发,业务服务负责验证。

尤其是非对称签名算法中,认证服务持有私钥,业务服务只持有公钥。业务服务能够验证令牌,但不能签发新的合法令牌。

适合 API、移动端和跨服务场景

非浏览器客户端可以主动把 JWT 放在 Authorization 请求头中,不依赖浏览器 Cookie 规则。不同服务、不同技术栈之间也更容易约定标准化声明。

令牌中可以携带有限的上下文

服务端验证签名后可以直接读取 subaudscope 等声明,减少把“主体是谁”作为一次单独查询的需要。

横向扩展时不必复制 Access Token 状态

应用实例可以共享验证密钥或公钥,不需要把每条 Access Token 会话复制到每个实例的本地内存中。

5. JWT 的缺点

难以立即撤销

服务端只验证签名和过期时间时,已经签发的 JWT 在 exp 到来前通常都有效。用户点击退出登录时,删除浏览器里的 JWT 只能阻止当前浏览器继续发送,无法让已经被窃取的 JWT 立即失效。

权限可能过期

如果 JWT 中写入了角色或 scope,用户权限变化后,旧 JWT 里的声明不会自动变化。在旧令牌过期前,服务端如果完全信任这些声明,就可能继续使用旧权限。

令牌泄露后影响较大

JWT 本质上通常是 Bearer Token:谁拿到谁就可以使用。JWT Payload 可以阅读,不能因为“做了签名”就把它当作隐私容器。

令牌尺寸可能较大

Session ID 通常只是几十个随机字符,而 JWT 可能包含较多声明。把它放在每个请求的 Header 或 Cookie 中,会增加请求体积;放进 Cookie 时还受到浏览器 Cookie 大小限制。

密钥轮换和受众管理更复杂

多服务环境需要管理密钥发布、轮换、kid、JWKS 缓存、算法白名单、issaud。如果所有服务共享一个 HMAC 密钥,任意一个服务泄露密钥都可能伪造令牌。

六、Session 与 JWT 的详细对比

对比维度服务端 SessionJWT
本质服务端保存会话状态,客户端保存会话标识客户端保存包含声明的签名令牌
服务端是否保存每个登录状态通常需要纯 Access Token 验证模式通常不需要
客户端保存内容随机、无业务含义的 Session ID用户标识、过期时间、受众等声明及签名
服务端如何识别用户查询 Session ID 对应的用户验证签名后读取 sub 等声明
过期判断读取 Session 的 expires_at检查 JWT 的 exp
注销当前设备删除或撤销 Session 记录删除本地 Token;要立即失效需额外撤销机制
管理员让用户全部下线删除该用户的所有 Session需要撤销列表、会话版本或等待 Token 过期
权限变化生效速度可以较快生效,取决于服务端缓存策略旧 JWT 中的声明可能继续有效
多实例扩展需要共享 Session Store 或其他同步方案各实例共享公钥/密钥即可验证 Access Token
存储压力活跃会话数越多,服务端存储越大Access Token 不保存时,服务端存储压力较小
认证查询通常需要 Session Store 查询可本地完成签名和时间验证,但授权仍可能查库
浏览器常见传输HttpOnly CookieAuthorization Header 或 HttpOnly Cookie
Cookie 自动发送常见,需要处理 CSRF若放 Cookie,同样需要处理 CSRF
放入 localStorage一般不需要常见但会暴露给同源 XSS
泄露后的风险直到 Session 被撤销或过期直到 JWT 过期或被额外撤销
审计和设备管理直接记录每个 Session,比较方便需要额外记录 Refresh Token 或登录事件
实现复杂度模型直观,生产上需要设计存储和扩展表面简单,但密钥、撤销、刷新、声明和轮换更复杂
更适合传统网站、SSR、后台、BFFAPI、移动端、OAuth/OIDC、微服务调用

七、安全性对比:不要只看“服务端有没有存储”

1. Session ID 和 JWT 都是凭证

无论是下面哪一种:

Cookie: session=RANDOM_ID

还是:

Authorization: Bearer eyJ...

只要攻击者能够拿到并发送,服务端就可能把攻击者当成真实用户。两者都应当:

  • 只通过 HTTPS 传输;
  • 使用密码学安全的随机值或安全的签名算法;
  • 设置合理的过期时间;
  • 避免写入 URL、Referer、日志和错误信息;
  • 对登录、登出、改密和权限变化进行审计;
  • 对高风险操作要求重新认证或 MFA。

2. Cookie、HttpOnly 和 CSRF

如果凭证放在 Cookie 中,浏览器会根据 Cookie 规则自动发送。推荐使用:

Set-Cookie: __Host-session=...; Path=/; HttpOnly; Secure; SameSite=Lax

HttpOnly 可以阻止 JavaScript 直接读取 Cookie,但不能阻止 XSS 代码借助当前页面发起已经认证的请求。因此还需要做好 XSS 防护。

Cookie 自动发送可能产生 CSRF 风险,应该结合:

  • SameSite=LaxSameSite=Strict
  • 有副作用请求的 CSRF Token;
  • Origin/Referer 校验;
  • 精确的 CORS allowlist;
  • 重要操作重新认证。

3. Authorization Header 和 XSS

如果 JWT 放在 Authorization Header 中,浏览器不会像 Cookie 一样在所有匹配请求中自动附加,CSRF 风险通常更低。但前端必须自己读取和发送 Token。

如果 Token 放到 localStorage,同源 XSS 代码可以直接读取它并发送到攻击者服务器。sessionStorage 只缩短了跨标签页和持久化范围,不能解决 XSS 读取问题。

这不是说 JWT 一定不能放在浏览器,而是要根据架构选择:

  • 浏览器只访问自己的后端:优先 BFF 或 HttpOnly Session Cookie;
  • 必须由 SPA 直接调用 API:使用短期 Access Token,并设计安全的 Refresh Token 方案;
  • 不要把长期、高权限的凭证长期放在可被 JavaScript 读取的存储中。

4. 算法和声明校验

JWT 验证不能只做“能否解码”:

  • 固定允许的算法集合;
  • 验证签名;
  • 检查 expnbfiat
  • 检查 issaud
  • 检查 scope、权限范围和租户边界;
  • 处理时钟偏差,但不要允许过大的容忍窗口;
  • 做密钥轮换和公钥缓存;
  • 不接受非预期的 none 或算法降级。

5. Session Fixation

Session 方案需要防止 Session Fixation:登录前后不能继续使用攻击者预先知道的 Session ID。正确做法是登录成功后轮换 Session ID,并将旧的匿名会话与新会话分离。

JWT 通常由登录成功后新签发的令牌代表新会话,也要避免接受来源不明、算法不符合预期或绑定关系不清晰的 Token。

八、注销、续期与权限变化

1. Session 的注销

Session 注销通常是:

  1. 从请求中取出 Session ID;
  2. 在服务端删除或标记对应记录为 revoked;
  3. 清除浏览器 Cookie。

即使攻击者之前复制了 Session ID,只要服务端删除记录,后续请求也会失败。

2. JWT 的注销

JWT 注销至少有两个动作:

  1. 清除客户端保存的 JWT;
  2. 处理服务端仍然有效的 JWT。

如果不做第二步,已经被盗的 JWT 仍可以在过期前使用。常见处理方式包括:

  • 让 Access Token 很短时间过期;
  • 保存并撤销 Refresh Token;
  • 维护 jti 黑名单;
  • 为用户保存 sessionVersion,JWT 中带版本号,请求时比较版本;
  • 对高风险接口做在线 Introspection;
  • 发生改密、封禁时拒绝旧的 iat 或旧会话版本。

这些方法越强调“立即失效”,越需要服务端状态,JWT 的纯无状态特征也就越弱。

3. Refresh Token 不等于普通 JWT

生产系统经常使用:

短期 Access Token + 较长期 Refresh Token

Access Token 用于访问 API,过期时间较短;Refresh Token 只用于换取新的 Access Token,通常需要:

  • 存储在更安全的位置;
  • 服务端记录哈希或会话信息;
  • 每次刷新后轮换;
  • 检测重用行为;
  • 退出登录和改密时撤销;
  • 按设备或客户端管理。

因此,“使用 JWT”并不意味着系统完全不需要数据库。很多系统只是让 Access Token 自包含,而把长期会话、刷新和撤销状态保留在服务端。

九、扩展性和性能:JWT 不一定天然更快

1. Session 的扩展

单机开发时,Session 可以放在进程内存中,但生产环境多实例部署通常需要共享存储:

请求 -> 任意应用实例 -> Redis/数据库 Session Store

常见优化包括:

  • 使用 Redis 等低延迟共享存储;
  • 设置过期时间和自动淘汰;
  • 只在需要时查询完整用户信息;
  • 对稳定的非敏感数据做短时缓存;
  • 监控存储延迟和连接池;
  • 不把 Session 绑定到单台应用实例。

2. JWT 的扩展

JWT 的业务服务可以本地验证签名:

认证服务 -> 私钥签发 JWT
业务服务 -> 使用公钥验证 JWT

这能减少中心 Session Store 的读取,但带来新的运维问题:

  • 公钥如何分发和缓存;
  • 密钥如何轮换;
  • 多个服务如何限制 aud 和 scope;
  • 旧密钥保留多久;
  • 如何处理 Token 泄露和立即撤销;
  • JWT 过大导致请求头膨胀。

3. 查询次数不是唯一指标

Session 多一次 Session Store 查询,不代表一定更慢;Redis 查询可能比复杂的业务数据库查询更快。JWT 少一次会话查询,也不代表整个请求不查数据库,因为服务端可能仍需要:

  • 查询用户是否被封禁;
  • 查询资源所属租户;
  • 查询最新角色和权限;
  • 读取订单、文章或文件数据;
  • 记录审计和风控事件。

真正应该根据实际流量、延迟、可用性和撤销需求做压测,而不是简单认为“JWT 无状态,所以一定快”。

十、不同业务场景应该怎么选

场景一:传统服务端渲染网站

推荐:

服务端 Session + HttpOnly Cookie

原因:

  • SSR 页面可以直接识别用户;
  • 登录、登出和权限变化容易管理;
  • Cookie 不需要暴露给页面 JavaScript;
  • 与服务端路由、中间件和模板结合自然。

场景二:后台管理系统

通常也优先选择服务端 Session。后台操作涉及删除、导出、改密、用户管理等高风险动作,立即撤销和权限即时生效比“少查一次 Session Store”更重要。

如果使用 JWT,应缩短 Access Token 生命周期,并为管理员操作增加重新认证、MFA、设备管理和撤销能力。

场景三:SPA + 自有 API

不要先入为主地把 JWT 放进 localStorage。可以按下面顺序考虑:

  1. 使用 BFF,由后端保存和转发上游 Token;
  2. 使用 HttpOnly Cookie + 服务端 Session;
  3. 确实需要前端直接调用 API 时,再使用短期 Access Token,并完善 Refresh Token Rotation、CSP 和 XSS 防护。

场景四:移动端 App

移动端通常不能完全依赖浏览器 Cookie 自动管理,会使用 OAuth 2.0/OIDC 的 Access Token 和 Refresh Token。具体保存应使用系统安全存储,例如 Keychain 或 Android Keystore,而不是普通文本文件。

JWT 是否作为 Access Token,要看资源服务器是否需要本地验证、权限变化是否频繁,以及是否已有标准身份平台。

场景五:微服务

可以采用短期 JWT Access Token,让网关或各个服务使用公钥验证。但每个服务仍必须:

  • 校验签发者和受众;
  • 只接受自己需要的 scope;
  • 独立执行资源授权;
  • 不把网关已经认证当作全部授权结论;
  • 设计密钥轮换和故障策略。

也可以让网关把外部认证转换为内部 Session 或内部身份上下文,具体取决于组织边界和服务治理方式。

场景六:需要多设备会话管理

如果需要展示“我的登录设备”、单独踢出某台设备、查看登录时间和 IP,服务端 Session 或服务端保存 Refresh Token 的方案更直观。

纯 JWT Access Token 很难仅凭令牌本身完成设备级管理,因为服务端没有一条对应的可操作会话记录。

十一、当前项目属于哪种方式

当前项目使用的是服务端 Session + HttpOnly Cookie,不是 JWT:

浏览器 Cookie: archive_session
服务端数据库: .data/auth.sqlite
服务端表: sessions

登录成功后:

  1. 服务端生成一个随机 Token;
  2. 浏览器保存原始 Token;
  3. 服务端将 Token 的 SHA-256 哈希写入 sessions.token_hash
  4. 同时保存 user_idcreated_atexpires_at
  5. 后续请求自动携带 archive_session Cookie;
  6. 服务端重新计算哈希并查询 sessions 表;
  7. 查询到有效会话后,再关联 users 表识别用户。

当前会话的有效期是从创建时刻开始计算的固定 30 天。访问网页不会自动把它变成新的 30 天,也就是当前不是滑动续期模式。

退出登录时,服务端删除对应的 sessions 记录,同时清除 Cookie。即使浏览器还保留旧 Cookie,服务端也不会再认可它。

十二、常见误区

误区一:Cookie 就是 Session

不准确。Cookie 是浏览器的保存和传输机制,Session 是服务端对登录会话的管理方式。常见组合是“Session ID 放在 Cookie 中”。

误区二:Token 就是 JWT

不准确。Token 是泛称,Opaque Token、OAuth Access Token 和 JWT 都可以被称为 Token。

误区三:JWT 加密了 Payload

不准确。普通签名 JWT 的 Payload 通常可被解码。签名用于防篡改,不用于保密。需要保密时应该使用合适的加密方案,而不是把数据放进普通 JWT。

误区四:JWT 无状态就不能退出登录

不准确。可以删除客户端令牌,也可以通过短期 Access Token、Refresh Token 撤销、黑名单或会话版本实现退出和失效,只是立即撤销需要额外服务端状态。

误区五:JWT 一定比 Session 更适合前端

不准确。浏览器应用最关心的是 XSS、CSRF、撤销、权限变化、渲染模式和部署拓扑。很多 Web 应用使用 HttpOnly Cookie + 服务端 Session 更简单、风险边界也更清晰。

误区六:JWT 中的 role: admin 就能决定权限

不准确。JWT 声明只能作为认证上下文或权限提示的一部分。服务端仍需要检查用户状态、租户边界、资源归属和当前授权策略。

误区七:Session 存数据库一定会拖垮服务器

不准确。合理的 TTL、索引、Redis、连接池和水平扩展可以支撑大量 Session。是否适合要通过实际压测判断。

误区八:HttpOnly 可以防住 XSS

不准确。HttpOnly 可以防止脚本直接读取 Cookie,但 XSS 仍可能借助当前页面发起已认证的请求。输入处理、输出编码、CSP 和依赖安全仍然不可缺少。

十三、最终决策清单

选择认证方案时,可以依次回答这些问题:

  1. 这是传统网站、SSR、SPA、移动端还是开放 API?
  2. 是否需要多个服务直接验证同一份凭证?
  3. 是否要求管理员立即让用户下线?
  4. 用户改密、封禁、角色变化后,旧凭证能否继续有效几分钟?
  5. 是否需要设备级会话列表和单设备注销?
  6. 浏览器是否必须直接调用第三方 API?
  7. 能否可靠实现 CSRF、XSS、CSP、密钥轮换和 Token 刷新?
  8. 是否有 Redis、数据库或统一身份平台来维护必要状态?
  9. 是否明确了 issaud、scope、租户和资源授权边界?
  10. 是否做过真实流量下的性能、故障和撤销测试?

一个简单的选择思路是:

浏览器自有网站 / SSR / 后台
    -> HttpOnly Cookie + 服务端 Session

SPA / 移动端 / 开放 API
    -> 短期 Access Token + 安全的 Refresh Token 方案

多个系统统一身份
    -> OAuth 2.0 / OpenID Connect + 各业务系统自己的本地会话

十四、总结

Session 和 JWT 的核心区别不是“一个用 Cookie、一个不用 Cookie”,而是服务端是否为每个登录凭证维护可查询、可撤销的会话状态。

  • 服务端 Session:客户端保存随机 ID,服务端保存会话详情;撤销和权限变化容易,适合网站和后台,但需要共享存储和 CSRF 防护。
  • JWT:客户端保存带签名的声明,服务端可以本地验证;适合 API、移动端和服务间调用,但立即撤销、权限更新、密钥轮换和刷新机制更复杂。
  • Cookie 和 Authorization Header:是凭证传输方式,不决定凭证究竟是 Session ID 还是 JWT。
  • JWT 的无状态:指服务端不必为每个 Access Token 保存登录记录,不代表系统完全没有服务端状态。
  • 安全的关键:HTTPS、凭证保护、合理过期、撤销策略、服务端授权、密钥管理和完整的风控设计,而不是简单选择某个名词。

在大多数面向浏览器的自有网站中,先把 HttpOnly Cookie、服务端 Session、CSRF 和服务端授权做好,往往比为了“无状态”而引入复杂 JWT 体系更稳妥。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS