当前位置: 首页 > news >正文

【8/20】用户认证基础:JWT 在 Express 中的实现,实现安全登录

标签:用户认证、JWT、Express.js、安全登录、Node.js、MongoDB、入门教程、项目实践

前言

欢迎来到“前后端与数据库交互详解”系列的第8篇文章!在前七篇文章中,我们构建了从 Vue.js 前端到 Express 后端再到 MongoDB 数据库的全栈应用,包括一个完整的用户管理系统。然而,当前的 API 是公开的——任何人可以操作用户数据,这不安全。

本篇文章的焦点是 用户认证基础,特别是用 JWT 在 Express 中实现安全登录。我们将解释认证是什么、JWT 如何工作,并在结尾扩展第七篇的项目:添加注册、登录端点,使用 JWT 保护 CRUD 操作。只有认证用户才能访问 API。这将帮助您理解后端安全,形成一个带认证的独立应用。未来,我们将整合到前端。

前提:您已安装 Express 和 Mongoose(从前文)。额外安装:npm install jsonwebtoken bcryptjs(JWT 用于令牌,bcrypt 用于密码哈希)。我们用环境变量存储密钥(.env 文件,需 npm install dotenv)。

一、用户认证是什么?

用户认证是验证用户身份的过程,确保只有授权用户访问资源。在 Web 应用中,它包括注册、登录和会话管理。

  • 为什么需要认证?

    • 安全:保护敏感数据,如用户列表(第七篇)。
    • 访问控制:区分公共和私有 API。
    • 常见方法:Session-based(传统)、Token-based(现代,如 JWT)。
    • JWT 优势:无状态(不存服务器)、可扩展、包含用户信息(payload),适合 RESTful API。
    • 对比其他:OAuth 更复杂;我们用 JWT 简化入门。
    • 在前后端中的作用:后端生成/验证 JWT,前端存储并发送令牌。
  • 核心概念

    • 注册:存储用户(用户名、哈希密码)。
    • 登录:验证凭证,生成 JWT。
    • 中间件:Express middleware 检查请求头中的 Authorization: Bearer 。
    • 密码安全:用 bcrypt 哈希存储。
    • JWT 结构:Header.Payload.Signature;用 jsonwebtoken.sign() 生成,verify() 验证。

在我们的系列中,认证是后端安全层:保护第七篇的 API。

二、用户认证的基本使用

  1. 用户模型:扩展 Mongoose schema 添加 username/password。
  2. 注册端点:POST /api/register,哈希密码,保存用户。
  3. 登录端点:POST /api/login,验证密码,生成 JWT。
  4. 保护路由:用 middleware 检查 JWT。

示例:生成 JWT jwt.sign({ id: user._id }, 'secret', { expiresIn: '1h' })

接下来,通过项目实践这些。

三、实现完整项目:带认证的用户管理系统

项目目标:扩展第七篇的 Express API,添加用户模型、注册/登录端点,用 JWT 保护 CRUD 操作。未认证请求将被拒绝。这是一个独立的完整后端应用,可以用 Postman 测试登录后访问用户数据。前端整合将在下一篇文章。

步骤 1: 创建项目文件夹并安装依赖

  • 基于第七篇的 user-api-mongodb 文件夹。
  • 安装额外依赖:npm install jsonwebtoken bcryptjs dotenv
  • 新建 .env 文件:JWT_SECRET=your_secret_key(替换为强密钥)。

步骤 2: 编写服务器代码

更新 server.js 文件,添加认证逻辑:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const dotenv = require('dotenv');
dotenv.config();const app = express();
const PORT = 3000;app.use(cors());
app.use(express.json());// 连接 MongoDB(替换为您的字符串)
mongoose.connect('mongodb://localhost:27017/userdb', { useNewUrlParser: true, useUnifiedTopology: true }).then(() => console.log('Connected to MongoDB')).catch(err => console.error('MongoDB connection error:', err));// 用户模型(扩展为认证)
const userSchema = new mongoose.Schema({username: { type: String, required: true, unique: true },password: { type: String, required: true },name: String, // 可选,扩展用户数据email: String
});
userSchema.pre('save', async function(next) {if (this.isModified('password')) {this.password = await bcrypt.hash(this.password, 10);}next();
});
const User = mongoose.model('User', userSchema);// JWT 中间件:保护路由
const authMiddleware = (req, res, next) => {const token = req.header('Authorization')?.replace('Bearer ', '');if (!token) return res.status(401).json({ error: 'No token provided' });try {const decoded = jwt.verify(token, process.env.JWT_SECRET);req.user = decoded;next();} catch (err) {res.status(401).json({ error: 'Invalid token' });}
};// 注册端点
app.post('/api/register', async (req, res) => {try {const { username, password } = req.body;const user = new User({ username, password });await user.save();res.status(201).json({ message: 'User registered' });} catch (err) {res.status(400).json({ error: 'Registration failed' });}
});// 登录端点
app.post('/api/login', async (req, res) => {try {const { username, password } = req.body;const user = await User.findOne({ username });if (!user || !(await bcrypt.compare(password, user.password))) {return res.status(401).json({ error: 'Invalid credentials' });}const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });res.json({ token });} catch (err) {res.status(500).json({ error: 'Server error' });}
});// 保护的用户路由(从第七篇扩展,只列出 GET all 和 POST 示例;类似添加 PUT/DELETE)
app.get('/api/users', authMiddleware, async (req, res) => {const users = await User.find();res.json(users);
});app.post('/api/users', authMiddleware, async (req, res) => {const newUser = new User(req.body);await newUser.save();res.status(201).json(newUser);
});// 启动服务器
app.listen(PORT, () => console.log(`Server on http://localhost:${PORT}`));
  • 解释
    • 用户模型:添加 username/password,pre-save 钩子哈希密码。
    • 注册:创建用户,自动哈希。
    • 登录:验证密码,生成 JWT(包含 user.id)。
    • authMiddleware:检查令牌,解码后附加到 req.user。
    • 保护路由:应用 middleware 到 /api/users 端点(扩展第七篇的 CRUD)。
    • 安全:密码不明文存储,JWT 有过期时间。

步骤 3: 运行和测试

  • 运行:node server.js(终端显示连接成功)。
  • 测试注册:Postman POST /api/register,body: {“username”: “test”, “password”: “123456”},响应 201。
  • 测试登录:POST /api/login,同 body,响应 {token: “…”}。
  • 测试保护 API:GET /api/users,添加 header “Authorization: Bearer ”,成功返回用户;无 token 返回 401。
  • 这是一个完整的带认证后端!数据操作需登录。

步骤 4: 扩展(可选)

  • 角色:添加 role 字段,middleware 检查权限。
  • 刷新 token:实现 refresh endpoint。
  • 错误处理:自定义消息。

四、常见问题与调试

  • Token 无效?检查密钥匹配、过期时间;用 jwt.io 调试。
  • 密码不匹配?确保 bcrypt compare 正确。
  • CORS?已启用,但检查前端请求头。
  • 生产安全?用 HTTPS,强密钥,避免硬编码。

总结

通过本篇,您入门了用户认证,用 JWT 在 Express 中实现安全登录。带认证的用户管理系统证明了如何保护 API,形成独立的セキュア后端项目。

下一篇文章:前端认证整合:Vue.js 中处理 JWT,实现登录页面和受保护路由。我们将结合前端,实现完整认证流。如果有疑问,欢迎评论!

(系列导航:这是第8/20篇。关注我,跟着学完整系列!)

http://www.dtcms.com/a/391233.html

相关文章:

  • 独立站有哪些建站工具
  • Linux 终端常用快捷键整理
  • 跨域的两种解决方法
  • 小程序中获取年月日时分的组件
  • Redis热升级秘籍:零停机迁移方案与工具链
  • 时序数据库选型指南深度解析IoTDB架构设计与性能对比
  • springboot超市管理系统的设计与实现(代码+数据库+LW)
  • 让Trae写一个AI的api中继服务
  • 跨国制造业SD-WAN:延迟下降78%,运维成本下降53%
  • MySQL服务启动不成功的可能解决方法
  • 硬解码出现画面回退分析
  • P1068 [NOIP 2009 普及组] 分数线划定-普及-
  • 用python语言如何排大小
  • pycharm连接GitHub,怎么配置 SSH 密钥并改用 SSH 连接
  • ​​[硬件电路-265]:电源系统要考虑的因素包括:不同的输出电压、隔离防干扰、防反、防浪涌、电压可调、电源开关、电池、可充电、低纹波、低噪声、防波动等
  • 【开题答辩全过程】以 基于Python的电影推荐系统为例,包含答辩的问题和答案
  • 格拉姆角场(Gramian Angular Field, GAF)详解
  • 前端开发工具Vue有哪些?常用Vue前端开发工具推荐、Vue开发工具对比与最佳实践分享
  • 基于vue的幼儿园健康管理系统0fz0y(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 第69课 分类任务: 基于BERT训练情感分类与区别二分类本质思考
  • Mysql杂志(二十)——MyISAM索引结构与B树B+树
  • Java 大视界 -- 基于 Java 的大数据实时流处理在金融高频交易数据分析中的应用
  • BonkFun 推出 USD1:Meme 币玩法的新入口
  • flutter在包含ListVIew的滚动列表页面中监听手势
  • Redis 三种集群模式详解
  • 打开hot100
  • Ant-Design Table中使用 AStatisticCountdown倒计时,鼠标在表格上移动时倒计时被重置
  • Linux crontab 定时任务工具使用
  • 阿里云RDS mysql8数据本地恢复,与本地主从同步(容器中)
  • 记录一次mysql启动失败问题解决