2、用户认证机制:Session vs JWT,Cookie与的安全存储

用户认证,说白了就是回答三个问题:你是谁?你怎么证明?我怎么信你?

我做了这么多年安全架构,见过太多团队在认证这块踩坑。有的项目上线第一天就被拖库,有的因为泄露导致数据被爬。今天咱们就把Session和JWT这两个主流方案掰开揉碎,看看它们到底怎么选、怎么存才安全。

2.1 Session vs JWT:两种认证哲学

先看一张对比图,帮你快速建立整体认知。

Session vs JWT 认证流程对比 Session 模式 ① 用户登录 → 服务端创建Session ② Session ID 写入Cookie返回客户端 ③ 后续请求携带Cookie → 服务端查Session ④ 验证通过 → 返回业务数据 JWT 模式 ① 用户登录 → 服务端签发JWT ② JWT 返回客户端(Cookie/Header) ③ 后续请求携带JWT → 服务端验签 ④ 验签通过 → 解析Payload获取用户信息 核心区别:Session状态在服务端,JWT状态在客户端(自包含)

2.2 Session:服务端有状态

Session的原理很简单。用户登录成功后,服务端生成一个唯一ID,存到内存或Redis里。这个ID通过Cookie发给浏览器。下次请求时,浏览器自动带上Cookie,服务端查一下Session数据,就知道你是谁了。

我习惯把Session比作「酒店房卡」。房卡本身只是一串数字,真正的房间信息存在前台系统里。你拿着房卡去开门,前台先查系统确认你是302房的客人,才放你进去。

Session的优点

  • 服务端完全可控:想踢人下线?删掉Session就行。我在项目中遇到过紧急情况,发现某个用户账号被盗,直接Redis里del掉他的Session,对方立即失效。
  • Payload无大小限制:Session里存多少数据都行,反正不经过网络传输。
  • 实现简单:主流框架都有现成支持,Spring Session、Express Session,开箱即用。

Session的痛点

  • 扩展性差:多台服务器必须共享Session存储。要么用Redis集中管理,要么用粘性Session。我见过一个团队上线后才发现负载均衡没配Session共享,用户每次刷新都重新登录,那叫一个尴尬。
  • 移动端不友好:App原生请求不会自动管理Cookie,需要手动处理。
  • CSRF风险:Cookie自动携带的特性,容易被跨站请求伪造利用。

2.3 JWT:无状态的自包含令牌

JWT(JSON Web )的思路完全不同。它把用户信息加密签名后,直接发给客户端。服务端不再存任何会话数据。下次请求时,客户端把JWT带回来,服务端验签通过就直接解析出用户信息。

你想想看,这就像「身份证」。身份证上印着你的姓名、照片、有效期,任何机构只要验证防伪标识,就能确认你的身份,不需要打电话回公安局查。

JWT的结构

一个JWT由三部分组成,用点号分隔:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
部分 内容 说明
Header 签名算法、类型 Base64编码,可解码查看
Payload 用户ID、角色、过期时间等 Base64编码,不要放密码
Signature 对前两部分的签名 防止篡改,需要服务端密钥验证
⚠️ 重要提醒:Payload只是Base64编码,不是加密!任何拿到JWT的人都可以解码看到里面的内容。我曾经在代码审查时发现有人把用户密码直接塞进Payload,这等于把钥匙挂在门上。

JWT的优点

  • 天然支持分布式:服务端不需要共享存储,任何节点只要知道密钥就能验证。
  • 跨域友好:可以放在Authorization Header里,不受Cookie同源策略限制。
  • 适合移动端和微服务:App和微服务之间传递JWT非常方便。

JWT的痛点

  • 无法主动失效:JWT签发后,在过期之前一直有效。想踢人下线?除非维护黑名单,但那就又变成有状态了。
  • Payload体积大:每次请求都要携带,如果存太多信息,会显著增加网络开销。
  • 密钥管理复杂:签名密钥泄露等于全线失守。

2.4 Cookie与的安全存储

无论用Session还是JWT,最终都要把凭证存到客户端。怎么存才安全?这里面的坑可不少。

Cookie的安全属性

如果选择用Cookie存Session ID或JWT,以下四个属性必须配齐:

Set-Cookie: session_id=abc123; 
  HttpOnly;        // 禁止JavaScript读取,防止XSS窃取
  Secure;          // 仅HTTPS传输
  SameSite=Strict; // 禁止跨站请求携带,防止CSRF
  Path=/;          // 限定路径范围
  Max-Age=3600     // 设置过期时间
💡 我的习惯:生产环境一定开HttpOnly和Secure。SameSite建议用Lax模式,Strict模式对用户体验影响较大(比如从第三方链接跳转过来会丢失登录态)。

的存储方案

如果JWT放在Authorization Header里传输,客户端怎么存?

存储位置 优点 风险 推荐场景
localStorage 持久化,刷新不丢失 XSS可读取,无法HttpOnly 不推荐
sessionStorage 关闭标签页自动清除 XSS可读取,多标签不共享 低安全要求
HttpOnly Cookie XSS无法读取 需处理CSRF 推荐(Web端)
内存变量 最安全,不持久化 刷新页面丢失 高安全要求

我个人最推荐的做法是:JWT存HttpOnly Cookie + 额外CSRF 。这样既防XSS窃取,又防CSRF利用。移动端则用系统安全存储(iOS Keychain / Android EncryptedSharedPreferences)。

2.5 避坑指南

这些年我踩过的坑,列出来给大家提个醒:

  • JWT过期时间别设太长:我曾经见过有人设30天,结果泄露后整整一个月控制不了。建议Access 15分钟,Refresh 7天。
  • 不要在JWT里放敏感信息:Payload是明文,放手机号、邮箱都有风险。真要放,至少加密一下。
  • Session存储别用默认内存:生产环境一定要用Redis,否则重启服务所有用户都要重新登录。
  • Cookie的Domain别设太宽:设成.com会导致所有子域名共享Cookie,安全边界被打破。
  • 密钥定期轮换:JWT签名密钥至少每季度换一次,换完后旧要能平滑过渡。

总结一下:

  • 单体应用、需要服务端主动控制 → Session
  • 分布式、微服务、移动端 → JWT
  • 无论哪种,客户端存储都要用 HttpOnly + Secure + SameSite
  • 永远不要信任客户端传来的任何数据,验签和校验是底线