如何在Node.js中生成带过期时间的一次性下载链接?
实现Node.js一次性过期下载链接方案
核心思路是生成带签名与过期时间的令牌URL,后端通过验证令牌的有效性(是否过期、是否已使用)来决定是否允许文件下载,具体步骤如下:
1. 生成带过期时间的下载令牌
使用JWT(JSON Web Token)对文件路径、过期时间进行签名,避免令牌被篡改。JWT自带过期验证逻辑,无需额外存储过期时间(若需严格一次性使用,后续再添加令牌黑名单)。
先安装依赖:
npm install jsonwebtoken nodemailer express
生成链接的代码:
const jwt = require('jsonwebtoken'); // 生产环境务必用环境变量存储密钥,不要硬编码 const SECRET_KEY = process.env.JWT_SECRET || 'your_strong_custom_secret'; // 生成下载链接,expiresIn支持'1d'(1天)、'7d'(1周)等格式 function generateDownloadUrl(filePath, expiresIn = '1d') { const token = jwt.sign( { filePath }, // 令牌中携带文件路径 SECRET_KEY, { expiresIn } ); // 替换为你的实际域名和下载路由 return `http://your-domain.com/download?token=${token}`; }
2. 创建下载验证路由
后端接收令牌后,先验证其有效性,再检查是否已被使用(实现一次性),验证通过则触发文件下载:
const express = require('express'); const app = express(); const jwt = require('jsonwebtoken'); const path = require('path'); // 生产环境建议用Redis存储已使用令牌,避免服务器重启后数据丢失 const usedTokens = new Set(); app.get('/download', (req, res) => { const { token } = req.query; if (!token) { return res.status(400).send('缺少下载令牌'); } if (usedTokens.has(token)) { return res.status(403).send('链接已失效'); } try { const decoded = jwt.verify(token, SECRET_KEY); const fullFilePath = path.resolve(__dirname, decoded.filePath); // 标记令牌已使用,确保一次性 usedTokens.add(token); // 触发文件下载,res.download自动处理文件头 res.download(fullFilePath, (err) => { if (err) res.status(404).send('文件不存在'); }); } catch (err) { if (err.name === 'TokenExpiredError') { return res.status(403).send('链接已过期'); } return res.status(403).send('无效的下载链接'); } });
3. 发送下载链接邮件
用nodemailer实现邮件发送,将生成的链接推送给用户:
const nodemailer = require('nodemailer'); // 根据你的邮箱服务商配置SMTP,比如Gmail、QQ邮箱等 const transporter = nodemailer.createTransport({ service: 'Gmail', auth: { user: process.env.EMAIL_USER || 'your-email@gmail.com', pass: process.env.EMAIL_PASS || 'your-app-password' // 注意用授权码而非邮箱密码 } }); async function sendDownloadEmail(userEmail, downloadUrl) { const mailOptions = { from: '你的服务名称 <your-email@gmail.com>', to: userEmail, subject: '您的专属文件下载链接', html: ` <p>点击下方链接下载文件:</p> <a href="${downloadUrl}" target="_blank">${downloadUrl}</a> <p>链接1天后过期,且仅可使用一次</p> ` }; try { await transporter.sendMail(mailOptions); console.log(`下载链接已发送至${userEmail}`); } catch (err) { console.error('邮件发送失败:', err); } }
4. 整合使用示例
// 处理用户的下载请求 app.post('/request-download', express.json(), async (req, res) => { const { userEmail } = req.body; // 替换为你实际的文件路径,注意限制目录范围避免安全风险 const targetFile = './public/assets/example.pdf'; const downloadUrl = generateDownloadUrl(targetFile, '1d'); await sendDownloadEmail(userEmail, downloadUrl); res.send('下载链接已发送到您的邮箱,请查收'); }); app.listen(3000, () => { console.log('服务器运行在http://localhost:3000'); });
关键注意事项
- 密钥安全:生产环境必须用环境变量存储JWT密钥和邮箱密码,绝不能硬编码到代码中
- 令牌存储:内存Set仅适合测试,生产环境用Redis或数据库存储已使用的令牌,保证服务器重启后数据不丢失
- 文件权限:限制令牌中的文件路径只能访问指定目录,防止恶意用户通过篡改令牌访问敏感文件
- 邮件配置:不同邮箱服务商的SMTP规则不同,比如QQ邮箱需要开启SMTP服务并使用授权码登录
内容的提问来源于stack exchange,提问作者M A
相关产品推荐
相关产品推荐

