发布时间:2026/9/2 21:01:20
Node系列 · Express:登录与认证 Node系列 · Express登录与认证登录与认证是后端最常见的需求。本章从登录接口设计到鉴权中间件把完整流程串起来——session 与 JWT 两种方案都会讲后续章节单独展开。一、登录与认证的核心流程数据库服务器客户端数据库服务器客户端alt[密码正确][密码错误]POST /api/auth/login{ email, password }SELECT user WHERE email?user (含 password hash)bcrypt.compare(password, hash)生成 sessionId / JWT200 OKSet-Cookie 或 { token }401 UnauthorizedGET /api/profileCookie: sessionIdxxx鉴权中间件验证200 OK 用户信息二、登录接口设计2.1 接口签名POST /api/auth/login Content-Type: application/json { email: aliceexample.com, password: plain-password }成功响应HTTP/1.1 200 OK Set-Cookie: sessionIdxxx; HttpOnly; Secure; SameSiteLax { user: { id: 1, name: Alice, email: aliceexample.com } }失败响应HTTP/1.1 401 Unauthorized { error: 邮箱或密码错误 }::: tip错误信息要模糊——“邮箱或密码错误而不是邮箱不存在或密码错误”。后者会泄露账号是否存在。:::2.2 Express 实现const express require(express); const bcrypt require(bcrypt); const router express.Router(); router.post(/login, async (req, res, next) { try { const { email, password } req.body; // 1. 验证请求体 if (!email || !password) { return res.status(400).json({ error: 邮箱和密码必填 }); } // 2. 查用户 const user await User.findOne({ where: { email } }); if (!user) { // 用同一条错误信息避免泄露账号是否存在 return res.status(401).json({ error: 邮箱或密码错误 }); } // 3. 验证密码 const isMatch await bcrypt.compare(password, user.passwordHash); if (!isMatch) { return res.status(401).json({ error: 邮箱或密码错误 }); } // 4. 设置会话这里用 sessionId详细见 session 章节 req.session.userId user.id; // 5. 返回用户信息不含密码 res.json({ user: { id: user.id, name: user.name, email: user.email, }, }); } catch (err) { next(err); } }); module.exports router;三、注册接口配套router.post(/register, async (req, res, next) { try { const { email, password, name } req.body; // 1. 验证 if (!email || !password) { return res.status(400).json({ error: 邮箱和密码必填 }); } if (password.length 8) { return res.status(400).json({ error: 密码至少 8 位 }); } // 2. 查重 const exists await User.findOne({ where: { email } }); if (exists) { return res.status(409).json({ error: 邮箱已被注册 }); } // 3. 密码哈希bcrypt 自动加盐 const passwordHash await bcrypt.hash(password, 10); // 4. 创建用户 const user await User.create({ email, name, passwordHash }); // 5. 自动登录 req.session.userId user.id; res.status(201).json({ user: { id: user.id, name: user.name, email: user.email }, }); } catch (err) { next(err); } });四、鉴权中间件登录后访问受保护接口需要我已登录的凭证function requireAuth(req, res, next) { if (!req.session.userId) { return res.status(401).json({ error: 未登录 }); } next(); } // 保护接口 app.use(/api/profile, requireAuth); app.use(/api/orders, requireAuth); // 公开接口 app.use(/api/auth/login, /* 不加 requireAuth */); app.use(/api/public, /* 不加 requireAuth */);4.1 加载当前用户每次请求加载用户信息避免在 req.user 上缓存过期数据async function loadUser(req, res, next) { if (!req.session.userId) return next(); try { const user await User.findByPk(req.session.userId, { attributes: [id, name, email, avatar], // 不取 password }); if (user) req.user user; next(); } catch (err) { next(err); } } app.use(loadUser); // 业务路由里直接用 router.get(/profile, (req, res) { if (!req.user) return res.status(401).json({ error: 未登录 }); res.json(req.user); });4.2 角色鉴权function requireRole(role) { return (req, res, next) { if (!req.user) return res.status(401).json({ error: 未登录 }); if (req.user.role ! role) { return res.status(403).json({ error: 权限不足 }); } next(); }; } router.delete(/users/:id, requireRole(admin), deleteUser);五、退出登录router.post(/logout, (req, res) { // 销毁 session req.session.destroy((err) { if (err) return next(err); res.clearCookie(connect.sid); // 清除 session cookie res.json({ ok: true }); }); });六、密码安全规范6.1 必须做的事规则理由用bcrypt哈希自动加盐 慢哈希至少 8 位太短会被秒破不限制字符集强密码规则反而让用户用Password1!不存明文数据库泄露 用户全完登录失败通用提示不泄露账号是否存在限流登录接口防暴力破解6.2 不要做的事❌ 用 MD5 / SHA1 哈希密码已被攻破❌ 自己加盐然后存 hash直接用 bcrypt❌ 在日志里记录密码❌ 通过 URL query 传密码❌ 把 token 存 LocalStorage防 XSS 偷七、常见漏洞漏洞攻击方式防护暴力破解反复试密码登录限流 验证码凭据填充用泄露密码批量试检测异常 IP / 异地登录会话固定攻击者预设 sessionId登录成功后重置 sessionId会话劫持XSS 偷 CookieHttpOnly SecureCSRF跨站请求SameSiteLax CSRF Token八、最佳实践场景推荐密码存储bcryptcost10-12会话方案Sessionexpress-session或 JWTCookie 属性HttpOnlySecureSameSiteLax失败提示通用邮箱或密码错误接口限流express-rate-limit日志脱敏不记密码、token退出req.session.destroy()res.clearCookie()九、小结登录流程验证请求体 → 查用户 → bcrypt 比对 → 设置会话 → 返回用户信息鉴权中间件requireAuth检查req.session.userId未登录返回 401角色鉴权requireRole(role)在 requireAuth 基础上再校验角色失败提示要通用“邮箱或密码错误”不泄露账号是否存在密码用 bcrypt 哈希Cookie 用 HttpOnly Secure SameSiteLaxsession 与 JWT 两种会话方案下两章分别讲

相关新闻

2026/9/2 21:01:20

微信小程序集成科大讯飞语音识别:音频处理与实时流式架构实战

简介:本资源是一套面向微信小程序开发者的科大讯飞语音识别集成实战方案,聚焦解决小程序端语音上传、PCM格式转换、音频降噪提取及实时语音转文字等核心痛点,适用于具备基础JavaScript与小程序开发能力的中初级开发者。压缩包共34个文件&…

2026/9/2 21:01:20

Python的Web框架“FastApi“常用的ORM框架

安装第三方库:"sqlalchemy" 让你用 Python 类和对象来操作数据库表和数据,把 SQL 语句翻译成 Python 代码。加"[asyncio] "确保 greenlet 被安装,让你能用 create_async_engine"aiomysql"负责真正的数据库网络…

2026/9/2 21:01:20

鲜花网站课程设计全攻略:从需求拆解到答辩避坑

简介:这是一份基于HTML5、CSS3与JavaScript的鲜花网站课程设计项目,主要面向计算机相关专业学生及前端入门开发者,用于完成网页设计类课程作业或积累仿真实战经验。项目不仅使用语义化标签搭建页面框架,借助Flex/Grid实现响应式布…

2026/9/2 21:16:21

Geek 对比 Revo,哪款免安装卸载工具更适合你

极简主义与全能战士的碰撞:两款卸载工具的哲学分野 在 Windows 生态中,软件卸载往往被简化为“点击删除”的机械动作。然而,对于追求系统极致纯净的极客和高级用户而言,真正的挑战在于如何清除那些隐藏在注册表深处、AppData 文件…

2026/9/2 21:16:21

深挖历史版本,ipatool 助力 iOS 兼容性测试与逆向分析

为什么历史版本成了“黑盒” 在 iOS 生态的长期维护与安全研究中,我们常面临一个尴尬的现实:App Store 只向普通用户和开发者提供应用的“最新版本”。一旦新版本推送上线,旧版本的 IPA 安装包便仿佛从网络中蒸发,难以通过官方渠道…

2026/9/2 21:16:21

从零配置虚拟 iPhone,vphone-cli 完整工作流复盘

硬件门槛与底层权限配置 在 macOS 上运行真实的 iOS 系统并非简单的软件安装,而是一次对系统底层虚拟化能力的深度调用。vphone-cli 的核心依赖于 Apple Silicon 架构(M 系列芯片)特有的 Virtualization.framework,这意味着 Intel…

2026/9/2 21:16:21

TinyMCE格式刷插件实战:配置、原理与避坑指南

简介:TinyMCE格式刷插件压缩包,专为使用TinyMCE富文本编辑器的前端开发者与内容管理维护人员准备,解决多段文本快速复制格式、保持排版风格一致的问题。资源共2个文件,整体仅8KB,包含一个JavaScript插件文件与一份txt说…

2026/9/2 21:16:21

从EDG的逆袭看团队建设:失败、复盘与长期主义的冠军之路

每到世界赛接近尾声,我总会想起那只 EDG。不是因为它在 2021 年拿下了全球总决赛冠军,而是因为在那之前,它几乎把所有关于“意难平”的剧本都演过了一遍:联赛里是霸主,到了世界赛却像换了支队伍;阵容明明不…

2026/9/2 21:11:21

Codex CLI 安装配置与排错:从 binary 报错到接入 DeepSeek 完整指南

Codex CLI 是 OpenAI 提供的本地命令行编码代理。它不是简单聊天框,而是能读取当前项目目录、理解代码结构、直接修改文件并执行命令的终端工具。很多开发者在安装完 Codex CLI 后,并不是卡在模型能力上,而是卡在环境问题上:启动插…

2026/9/1 16:02:17

vSound小提琴数字处理器实操指南:从接线到演出的完整配置

电小提琴或者原声小提琴插电演出,第一个绕不开的坎就是声音难听。原声琴的共鸣和空气感一旦进了拾音器,出来的往往是一坨干瘪、发尖、带着奇怪塑料味的信号。我当初第一次把琴接上乐队调音台,直接被主唱吐槽"你这声音像在锯钢丝"。…

2026/9/2 9:00:32

传感器接口IC如何攻克生物化学传感的微弱信号难题?

1. 从电极到比特流:为什么生物化学传感必须依赖专用接口IC 做生物化学传感的人都有过类似的经历:明明传感器本身性能很好,信号输出却一塌糊涂——噪声大、漂移明显、重复性差,怎么调都达不到预期。很多时候问题并不在传感器&#…

2026/9/2 8:41:06

STM32F411CEU6多通道ADC采集:扫描模式+DMA实现详解

1. 多通道 ADC 的用武之地把“Multichannel ADC”和“STM32F411CEU6”这两个关键字放在一起,其实就是嵌入式开发里最常遇到的一类需求:用一块不算贵的 MCU,同时采集多路模拟信号。STM32F411CEU6 是 48 引脚的 Cortex-M4F 主控,主频…

2026/9/2 0:03:41

单片机毕业设计-基于单片机与蓝牙通讯的输液状态监测终端设计与开发 基于 STM32 或 51 单片机的液位‑滴速‑温度多参数输液监护装置设计(024005)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/9/2 0:03:41

DeepSeek字幕翻译实战:从API调用到批量SRT转中文的完整方案

这次我们来看一个很实用的 DeepSeek 落地场景:用 DeepSeek 把英文视频字幕自动翻译成中文。具体案例是《恶魔君》1989 年第 28 集的英转中字幕任务,标题写得很直白,但背后其实是一整套可以复用的技术流程:字幕解析、模型调用、批量…

2026/9/2 0:03:41

用Python搭建搞笑语音助手:从语音识别到语音合成全教程

当你家里摆着一台天猫精灵,却总希望语音助手偶尔“不正经”一点,不用官方腔回答问题,而是张口就接几句搞笑段子,会是什么体验?我最近动手验证了一下这个想法——没有去改装任何市面上现有的智能音箱,而是直…

2026/9/2 1:15:22

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/2 1:15:22

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/2 1:15:20

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…