Tech-Paul

work hard, play hard

JWT(JSON Web Token) 是一种开放标准(RFC 7519),用于在网络应用环境间传递声明信息的安全方式。它由三部分组成:头部(Header)有效载荷(Payload)签名(Signature),通过使用基于 JSON 的格式来表示。

JWT 常用于身份验证和信息交换,在现代 Web 应用中,特别是在 API 和单点登录(SSO)中应用广泛。


JWT 的结构

JWT 是由三部分组成的字符串,中间用 . 分隔,格式如下:

1
<Header>.<Payload>.<Signature>

1. Header(头部)

Header 通常由两部分信息组成:令牌的类型(通常是 JWT)和所使用的签名算法(如 HS256RS256)。

示例

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}
  • alg:签名算法,表示如何加密 JWT,例如 HMAC SHA256 (HS256) 或 RSA SHA256 (RS256)。
  • typ:表示 JWT 类型,通常是 “JWT”。

这部分被 Base64Url 编码,形成 JWT 的第一部分。

2. Payload(有效载荷)

Payload 部分包含了传递的数据,也叫做“声明(claims)”。声明分为三种类型:

  • 注册声明(Registered claims):这些是 JWT 中的预定义字段,用于提供一些公共的、有用的信息。
    • iss(Issuer):签发人
    • sub(Subject):主题
    • aud(Audience):接收方
    • exp(Expiration Time):过期时间,UNIX 时间戳格式
    • nbf(Not Before):在此时间之前不可用
    • iat(Issued At):签发时间
    • jti(JWT ID):JWT 的唯一标识符
  • 公共声明(Public claims):可以自定义的信息,需要确保不会与其他方的声明冲突。
  • 私有声明(Private claims):双方自定义的声明,通常是用于在特定的应用中传递信息(如用户 ID、角色等)。

示例

1
2
3
4
5
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}

这部分数据也会被 Base64Url 编码,形成 JWT 的第二部分。

3. Signature(签名)

签名是用来验证消息的真实性和完整性,确保数据在传输过程中没有被篡改。签名是通过将前两部分(Header 和 Payload)与一个密钥结合,并使用指定的签名算法进行加密生成的。

生成签名的步骤

  1. 对 Header 和 Payload 使用 Base64Url 编码。
  2. 将编码后的 Header 和 Payload 用 . 连接。
  3. 使用密钥和指定的算法(如 HS256)对连接后的字符串进行签名。

示例

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

签名部分用于验证 JWT 是否未被篡改,并且可以验证发送者的身份。如果签名不匹配,说明数据被篡改或密钥不正确。


JWT 工作原理

  1. 用户登录
    • 用户向服务器提交用户名和密码。
    • 服务器验证用户身份后,生成一个 JWT 并将其返回给客户端。这个 JWT 中通常包含用户的 ID、角色信息以及过期时间等。
  2. 客户端存储
    • 客户端(如浏览器、移动设备等)将 JWT 存储在本地(通常是 localStoragesessionStorage)或 HTTP cookie 中。
  3. 客户端请求 API
    • 在每次请求时,客户端将 JWT 添加到请求头中,通常通过 Authorization 字段传递。
      1
      Authorization: Bearer <JWT>
  4. 服务器验证
    • 服务器接收到请求后,从请求头中提取 JWT。
    • 服务器使用预先存储的密钥(对于对称加密算法)或公钥(对于非对称加密算法)验证签名是否有效。如果有效,服务器将解码 JWT,获取其中的有效载荷,并根据这些信息处理请求(如获取用户信息、授权等)。
  5. 返回响应
    • 如果 JWT 验证通过,服务器返回请求的资源。如果验证失败,服务器返回 401 Unauthorized 错误。

JWT 的优点

  1. 无状态认证(Stateless Authentication)

    • 由于所有的信息都保存在 JWT 本身,服务器不需要存储会话信息,因此可以实现无状态认证。每个请求都可以通过 JWT 独立验证,简化了后端的状态管理。
  2. 跨平台支持

    • JWT 使用 JSON 格式,可以在不同的平台和编程语言中方便地解析和生成。
  3. 分布式系统友好

    • JWT 适用于分布式系统中,因为它不依赖于中央存储。每个微服务可以独立验证 JWT,而无需访问集中式数据库或会话存储。
  4. 灵活性和扩展性

    • JWT 支持自定义声明,可以根据需求存储额外的用户信息和应用上下文。

JWT 的缺点

  1. 无法撤销

    • 一旦生成 JWT,它将一直有效,直到过期。如果 JWT 被盗,攻击者可以在有效期内访问受保护的资源。没有内建的机制来撤销 JWT,因此需要其他机制(如黑名单)来处理这种情况。
  2. JWT 较大

    • 由于 JWT 中包含了用户信息,可能会导致其相对较大,尤其是在需要存储较多信息时。每个请求都要携带这个 token,可能会增加网络传输的开销。
  3. 密钥管理问题

    • 如果使用对称加密算法(如 HS256),密钥的泄露可能导致所有 JWT 的安全性受到威胁。如果使用非对称加密算法(如 RS256),密钥对的管理可能会更复杂。

JWT 使用场景

  1. 身份认证

    • 在用户登录时,服务器生成一个 JWT,并将其返回给客户端。之后,客户端每次向服务器发起请求时,都会携带 JWT,以进行身份验证。
  2. 信息交换

    • JWT 可以用来安全地在各方之间交换信息,JWT 的签名可以保证数据在传输过程中未被篡改。
  3. 单点登录(SSO)

    • 多个应用可以共享一个统一的 JWT 进行身份验证,实现跨域和跨平台的单点登录。

总结

JWT 是一种轻量级的、可扩展的方式,用于在分布式系统中传递身份验证信息和其他数据。它的优点在于能够实现无状态的认证,并且可以灵活地扩展和自定义。然而,它也有一些缺点,例如没有内置的撤销机制和较大的尺寸,使用时需要考虑这些问题。

1. 使用 console 进行调试

使用 console.log()console.error() 等打印输出调试信息。这种方法适用于快速查看变量值、函数执行情况等。

1
2
const a = 5
console.log(a) // 输出变量 a 的值

优点:

  • 简单直接,适用于快速调试。
  • 不需要任何额外的工具或配置。

缺点:

  • 不适用于复杂的调试需求,尤其是在调试异步代码时,可能很难跟踪。
  • 需要手动清除 console 语句,可能导致代码污染。

2. Node.js 内置调试器 (--inspect--inspect-brk)

Node.js 提供了内置的调试工具,可以通过命令行参数启用。这种方法非常强大,可以配合 Chrome DevTools 或 Visual Studio Code 使用。

启用调试:

  • **--inspect**:启动调试器,但程序会继续运行。
  • **--inspect-brk**:启动调试器并在第一行代码处暂停,适用于需要在代码开始运行时调试的场景。
1
2
3
node --inspect app.js
# 或者
node --inspect-brk app.js

启用调试后,Node.js 会在默认端口 9229 启动调试服务。你可以使用 Chrome 浏览器或其他支持的调试工具连接到该端口进行调试。

使用 Chrome DevTools:

  1. 打开 Chrome 浏览器,访问 chrome://inspect
  2. 点击 “Configure” 并确保 “localhost:9229” 已经添加。
  3. 在 “Remote Targets” 下找到你的 Node.js 程序,点击 “inspect” 进行调试。

使用 Visual Studio Code 调试:

  1. 在 VS Code 中,打开你的项目。
  2. .vscode 目录中创建一个 launch.json 配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/app.js"
}
]
}
  1. 使用 VS Code 的调试面板启动调试。

优点:

  • 通过 Chrome DevTools 或 VS Code 提供的强大功能,可以查看堆栈、变量、设置断点、逐步执行等。
  • 支持异步代码调试,能够方便地查看回调函数、Promise 等执行情况。

缺点:

  • 需要手动启动调试模式,不适合频繁使用。

3. 使用 debug 模块

debug 是一个非常流行的第三方库,可以帮助开发者在 Node.js 应用中轻松启用和禁用调试输出。它允许你通过命名空间控制日志输出,非常适用于复杂的应用。

安装:

1
npm install debug

使用:

1
2
3
4
5
6
7
8
9
10
11
const debug = require("debug")("myapp:server")
const http = require("http")

http
.createServer((req, res) => {
debug("Request received")
res.end("Hello, world!")
})
.listen(3000)

debug("Server is running on port 3000")

通过设置 DEBUG 环境变量,你可以控制哪些命名空间的调试信息会被打印出来。

1
DEBUG=myapp:* node app.js

优点:

  • 能够轻松控制日志输出,适合调试多个模块。
  • 可以启用不同的命名空间,帮助开发者更好地跟踪不同部分的执行情况。

缺点:

  • 需要手动引入 debug 库,可能会增加额外的依赖。

4. 使用 node-inspect(CLI 调试)

Node.js 自带的命令行调试工具 node-inspect 提供了一个交互式的调试体验。你可以通过它在命令行中调试 Node.js 程序。

启动调试:

1
node-inspect app.js

这将启动一个交互式调试器,你可以输入调试命令来逐步执行代码,检查变量等。

优点:

  • 适用于命令行界面中进行简单调试。
  • 不需要依赖其他工具或图形化界面。

缺点:

  • 命令行调试体验较差,不如图形化工具(如 Chrome DevTools)直观。

5. 使用 nodemon 自动重启

nodemon 是一个工具,用于自动检测代码更改并重启应用程序,特别适用于开发过程中的频繁调试。通过结合 nodemon 和调试工具(如 --inspect),可以让调试过程更加高效。

安装:

1
npm install -g nodemon

使用:

1
nodemon --inspect app.js

这样,nodemon 会在你修改代码时自动重启 Node.js 程序,并且仍然保持调试功能。

优点:

  • 自动检测文件变动,适合频繁修改代码时使用。
  • 能与 --inspect 一起工作,提供更高效的调试体验。

缺点:

  • 可能对性能有一定影响,尤其是在大型项目中。

6. 使用断点调试

在 Visual Studio Code 中,你可以通过设置断点来暂停代码的执行,检查当前堆栈、变量等信息。这是通过 launch.json 配置文件进行设置的。

设置断点:

  1. 在 VS Code 中打开文件。
  2. 在代码的行号上点击,设置断点。
  3. 启动调试,执行代码时会在断点处暂停。

优点:

  • 通过图形化界面管理断点,可以更直观地跟踪代码执行。
  • 适合复杂的调试需求,可以检查堆栈、变量、对象等。

缺点:

  • 需要使用专门的 IDE,如 Visual Studio Code。

7. 使用性能分析工具(例如 clinic.js

如果你需要分析 Node.js 应用的性能,clinic.js 是一个非常强大的工具,它可以帮助你分析 CPU 使用情况、内存使用情况、事件循环等。

安装:

1
npm install -g clinic

使用:

1
clinic doctor -- node app.js

这将启动性能分析,clinic 会生成一个报告,帮助你发现性能瓶颈。

优点:

  • 强大的性能分析工具,适用于大型项目的性能调优。
  • 可以帮助开发者了解 Node.js 应用的性能瓶颈。

缺点:

  • 主要用于性能分析,调试方面的功能较弱。

总结

在 Node.js 开发中,调试是一个非常重要的步骤,常用的调试方式有:

  1. 使用 console.log() 打印输出(适用于简单调试)。
  2. 使用 --inspect--inspect-brk 启动内置调试器(结合 Chrome DevTools 或 VS Code 使用)。
  3. 使用 debug 模块来控制日志输出。
  4. 使用命令行调试工具 node-inspect 进行交互式调试。
  5. 使用 nodemon 来实现代码更改时自动重启并保持调试功能。
  6. 在 IDE(如 VS Code)中设置断点进行调试。
  7. 使用 clinic.js 等工具进行性能分析。

选择合适的调试工具和方法,能够提高开发效率、帮助排查问题。

1. Node.js 基础

  • 事件驱动
    • 事件循环(Event Loop)
    • 非阻塞 I/O
  • 模块系统
    • CommonJS 模块
    • requiremodule.exports
    • ES 模块(import/export
  • 全局对象
    • global
    • process
    • Buffer
  • 核心模块
    • fs:文件系统
    • http/https:HTTP 服务器
    • path:路径处理
    • stream:流处理
    • events:事件触发器
    • child_process:子进程
    • cluster:集群

2. 异步编程

  • 回调函数
    • 回调地狱(Callback Hell)
    • 错误优先回调(Error-first Callback)
  • Promise
    • Promise
    • Promise.all/Promise.race
  • Async/Await
    • 异步函数
    • 错误处理(try/catch
  • 事件驱动
    • EventEmitter
    • 自定义事件

3. 网络编程

  • HTTP/HTTPS
    • 创建 HTTP 服务器
    • 处理请求和响应
    • RESTful API 设计
  • WebSocket
    • 实时通信
    • ws
  • TCP/UDP
    • net 模块
    • dgram 模块

4. 数据库

  • SQL 数据库
    • MySQL
    • PostgreSQL
  • NoSQL 数据库
    • MongoDB
    • Redis
  • ORM/ODM
    • Sequelize(SQL)
    • Mongoose(MongoDB)

5. 框架与工具

  • Express.js
    • 路由
    • 中间件
    • 错误处理
  • Koa.js
    • 中间件机制
    • 上下文(Context)
  • NestJS
    • 模块化
    • 依赖注入
  • 工具
    • NPM/Yarn
    • Nodemon
    • PM2

6. 性能优化

  • 事件循环优化
    • 避免阻塞操作
    • 使用 setImmediateprocess.nextTick
  • 集群与负载均衡
    • cluster 模块
    • 负载均衡策略
  • 缓存
    • Redis 缓存
    • 内存缓存(如 node-cache
  • 流处理
    • 可读流、可写流、双工流
    • 管道(pipe

7. 安全

  • 常见漏洞
    • XSS(跨站脚本攻击)
    • CSRF(跨站请求伪造)
    • SQL 注入
  • 安全实践
    • 使用 HTTPS
    • 输入验证与清理
    • 使用 Helmet 中间件
  • 认证与授权
    • JWT(JSON Web Token)
    • OAuth 2.0

8. 测试

  • 单元测试
    • Mocha
    • Jest
  • 集成测试
    • Supertest
  • 测试覆盖率
    • Istanbul(nyc
  • Mocking
    • Sinon.js

9. 部署与 DevOps

  • 部署
    • Docker 容器化
    • Kubernetes
  • CI/CD
    • Jenkins
    • GitHub Actions
  • 日志管理
    • Winston
    • Morgan
  • 监控
    • Prometheus
    • Grafana

10. 高级主题

  • 微服务架构
    • 服务发现
    • 消息队列(RabbitMQ、Kafka)
  • GraphQL
    • Apollo Server
    • 类型定义与解析器
  • Serverless
    • AWS Lambda
    • Google Cloud Functions
  • TypeScript
    • 类型系统
    • 与 Node.js 集成

11. 面试常见问题

  • Node.js 事件循环机制
  • 如何避免回调地狱?
  • Express 中间件的工作原理
  • 如何优化 Node.js 应用的性能?
  • 如何处理未捕获的异常?
  • 如何实现 JWT 认证?
  • 如何设计一个高并发的 Node.js 应用?

可读流

fs.createReadStream(‘large-file’).pipe(csv()).on().on

可写流

fs.createWriteStream(‘out.csv’)

  • 适用场景:小文件,读取后直接处理:
    const data = JSON.parse(fs.readFileSync(“file.json”, “utf8”));
    console.log(data);

读取大文件

1
2
3
4
5
6
7
8
9
10
11
const fs = require("fs");

const stream = fs.createReadStream("big-file.txt", { encoding: "utf8" });

stream.on("data", (chunk) => {
console.log("Received chunk:", chunk);
});

stream.on("end", () => {
console.log("File read completed.");
});

读取 + 处理数据流(管道)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const fs = require("fs");
const { Transform } = require("stream");

const readStream = fs.createReadStream("input.txt", { encoding: "utf8" });
const writeStream = fs.createWriteStream("output.txt");

const transformStream = new Transform({
transform(chunk, encoding, callback) {
const modifiedChunk = chunk.toString().toUpperCase(); // 转换数据
callback(null, modifiedChunk);
},
});

readStream.pipe(transformStream).pipe(writeStream);

writeStream.on("finish", () => console.log("File transformation completed."));

Node.js 进行 ETL(数据提取、转换、加载)

示例:从 CSV 读取数据 → 处理 → 存入数据库

1
2
3
4
5
6
7
8
9
10
fs.createReadStream("data.csv")
.pipe(csv())
.on("data", async (row) => {
const query = "INSERT INTO users (name, age) VALUES ($1, $2)";
await db.query(query, [row.name, row.age]); // 插入数据库
})
.on("end", () => {
console.log("CSV 数据导入完成");
db.end();
});

0%