diff --git "a/\346\235\216\346\231\250\346\273\224/20260417-\346\212\212nodejs\351\203\250\347\275\262\345\210\260\346\234\215\345\212\241\345\231\250\344\270\212.md" "b/\346\235\216\346\231\250\346\273\224/20260417-\346\212\212nodejs\351\203\250\347\275\262\345\210\260\346\234\215\345\212\241\345\231\250\344\270\212.md" new file mode 100644 index 0000000000000000000000000000000000000000..6e9bf3bff0cf2e124e1e268db06d2411e561cf40 --- /dev/null +++ "b/\346\235\216\346\231\250\346\273\224/20260417-\346\212\212nodejs\351\203\250\347\275\262\345\210\260\346\234\215\345\212\241\345\231\250\344\270\212.md" @@ -0,0 +1,774 @@ +# JWT身份验证系统进阶实现 + +## 技术概述 + +**JWT(JSON Web Token)**是一种基于令牌的身份验证机制,通过数字签名确保信息的完整性和真实性,广泛应用于现代Web应用的身份验证系统。 + +## 核心组件 + +| 组件 | 功能 | +| ------------- | ---------------------- | +| **Header** | 包含令牌类型和签名算法 | +| **Payload** | 存储用户信息和声明 | +| **Signature** | 验证令牌的完整性 | + +## 实施步骤 + +### 步骤一:环境搭建与基础实现 + +1. **项目初始化** + +```bash +# 创建项目目录 +mkdir jwt-demo +cd jwt-demo + +# 初始化项目 +npm init -y + +# 安装JWT库 +npm install jsonwebtoken +``` + +2. **核心JWT工具实现** + +创建 `src/jwt.js`: + +```javascript +const jwt = require("jsonwebtoken"); + +// 密钥(实际项目中应存储在环境变量中) +const SECRET = "my-secret-key"; + +/** + * 生成JWT令牌 + * @param {Object} user 用户信息 + * @returns {string} JWT令牌 + */ +function generateToken(user) { + return jwt.sign( + { + id: user.id, + username: user.username, + role: user.role, + }, + SECRET, + { expiresIn: "1h" }, // 令牌有效期 + ); +} + +/** + * 验证JWT令牌 + * @param {string} token JWT令牌 + * @returns {Object|null} 解码后的用户信息或null + */ +function verifyToken(token) { + try { + return jwt.verify(token, SECRET); + } catch (err) { + return null; + } +} + +// 测试代码 +const user = { id: 1, username: "zhangsan", role: "admin" }; +const token = generateToken(user); +console.log("生成的Token:", token); + +const decoded = verifyToken(token); +console.log("验证结果:", decoded); + +module.exports = { generateToken, verifyToken, SECRET }; +``` + +3. **测试运行** + +```bash +node src/jwt.js +``` + +### 步骤二:认证中间件实现 + +1. **创建认证中间件** + +创建 `src/middleware/auth.js`: + +```javascript +const jwt = require("jsonwebtoken"); + +const SECRET = "my-secret-key"; + +/** + * JWT认证中间件 + * @param {Object} req 请求对象 + * @param {Object} res 响应对象 + * @param {Function} next 下一个中间件 + */ +function authenticateToken(req, res, next) { + // 从Authorization header获取令牌 + const authHeader = req.headers["authorization"]; + const token = authHeader && authHeader.split(" ")[1]; + + // 令牌不存在 + if (!token) { + return res.status(401).json({ error: "未提供认证令牌" }); + } + + // 验证令牌 + jwt.verify(token, SECRET, (err, user) => { + if (err) { + return res.status(403).json({ error: "令牌无效或已过期" }); + } + + // 将用户信息添加到请求对象 + req.user = user; + next(); + }); +} + +module.exports = { authenticateToken, SECRET }; +``` + +2. **Express应用实现** + +创建 `src/app.js`: + +```javascript +const express = require("express"); +const jwt = require("jsonwebtoken"); +const { authenticateToken, SECRET } = require("./middleware/auth"); + +const app = express(); +app.use(express.json()); + +// 模拟用户数据(实际项目中应存储在数据库) +const users = [ + { id: 1, username: "admin", password: "123456", role: "admin" }, + { id: 2, username: "user", password: "123456", role: "user" }, +]; + +/** + * 登录接口 + * POST /login + */ +app.post("/login", (req, res) => { + const { username, password } = req.body; + + // 验证用户凭据 + const user = users.find( + (u) => u.username === username && u.password === password, + ); + + if (!user) { + return res.status(401).json({ error: "用户名或密码错误" }); + } + + // 生成JWT令牌 + const token = jwt.sign( + { id: user.id, username: user.username, role: user.role }, + SECRET, + { expiresIn: "1h" }, + ); + + res.json({ token }); +}); + +/** + * 获取当前用户信息(需要认证) + * GET /api/user + */ +app.get("/api/user", authenticateToken, (req, res) => { + res.json({ user: req.user }); +}); + +/** + * 获取所有用户(需要认证) + * GET /api/users + */ +app.get("/api/users", authenticateToken, (req, res) => { + // 移除密码字段后返回 + res.json(users.map(({ password, ...u }) => u)); +}); + +// 启动服务器 +app.listen(3000, () => { + console.log("服务器运行在 http://localhost:3000"); +}); +``` + +3. **测试认证流程** + +```bash +# 启动服务器 +node src/app.js + +# 登录获取令牌 +curl -X POST http://localhost:3000/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"123456"}' + +# 使用令牌访问受保护资源 +curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:3000/api/user +``` + +### 步骤三:完整认证系统实现 + +**技术要点**:实现Access Token和Refresh Token机制,提高安全性 + +1. **增强版认证系统** + +修改 `src/app.js`: + +```javascript +const express = require("express"); +const jwt = require("jsonwebtoken"); +const { authenticateToken, SECRET } = require("./middleware/auth"); + +const app = express(); +app.use(express.json()); + +// 模拟数据库 +let users = [ + { id: 1, username: "admin", password: "123456", role: "admin" }, + { id: 2, username: "user", password: "123456", role: "user" }, +]; + +/** + * 登录接口 + * POST /login + */ +app.post("/login", (req, res) => { + const { username, password } = req.body; + const user = users.find( + (u) => u.username === username && u.password === password, + ); + + if (!user) { + return res.status(401).json({ error: "用户名或密码错误" }); + } + + // 生成访问令牌(短期) + const accessToken = jwt.sign( + { id: user.id, username: user.username, role: user.role }, + SECRET, + { expiresIn: "15m" }, // 15分钟有效期 + ); + + // 生成刷新令牌(长期) + const refreshToken = jwt.sign( + { id: user.id }, + SECRET + "_refresh", // 使用不同的密钥 + { expiresIn: "7d" }, // 7天有效期 + ); + + res.json({ accessToken, refreshToken }); +}); + +/** + * 刷新令牌接口 + * POST /refresh + */ +app.post("/refresh", (req, res) => { + const { refreshToken } = req.body; + + if (!refreshToken) { + return res.status(401).json({ error: "未提供刷新令牌" }); + } + + // 验证刷新令牌 + jwt.verify(refreshToken, SECRET + "_refresh", (err, decoded) => { + if (err) { + return res.status(403).json({ error: "刷新令牌无效" }); + } + + // 查找用户 + const user = users.find((u) => u.id === decoded.id); + if (!user) { + return res.status(404).json({ error: "用户不存在" }); + } + + // 生成新的访问令牌 + const newAccessToken = jwt.sign( + { id: user.id, username: user.username, role: user.role }, + SECRET, + { expiresIn: "15m" }, + ); + + res.json({ accessToken: newAccessToken }); + }); +}); + +/** + * 受保护的资源 + * GET /api/protected + */ +app.get("/api/protected", authenticateToken, (req, res) => { + res.json({ + message: "这是受保护的资源", + user: req.user, + }); +}); + +// 启动服务器 +app.listen(3000, () => { + console.log("服务器运行在 http://localhost:3000"); +}); +``` + +2. **测试刷新令牌功能** + +```bash +# 登录获取令牌 +RESPONSE=$(curl -s -X POST http://localhost:3000/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"123456"}') + +# 提取访问令牌 +ACCESS_TOKEN=$(echo $RESPONSE | jq -r .accessToken) +REFRESH_TOKEN=$(echo $RESPONSE | jq -r .refreshToken) + +# 使用访问令牌访问受保护资源 +curl -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost:3000/api/protected + +# 使用刷新令牌获取新的访问令牌 +NEW_TOKEN=$(curl -s -X POST http://localhost:3000/refresh \ + -H "Content-Type: application/json" \ + -d '{"refreshToken":"'$REFRESH_TOKEN'"}' | jq -r .accessToken) + +# 使用新令牌访问 +curl -H "Authorization: Bearer $NEW_TOKEN" http://localhost:3000/api/protected +``` + +## 安全最佳实践 + +### 1. 密钥管理 + +- **环境变量存储**:将JWT密钥存储在环境变量中 +- **密钥轮换**:定期更换JWT密钥 +- **密钥强度**:使用足够长度和复杂度的密钥 + +### 2. 令牌安全 + +- **合理的过期时间**:Access Token短期,Refresh Token长期 +- **HTTPS传输**:确保令牌在HTTPS环境下传输 +- **令牌撤销**:实现令牌黑名单机制 + +### 3. 输入验证 + +- **请求验证**:验证所有用户输入 +- **参数校验**:使用中间件验证请求参数 +- **防止注入**:使用参数化查询 + +### 4. 错误处理 + +- **统一错误响应**:标准化错误响应格式 +- **日志记录**:记录认证失败和异常 +- **安全异常处理**:避免泄露敏感信息 + +## 完整项目结构 + +``` +jwt-demo/ +├── src/ +│ ├── middleware/ +│ │ └── auth.js # 认证中间件 +│ ├── utils/ +│ │ └── jwt.js # JWT工具 +│ ├── controllers/ +│ │ └── authController.js # 认证控制器 +│ ├── routes/ +│ │ └── authRoutes.js # 认证路由 +│ └── app.js # 应用入口 +├── config/ +│ └── config.js # 配置文件 +├── package.json +└── .env # 环境变量 +``` + +## 性能优化 + +1. **令牌验证优化**:使用缓存存储已验证的令牌 +2. **数据库查询优化**:使用索引加速用户查询 +3. **中间件优化**:减少认证中间件的计算开销 +4. **并发处理**:合理配置服务器并发连接数 + +## 总结 + +JWT身份验证系统是现代Web应用的标准认证方案,通过无状态设计和数字签名机制,提供了安全、高效的身份验证解决方案。本实现涵盖了从基础JWT生成到完整认证系统的各个环节,包括令牌管理、刷新机制和安全最佳实践,可作为实际项目的参考实现。 + +**核心优势**: + +- 无状态设计,易于水平扩展 +- 自包含信息,减少数据库查询 +- 标准协议,跨平台兼容 +- 灵活的过期和刷新机制 + +**应用场景**: + +- 单页应用(SPA) +- 移动应用API +- 微服务架构 +- 第三方集成 + +### 5.2 常用代码 + +``` +// 生成 +jwt.sign(payload, secret, { expiresIn: '7d' }); + +// 验证 +jwt.verify(token, secret); + +// 解析 +jwt.decode(token); +``` + +# Node.js项目服务器部署指南 + +## 技术概述 + +本指南详细介绍如何在Linux服务器上部署Node.js项目,包括环境搭建、项目部署、反向代理配置等完整流程,适用于生产环境的Node.js应用部署。 + +## 准备工作 + +### 环境要求 + +- **本地环境**:安装SSH客户端工具(Windows:PowerShell/Xshell/FinalShell;Mac/Linux:终端) +- **服务器环境**:Linux(Debian/Ubuntu)服务器,具备公网IP +- **网络要求**:服务器可正常连接外网 +- **权限要求**:拥有root或sudo权限的登录账号 + +## 服务器连接与初始化 + +### 远程连接服务器 + +```bash +# SSH连接服务器 +ssh root@服务器公网IP + +# 示例: +# ssh root@192.168.1.100 +``` + +**连接说明**: + +- 首次连接需确认主机指纹,输入`yes`回车 +- 输入密码时无显示,输入完成后回车 + +### 系统依赖更新 + +```bash +# 更新系统软件包 +apt update && apt upgrade -y +``` + +**命令说明**: + +- `apt update`:更新软件包索引 +- `apt upgrade`:升级已安装的软件包 +- `-y`:自动确认所有提示 + +## Node.js环境搭建 + +### 安装nvm(Node Version Manager) + +nvm是Node.js版本管理工具,支持多版本切换,推荐作为首选安装方式 + +#### 下载安装脚本 + +```bash +# 执行官方安装脚本 +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash + +# 若缺少curl,先安装: +# apt install curl -y +``` + +#### 配置环境变量 + +```bash +# 使nvm命令生效 +source ~/.bashrc +``` + +#### 验证安装 + +```bash +# 查看nvm版本 +nvm --version + +# 输出版本号表示安装成功 +# 示例:0.39.7 +``` + +### 安装Node.js + +#### 查看可用版本 + +```bash +# 查看所有可安装的Node.js版本 +nvm ls-remote + +# 推荐安装LTS(长期支持)版本 +``` + +#### 安装LTS版本 + +```bash +# 安装最新LTS版本 +nvm install --lts + +# 安装指定版本(示例) +# nvm install 20.10.0 +``` + +#### 设置默认版本 + +```bash +# 设置默认Node.js版本 +nvm alias default 20.10.0 + +# 验证默认版本 +node -v +``` + +### 验证安装 + +```bash +# 查看Node.js版本 +node -v + +# 查看npm版本 +npm -v +``` + +### 配置npm镜像源 + +```bash +# 配置淘宝镜像源(提升国内下载速度) +npm config set registry https://registry.npmmirror.com/ + +# 验证配置 +npm config get registry +``` + +### nvm常用命令 + +| 命令 | 功能 | +| ---------------------- | --------------- | +| `nvm ls` | 查看已安装版本 | +| `nvm use <版本>` | 切换Node.js版本 | +| `nvm uninstall <版本>` | 卸载指定版本 | +| `nvm help` | 查看所有命令 | + +## 项目部署 + +### 上传项目文件 + +**方法1:使用SCP命令** + +```bash +# 本地终端执行 +scp -r /本地项目路径 root@服务器IP:/目标路径 + +# 示例: +# scp -r /home/user/project root@192.168.1.100:/root/project +``` + +**方法2:使用SFTP工具** + +- FinalShell、Xshell等工具支持拖拽上传 + +### 安装项目依赖 + +```bash +# 进入项目目录 +cd /root/project + +# 安装依赖 +npm install +``` + +### 后台运行项目 + +使用pm2进程管理工具确保项目稳定运行 + +```bash +# 全局安装pm2 +npm install pm2 -g + +# 启动项目 +pm start # 或 pm2 start app.js + +# 查看运行状态 +pm ls +``` + +### pm2常用命令 + +| 命令 | 功能 | +| ------------------------- | ---------------- | +| `pm2 ls` | 查看运行中的项目 | +| `pm2 restart <项目名/ID>` | 重启项目 | +| `pm2 stop <项目名/ID>` | 停止项目 | +| `pm2 startup` | 设置开机自启 | +| `pm2 logs` | 查看项目日志 | + +## Nginx反向代理配置 + +### 安装Nginx + +```bash +# 安装Nginx +apt install nginx -y + +# 验证安装 +nginx -v +``` + +### Nginx服务管理 + +```bash +# 启动Nginx +systemctl start nginx + +# 停止Nginx +systemctl stop nginx + +# 重启Nginx +systemctl restart nginx + +# 重新加载配置(推荐) +systemctl reload nginx + +# 设置开机自启 +systemctl enable nginx + +# 查看状态 +systemctl status nginx +``` + +### 配置反向代理 + +#### 创建配置文件 + +```bash +# 进入配置目录 +cd /etc/nginx/sites-available/ + +# 创建项目配置文件 +vim node-project +``` + +#### 编写配置内容 + +```nginx +server { + listen 80; # 监听HTTP默认端口 + server_name 服务器公网IP或域名; # 例如:example.com + + # 反向代理配置 + location / { + proxy_pass http://127.0.0.1:3000; # 转发到Node.js端口 + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + +#### 启用配置 + +```bash +# 创建软链接 +ln -s /etc/nginx/sites-available/node-project /etc/nginx/sites-enabled/ + +# 验证配置 +nginx -t + +# 重新加载配置 +systemctl reload nginx +``` + +### 防火墙配置 + +```bash +# 安装防火墙工具(如需) +apt install ufw -y + +# 开放HTTP端口 +ufw allow 80/tcp + +# 开放Node.js端口(如需) +ufw allow 3000/tcp + +# 查看防火墙状态 +ufw status +``` + +## 部署验证 + +1. **启动服务**:确保Node.js项目通过pm2正常运行 +2. **检查Nginx**:确认Nginx服务运行且配置正确 +3. **访问测试**:在浏览器中输入服务器公网IP或域名 +4. **验证结果**:正常显示项目页面即部署成功 + +## 常见问题排查 + +### 502 Bad Gateway错误 + +- **原因**:Node.js项目未启动或端口配置错误 +- **解决**:执行`pm2 ls`检查项目状态,确认`proxy_pass`端口与项目一致 + +### 403 Forbidden错误 + +- **原因**:Nginx权限不足或配置错误 +- **解决**:检查配置文件,重启Nginx服务 + +### 无法访问 + +- **原因**:防火墙未开放对应端口 +- **解决**:执行`ufw allow 80/tcp`开放HTTP端口 + +### 端口占用 + +- **原因**:端口被其他进程占用 +- **解决**:使用`lsof -i :端口号`查看占用进程,使用`kill`命令终止 + +## 部署架构 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 用户浏览器 │────▶│ Nginx │────▶│ Node.js │ +│ (80端口) │ │ 反向代理 │ │ 应用服务 │ +└─────────────┘ └─────────────┘ └─────────────┘ +``` + +## 最佳实践 + +1. **版本管理**:使用nvm管理Node.js版本,确保环境一致性 +2. **进程管理**:使用pm2确保项目后台稳定运行 +3. **反向代理**:配置Nginx实现域名访问和负载均衡 +4. **安全配置**:设置合理的防火墙规则,限制访问权限 +5. **监控日志**:定期查看项目日志,及时发现问题 +6. **自动化部署**:考虑使用CI/CD工具实现自动化部署 +7. **备份策略**:定期备份项目代码和数据 + +## 总结 + +本指南提供了完整的Node.js项目服务器部署流程,从环境搭建到项目运行的全链路解决方案。通过nvm管理Node.js版本、pm2保证服务稳定、Nginx实现反向代理,构建了一个生产级的Node.js部署环境。 + +**核心优势**: + +- 灵活性:支持多版本Node.js切换 +- 稳定性:后台运行确保服务不中断 +- 可扩展性:Nginx支持负载均衡和HTTPS +- 可维护性:独立配置文件便于管理 + +**适用场景**: + +- 生产环境Node.js应用部署 +- 微服务架构服务部署 +- API服务端部署 +- 前端项目构建部署