夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Node基础

  • 《MySQL》学习笔记

  • Midway

  • Nest

    • 开篇词
    • 学习理由
    • nest概念扫盲
    • 快速掌握 nestcli
    • 5种http数据传输方式
    • IoC 解决了什么痛点问题?
    • 如何调试 Nest 项目
    • Provider注入对象
    • 全局模块和生命周期
    • AOP 架构有什么好处?
    • 一网打尽 Nest 全部装饰器
    • Nest如何自定义装饰器
    • Metadata和Reflector
    • ExecutionContext切换上下文
    • Module和Provider的循环依赖处理
    • 如何创建动态模块
    • Nest和Express,fastify
    • Nest的Middleware
    • RxJS和Interceptor
    • 内置Pipe和自定义Pipe
    • ValidationPipe验证post请求参数
    • 如何自定义 Exception Filter
    • 图解串一串 Nest 核心概念
    • 接口如何实现多版本共存
    • Express如何使用multer实现文件上传
    • Nest使用multer实现文件上传
    • 图书管理系统
    • 大文件分片上传
    • 最完美的 OSS 上传方案
    • Nest里如何打印日志
    • 为什么Node里要用Winston打印日志
    • Nest 集成日志框架 Winston
    • 通过Desktop学Docker也太简单了
    • 你的第一个 Dockerfile
    • Nest 项目如何编写 Dockerfile
    • 提升 Dockerfile 水平的 5 个技巧
    • Docker 是怎么实现的
    • 为什么 Node 应用要用 PM2 来跑?
    • 快速入门 MySQL
    • SQL 查询语句的所有语法和函数
    • 一对一、join 查询、级联方式
    • 一对多、多对多关系的表设计
    • 子查询和 EXISTS
    • SQL 综合练习
    • MySQL 的事务和隔离级别
    • MySQL 的视图、存储过程和函数
    • Node 操作 MySQL 的两种方式
    • 快速掌握 TypeORM
    • TypeORM 一对一的映射和关联 CRUD
    • TypeORM 一对多的映射和关联 CRUD
    • TypeORM 多对多的映射和关联 CRUD
    • 在 Nest 里集成 TypeORM
    • TypeORM保存任意层级的关系
    • 生产环境为什么用TypeORM的migration迁移功能
    • Nest 项目里如何使用 TypeORM 迁移
    • 如何动态读取不同环境的配置?
    • 快速入门 Redis
    • 在 Nest 里操作 Redis
    • 为什么不用 cache-manager 操作 Redis
    • 两种登录状态保存方式:JWT、Session
      • 服务端存储的 session + cookie
        • CSRF
        • 分布式 session
        • 跨域
      • 客户端存储的 token
        • 安全性
        • 性能
        • 没法让 JWT 失效
      • 总结
    • Nest 里实现 Session 和 JWT
    • MySQL + TypeORM + JWT 实现登录注册
    • 基于 ACL 实现权限控制
    • 基于 RBAC 实现权限控制
    • access_token和refresh_token实现无感登录
    • 单token无限续期实现登录无感刷新
    • 使用 passport 做身份认证
    • passport 实现 GitHub 三方账号登录
    • passport 实现 Google 三方账号登录
    • 为什么要使用 Docker Compose ?
    • Docker 容器通信的最简单方式:桥接网络
    • Docker 支持重启策略,是否还需要 PM2
    • 快速掌握 Nginx 的 2 大核心用法
    • 基于 Nginx 实现灰度系统
    • 基于 Redis 实现分布式 session
    • Redis + 高德地图,实现附近的充电宝
    • 用 Swagger 自动生成 api 文档
    • 如何灵活创建 DTO
    • class- validator 的内置装饰器,如何自定义装饰器
    • 序列化 Entity,你不需要 VO 对象
    • 手写序列化 Entity 的拦截器
    • 使用 compodoc 生成文档
    • Node 如何发邮件?
    • 实现基于邮箱验证码的登录
    • 基于 sharp 实现 gif 压缩工具
    • 大文件如何实现流式下载?
    • Puppeteer 实现爬虫,爬取 BOSS 直聘全部前端岗位
    • 实现扫二维码登录
    • Nest 的 REPL 模式
    • 实现 Excel 导入导出
    • 如何用代码动态生成 PPT
    • 如何拿到服务器 CPU、内存、磁盘状态
    • Nest 如何实现国际化?
    • 会议室预订系统:需求分析和原型图
    • 会议室预订系统:技术方案和数据库设计
    • 会议室预订系统:用户管理模块--用户注册
    • 会议室预订系统:用户管理模块--配置抽离、登录认证鉴权
    • 会议室预订系统:用户管理模块-- interceptor、修改信息接口
    • 会议室预订系统:用户管理模块--用户列表和分页查询
    • 会议室预订系统:用户管理模块-- swagger 接口文档
    • 会议室预订系统:用户管理模块-- 用户端登录注册页面
    • 会议室预订系统:用户管理模块-- 用户端信息修改页面
    • 会议室预订系统:用户管理模块-- 头像上传
    • 会议室预订系统:用户管理模块-- 管理端用户列表页面
    • 会议室预订系统:用户管理模块-- 管理端信息修改页面
    • 会议室预订系统:会议室管理模块-后端开发
    • 会议室预订系统:会议室管理模块-管理端前端开发
    • 会议室预订系统:会议室管理模块-用户端前端开发
    • 会议室预订系统:预定管理模块-后端开发
    • 会议室预订系统:预定管理模块-管理端前端开发
    • 会议室预订系统:预定管理模块-用户端前端开发
    • 会议室预订系统:统计管理模块-后端开发
    • 会议室预订系统:统计管理模块-前端开发
    • 会议室预订系统:后端项目部署到阿里云
    • 会议室预订系统:前端项目部署到阿里云
    • 会议室预定系统:用 migration 初始化表和数据
    • 会议室预定系统:文件上传 OSS
    • 会议室预定系统:Google 账号登录后端开发
    • 会议室预定系统:Google 账号登录前端开发
    • 会议室预定系统:后端代码优化
    • 会议室预定系统:集成日志框架 winston
    • 会议室预定系统:前端代码优化
    • 会议室预定系统:全部功能测试
    • 会议室预定系统:项目总结
    • Nest 如何创建微服务?
    • Nest 的 Monorepo 和 Library
    • 用 Etcd 实现微服务配置中心和注册中心
    • Nest 集成 Etcd 做注册中心、配置中心
    • 用 Nacos 实现微服务配置中心和注册中心
    • 基于 gRPC 实现跨语言的微服务通信
    • 快速入门 ORM 框架 Prisma
    • Prisma 的全部命令
    • Prisma 的全部 schema 语法
    • Primsa Client 单表 CRUD 的全部 api
    • Prisma Client 多表 CRUD 的全部 api
    • 在 Nest 里集成 Prisma
    • 为什么前端监控系统要用 RabbitMQ?
    • 基于 Redis 实现关注关系
    • 基于 Redis 实现各种排行榜(周榜、月榜、年榜)
    • 考试系统:需求分析
    • 考试系统:技术方案和数据库设计
    • 考试系统:微服务、Lib 拆分
    • 考试系统;用户注册
    • 考试系统:用户登录、修改密码
    • 考试系统:考试微服务
    • 考试系统:登录、注册页面
    • 考试系统:修改密码、试卷列表页面
    • 考试系统:新增试卷、回收站
    • 考试系统:试卷编辑器
    • 考试系统:试卷回显、预览、保存
    • 考试系统:答卷微服务
    • 考试系统:答题页面
    • 考试系统:自动判卷
    • 考试系统:分析微服务、排行榜页面
    • 考试系统:整体测试
    • 考试系统:项目总结
    • 用 Node.js 手写 WebSocket 协议
    • Nest 开发 WebSocket 服务
    • 基于 Socket.io 的 room 实现群聊
    • 聊天室:需求分析和原型图
    • 聊天室:技术选型和数据库设计
    • 聊天室:用户注册
    • 聊天室:用户登录
    • 聊天室:修改密码、修改信息
    • 聊天室:好友列表、发送好友申请
    • 聊天室:创建聊天室、加入群聊
    • 聊天室:登录、注册页面开发
    • 聊天室:修改密码、信息页面开发
    • 聊天室:头像上传
    • 聊天室:好友∕群聊列表页面
    • 聊天室:添加好友弹窗、通知页面
    • 聊天室:聊天功能后端开发
    • 聊天室:聊天功能前端开发
    • 聊天室:一对一聊天
    • 聊天室:创建群聊、进入群聊
    • 聊天室:发送表情、图片、文件
    • 聊天室:收藏
    • 聊天室:全部功能测试
    • 聊天室:项目总结
    • MongoDB 快速入门
    • 使用 mongoose 操作 MongoDB 数据库
    • GraphQL 快速入门
    • Nest 开发 GraphQL 服务:实现 CRUD
    • GraphQL + Primsa + React 实现 TodoList
    • 如何调试 Nest 源码?
  • 其他

  • 服务端
  • Nest
神说要有光
2025-03-10
目录

两种登录状态保存方式:JWT、Session

基本所有网站都有登录功能,登录之后再次请求依然是登录状态。

但 http 是无状态的,也就是说上一次请求和下一次请求之间没有任何关联。

那如何实现的这种登录状态的保存呢?

这个问题的解决有两种方案:

  • 服务端存储的 session + cookie 的方案
  • 客户端存储的 jwt token 的方案

但这两种方式也都有各自的缺点。

# 服务端存储的 session + cookie

给 http 添加状态,那就给每个请求打上个标记,然后在服务端存储这个标记对应的数据。这样每个被标记的请求都可以找到对应的数据,自然可以做到登录、权限等状态的存储。

这个标记应该是自动带上的,所以 http 设计了 cookie 的机制,在里面存储的数据是每次请求都会带上的。

然后根据 cookie 里的标记去查找的服务端对应的数据叫做 session,这个标记就是 session 的 id。

如图,因为请求自动带上 cookie,那两次请求就都可以找到 id 为 1 对应的 session,自然就知道当前登录的用户是谁,也可以存储其他的状态数据。

这就是 session + cookie 的给 http 添加状态的方案。

大家觉得这种方案有问题么?

有问题,而且问题还挺多的。

最大的一个问题就是臭名昭著的 CSRF(跨站请求伪造):

# CSRF

因为 cookie 会在请求时自动带上,那你在一个网站登录了,再访问别的网站,万一里面有个按钮会请求之前那个网站的,那 cookie 依然能带上。而这时候就不用再登录了。

这样万一点了这个按钮之后做了一些危险的操作呢?

是不是就很危险。

而且一般这种利用 CSRF 漏洞的网站都会伪装的很好,让你很难看出破绽来,这种网站叫做钓鱼网站。

为了解决这个问题,我们一般会验证 referer,就是请求是哪个网站发起的,如果发起请求的网站不对,那就阻止掉。

但这样依然不能完全解决问题,万一你用的浏览器也是有问题的,能伪造 referer 呢?

所以一般会用随机值来解决,每次随机生成一个值返回,后面再发起的请求需要包含这个值才行,否则就认为是非法的。

这个随机值叫做 token,可以放在参数中,也可以放在 header 中,因为钓鱼网站拿不到这个随机值,就算带了 cookie 也没发通过服务端的验证。

这是 session + cookie 这种方案的一个缺点,但是是有解决方案的。

它还有别的缺点,比如分布式的时候:

# 分布式 session

session 是把状态数据保存在服务端,那么问题来了,如果有多台服务器呢?

当并发量上去了,单台服务器根本承受不了,自然需要做集群,也就需要多台服务器来提供服务。

而且现在后端还会把不同的功能拆分到不同的服务中,也就是微服务架构,自然也需要多台服务器。

那不同服务器之间的 session 怎么同步?

登录之后 session 是保存在某一台服务器的,之后可能会访问到别的服务器,这时候那台服务器是没有对应的 session 的,就没法完成对应的功能。

这个问题的解决有两种方案:

一种是 session 复制,也就是通过一种机制在各台机器自动复制 session,并且每次修改都同步下。这个有对应的框架来做,比如 java 的 spring-session。

各台服务器都做了 session 复制了,那你访问任何一台都能找到对应的 session。

还有一种方案是把 session 保存在 redis,这样每台服务器都去那里查,只要一台服务器登录了,其他的服务器也就能查到 session,这样就不需要复制了。

分布式会话的场景,redis + session 的方案更常用一点。

还好,session 在分布式时的这个问题也算是有解决方案的。

但你你以为这就完了么?session + cookie 还有跨域的问题:

# 跨域

cookie 为了安全,是做了 domain 的限制的,设置 cookie 的时候会指定一个 domain,只有这个 domain 的请求才会带上这个 cookie。

而且还可以设置过期时间、路径等:

那万一是不同 domain 的请求呢?也就是跨域的时候,怎么带 cookie 呢?

a.xxx.com 和 b.xxx.com 这种还好,只要把 domain 设置为顶级域名 xxx.com 就可以了,那二三级域名不同也能自动带上。

但如果顶级域名也不同就没办法了,这种只能在服务端做下中转,把这俩个域名统一成同一个。

上面说的不是 ajax 请求,ajax 请求有额外的机制:

ajax 请求跨域的时候是不会挟带 cookie 的,除非手动设置 withCredentials 为 true 才可以。

而且也要求后端代码设置了对应的 header:

Access-Control-Allow-Origin: "当前域名";
Access-Control-Allow-Credentials: true

这里的 allow origin 设置 * 都不行,必须指定具体的域名才能接收跨域 cookie。

这是 session + cookie 方式的第三个坑,好在也是有解决方案的。

我们做下小结:

session + cookie 的给 http 添加状态的方案是服务端保存 session 数据,然后把 id 放入 cookie 返回,cookie 是自动携带的,每个请求可以通过 cookie 里的 id 查找到对应的 session,从而实现请求的标识。这种方案能实现需求,但是有 CSRF、分布式 session、跨域等问题,不过都是有解决方案的。

session + cookie 的方案确实不太完美,我们再来看另一种方式怎么样:

# 客户端存储的 token

session + cookie 的方案是把状态数据保存在服务端,再把 id 保存在 cookie 里来实现的。

既然这样的方案有那么多的问题,那我反其道而行之,不把状态保存在服务端了,直接全部放在请求里,也不放在 cookie 里了,而是放在 header 里,这样是不是就能解决那一堆问题了呢?

token 的方案常用 json 格式来保存,叫做 json web token,简称 JWT。

JWT 是保存在 request header 里的一段字符串(比如用 header 名可以叫 authorization),它分为三部分:

如图 JWT 是由 header、payload、verify signature 三部分组成的:

header 部分保存当前的加密算法,payload 部分是具体存储的数据,verify signature 部分是把 header 和 payload 还有 salt 做一次加密之后生成的。(salt,盐,就是一段任意的字符串,增加随机性)

这三部分会分别做 Base64,然后连在一起就是 JWT 的 header,放到某个 header 比如 authorization 中:

authorization: Bearer xxxxx.xxxxx.xxxx

请求的时候把这个 header 带上,服务端就可以解析出对应的 header、payload、verify signature 这三部分,然后根据 header 里的算法也对 header、payload 加上 salt 做一次加密,如果得出的结果和 verify signature 一样,就接受这个 token。

把状态数据都保存在 payload 部分,这样就实现了有状态的 http:

而且这种方式是没有 session + cookie 那些问题的,不信我们分别来看一下:

CSRF:因为不是通过自动带的 cookie 来关联服务端的 session 保存的状态,所以没有 CSRF 问题。

分布式 session: 因为状态不是保存在服务端,所以无论访问哪台服务器都行,只要能从 token 里解析出状态数据就行。

跨域:因为不是 cookie 那一套,自然也没有跨域的限制,只要手动带上 JWT 的 header 就行。

看起来这种方式好像很完美?

其实也不是,JWT 有 JWT 的问题:

# 安全性

因为 JWT 把数据直接 Base64 之后就放在了 header 里,那别人就可以轻易从中拿到状态数据,比如用户名等敏感信息,也能根据这个 JWT 去伪造请求。

所以 JWT 要搭配 https 来用,让别人拿不到 header。

# 性能

JWT 把状态数据都保存在了 header 里,每次请求都会带上,比起只保存个 id 的 cookie 来说,请求的内容变多了,性能也会差一些。

所以 JWT 里也不要保存太多数据。

# 没法让 JWT 失效

session 因为是存在服务端的,那我们就可以随时让它失效,而 JWT 不是,因为是保存在客户端,那我们是没法手动让他失效的。

比如踢人、退出登录、改完密码下线这种功能就没法实现。

但也可以配合 redis 来解决,记录下每个 token 对应的生效状态,每次先去 redis 查下 jwt 是否是可用的,这样就可以让 jwt 失效。

所以说,JWT 的方案虽然解决了很多 session + cookie 的问题,但也不完美。

小结下:

JWT 的方案是把状态数据保存在 header 里,每次请求需要手动携带,没有 session + cookie 方案的 CSRF、分布式、跨域的问题,但是也有安全性、性能、没法控制等问题。

# 总结

http 是无状态的,也就是请求和请求之间没有关联,但我们很多功能的实现是需要保存状态的。

给 http 添加状态有两种方式:

session + cookie:把状态数据保存到服务端,session id 放到 cookie 里返回,这样每次请求会带上 cookie ,通过 id 来查找到对应的 session。这种方案有 CSRF、分布式 session、跨域的问题。

jwt:把状态保存在 json 格式的 token 里,放到 header 中,需要手动带上,没有 cookie + session 的那些问题,但是也有安全性、性能、没法手动控制失效的问题。

上面这两种方案都不是完美的,但那些问题也都有解决方案。

常用的方案基本是 session + redis、jwt + redis 这种。

软件领域很多情况下都是这样的,某种方案都解决了一些问题,但也相应的带来了一些新的问题。没有银弹,还是要熟悉它们的特点,根据不同的需求灵活选用。

编辑 (opens new window)
上次更新: 2025/9/9 09:58:02
为什么不用 cache-manager 操作 Redis
Nest 里实现 Session 和 JWT

← 为什么不用 cache-manager 操作 Redis Nest 里实现 Session 和 JWT→

最近更新
01
UNiAPP中使用虚拟列表
01-09
02
uni统计使用
11-12
03
原生三方SDK集成探索
11-12
更多文章>
Copyright © 2019-2026 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式