打开网易新闻 查看精彩图片

你写的Express接口,现在任何人都能直接调用。这不是漏洞,是根本没装锁。

真实世界的API需要登录、拿密钥、再带着密钥请求数据。这套流程用JSON Web Token(JWT)实现,但官方文档像说明书,看完还是不会装。本文把Express+JWT认证拆成7个可执行的步骤,从空文件夹到能用的登录系统。

第1步:把依赖装对,省掉后面80%的报错

第1步:把依赖装对,省掉后面80%的报错

新建文件夹,初始化项目。npm init -y生成package.json后,装这四个包:express(框架)、mongoose(连MongoDB)、dotenv(读环境变量)、nodemon(热重启)。

改package.json的"type"为"module"才能用ESM语法。然后建index.js,写最简Express启动代码:

import express from 'express' const app = express(); const Port = process.env.PORT || 5000 const start = ()=>{ app.listen(Port, ()=>{ console.log('The server is running ....'); }) } start();

npm start看到"The server is running…"就算过。这步错了,后面全错。

第2步:MongoDB连接字符串别硬编码

第2步:MongoDB连接字符串别硬编码

建.env文件,把Atlas集群的连接字符串贴进去。格式长这样:MONGO_URI = mongodb+srv://name:password@clustername.xxx.mongodb.net/LoginApp?appName=NodeExpressTutorial。

单独建Database/connectdb.js处理连接逻辑,用mongoose.connect(url)方法。index.js里引入这个模块,用async/await包裹启动逻辑,连不上数据库时catch块能打印错误。

关键细节:process.env.MONGO_URI要在dotenv/config加载后才能读到,顺序错了会报undefined。

打开网易新闻 查看精彩图片

第3步:用户模型要存密码的"指纹"而非密码本身

第3步:用户模型要存密码的"指纹"而非密码本身

建Models/User.js,定义schema:用户名、邮箱、密码。但密码字段不能直接存明文,要用bcrypt加盐哈希。

盐(salt)是随机字符串,和密码拼接后再哈希。同样密码,不同盐值,哈希结果完全不同。即使数据库泄露,攻击者也无法反推原始密码。

在schema的pre('save')钩子中自动处理哈希。用户注册时传入明文密码,入库前自动变成60字符的哈希串。登录时再拿用户输入的密码,用bcrypt.compare()和库存哈希比对。

第4步:JWT的签发和验证拆成两个中间件

第4步:JWT的签发和验证拆成两个中间件

用户登录成功后,服务端要发"门票"——JWT。这个token包含用户ID和过期时间,用服务器密钥签名。客户端之后每次请求都带着token,服务端验证签名有效就放行。

建两个工具函数:createJWT用jsonwebtoken库的sign方法签发,verifyJWT用verify方法验证。密钥存环境变量JWT_SECRET,别写死在代码里。

token的有效期设多长?教程示例用30天,生产环境建议2小时+刷新机制。但本文是入门教程,先跑通再优化。

第5步:注册和登录路由要分开测

第5步:注册和登录路由要分开测

建Routes/auth.js,写两个POST端点。/register接收用户名、邮箱、密码,查重后创建用户,返回201。/login查邮箱是否存在,bcrypt比对密码,通过就签发JWT返回给客户端。

测试时用Postman或curl,先看注册能否入库,再看登录能否拿到token。两步都通,再往下做。

打开网易新闻 查看精彩图片

常见卡壳点:密码比对失败——检查bcrypt.compare的参数顺序,明文在前,哈希在后。token没返回——检查JWT_SECRET是否加载成功。

第6步:保护路由的 middleware 是最后一道门

建middleware/auth.js,导出一个验证函数。从请求头取Authorization字段,格式应该是Bearer 。提取token部分,用verifyJWT解码,拿到用户ID后查库确认存在,最后把用户信息挂到req.user上供后续使用。

哪个路由需要保护,就在前面加这个中间件。比如app.get('/dashboard', authenticateUser, dashboardController),没token或token无效的直接返回401。

注意:JWT验证通过只说明token没被篡改,不说明用户还存在。中间件里加一步数据库查询,防止用户已注销但token未过期的情况。

第7步:错误处理要覆盖三种失败场景

第7步:错误处理要覆盖三种失败场景

认证系统有三类错误:数据库连接失败(500)、凭证错误(401)、权限不足(403)。现在分别处理。

mongoose连接失败时,index.js的catch块打印error.message。登录时邮箱不存在或密码错误,统一返回"Invalid credentials",别告诉攻击者到底是哪个错了。token过期或无效,返回401并提示重新登录。

用express-async-errors包或自己写wrapper,避免每个async路由都写try-catch。错误中间件放在所有路由最后,接收(err, req, res, next)四个参数。

完整的认证系统现在能跑通:注册→登录→拿token→访问受保护路由→token过期重新登录。代码量不到200行,但每个环节都有坑。

你现在的token有效期设了多久?如果超过24小时,建议看看OWASP的会话管理建议。