Node.js后端如何实现仅单次生效的唯一API接口 防范数据爬取与泄露
单次有效API实现方案
核心逻辑
通过为每个API请求绑定一次性失效的唯一Token,将Token作为API路径的一部分,结合高性能KV存储实现单次访问校验,从根源上避免用户通过修改参数批量爬取数据。
具体实现步骤
- 服务端生成带签名的唯一Token:用户访问视频页面时,服务端生成UUID作为基础Token,将Token与对应视频资源ID、过期时间绑定,通过HMAC算法生成签名防止用户伪造Token,最终生成的完整Token同时存入Redis等KV数据库,设置5-10分钟的自动过期时间,避免无效数据占用存储。
- 改造API路径规则:将原有带资源参数的API(例如
/api/video?id=xxx)改为携带Token的路径形式(例如/api/video/{完整Token}),确保每个API地址都是唯一值。 - 请求校验逻辑开发:用户请求带Token的API时,首先查询KV数据库中是否存在对应Token:
- 若Token不存在(已使用/过期/伪造),直接返回
null - 若Token存在,立刻删除该Token保证仅可使用一次,校验Token签名合法性后返回对应视频资源数据
- 若Token不存在(已使用/过期/伪造),直接返回
- 页面刷新适配:用户刷新页面时,服务端会重新生成新的Token并下发,对应新的API地址仍仅支持单次访问。
可选优化点
- 增加IP级别的页面请求限流,防止攻击者批量刷页面获取大量有效Token爬取数据
- 若后续需要支持非公开资源,可将用户身份标识也绑定到Token签名规则中,避免Token被转发盗用
- 前端渲染时直接将API地址注入到页面逻辑中,不要暴露Token生成与拼接规则,降低被逆向的风险
Node.js 示例代码(基于Express + Redis)
const express = require('express'); const Redis = require('ioredis'); const { v4: uuidv4 } = require('uuid'); const crypto = require('crypto'); const app = express(); const redis = new Redis(); // 业务签名密钥,生产环境需存入环境变量避免泄露 const SIGN_SECRET = process.env.API_SIGN_SECRET || 'your_custom_secure_secret'; // 模拟业务逻辑:根据视频ID取视频数据 const getVideoInfoById = async (videoId) => { return { id: videoId, url: 'https://your-video-cdn-address/xxx.mp4', title: '示例视频标题' }; }; // 视频页面路由:下发带Token的API地址 app.get('/video/:videoId', async (req, res) => { const { videoId } = req.params; const rawToken = uuidv4(); // 生成签名防止Token被伪造篡改 const sign = crypto.createHmac('sha256', SIGN_SECRET) .update(`${rawToken}:${videoId}`) .digest('hex'); const fullToken = `${rawToken}.${sign}`; // Token存入Redis,有效期5分钟 await redis.set(`api:token:${fullToken}`, videoId, 'EX', 300); // 渲染页面时将API地址注入,前端直接调用该地址即可 res.render('video', { videoApi: `/api/video/${fullToken}` }); }); // 实际视频数据接口 app.get('/api/video/:token', async (req, res) => { const { token } = req.params; const tokenKey = `api:token:${token}`; // 查询Token是否存在,同时直接删除保证仅可调用一次 const videoId = await redis.getDel(tokenKey); if (!videoId) { return res.json(null); } // 校验签名合法性 const [rawToken, sign] = token.split('.'); const validSign = crypto.createHmac('sha256', SIGN_SECRET) .update(`${rawToken}:${videoId}`) .digest('hex'); if (sign !== validSign) { return res.json(null); } // 返回业务数据 const videoData = await getVideoInfoById(videoId); res.json(videoData); }); app.listen(3000, () => { console.log('service start at port 3000'); });
内容的提问来源于stack exchange,提问作者Kien Pham
相关产品推荐
相关产品推荐

