【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。
二、用户认证的基本使用
- 用户模型:扩展 Mongoose schema 添加 username/password。
- 注册端点:POST /api/register,哈希密码,保存用户。
- 登录端点:POST /api/login,验证密码,生成 JWT。
- 保护路由:用 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篇。关注我,跟着学完整系列!)