![]()
你写的Express接口,现在任何人都能直接调用。这不是漏洞,是根本没装锁。
真实世界的API需要登录、拿密钥、再带着密钥请求数据。这套流程用JSON Web Token(JWT)实现,但官方文档像说明书,看完还是不会装。本文把Express+JWT认证拆成7个可执行的步骤,从空文件夹到能用的登录系统。
第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连接字符串别硬编码
建.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步:用户模型要存密码的"指纹"而非密码本身
建Models/User.js,定义schema:用户名、邮箱、密码。但密码字段不能直接存明文,要用bcrypt加盐哈希。
盐(salt)是随机字符串,和密码拼接后再哈希。同样密码,不同盐值,哈希结果完全不同。即使数据库泄露,攻击者也无法反推原始密码。
在schema的pre('save')钩子中自动处理哈希。用户注册时传入明文密码,入库前自动变成60字符的哈希串。登录时再拿用户输入的密码,用bcrypt.compare()和库存哈希比对。
第4步:JWT的签发和验证拆成两个中间件
用户登录成功后,服务端要发"门票"——JWT。这个token包含用户ID和过期时间,用服务器密钥签名。客户端之后每次请求都带着token,服务端验证签名有效就放行。
建两个工具函数:createJWT用jsonwebtoken库的sign方法签发,verifyJWT用verify方法验证。密钥存环境变量JWT_SECRET,别写死在代码里。
token的有效期设多长?教程示例用30天,生产环境建议2小时+刷新机制。但本文是入门教程,先跑通再优化。
第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步:错误处理要覆盖三种失败场景
认证系统有三类错误:数据库连接失败(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的会话管理建议。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.