You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为React下载按钮添加Authorization请求头实现鉴权下载

带鉴权的前后端文件下载实现方案

现有代码存在两个直接导致功能失效的核心问题:

  • 前端错误将二进制文件流响应按JSON格式解析,没有触发浏览器原生下载行为
  • 后端res.download()方法仅支持读取本地文件系统的文件返回,如果文件二进制内容直接存储在数据库中,该方法无法正常工作;路由未挂载鉴权中间件时,鉴权逻辑本身不会生效

后端(Express.js)实现

根据文件存储方式二选一即可:

场景1:数据库仅存储文件本地路径,文件实体存在服务器磁盘

路由必须挂载鉴权校验中间件,确保请求经过token校验后才会执行下载逻辑:

// 路由添加鉴权中间件,authCheck替换为项目实际的鉴权逻辑
router.get('/download/:fid', authCheck, filesControllers.downloadFile);

const downloadFile = async (req, res, next) => {
  const fileId = req.params.fid;
  try {
    const filePost = await File.findById(fileId);
    if (!filePost) {
      return next(new HttpError("指定文件不存在", 404));
    }
    // res.download第一个参数是文件本地绝对路径,第二个参数是下载时显示的文件名
    res.download(filePost.file, filePost.originalName, (err) => {
      if (err) return next(new HttpError("文件读取失败", 500));
    });
  } catch (err) {
    return next(new HttpError("服务器查询异常", 500));
  }
};

场景2:文件二进制内容直接存储在数据库中(如MongoDB BinData类型)

不要使用res.download(),手动设置响应头后直接返回Buffer内容即可:

router.get('/download/:fid', authCheck, filesControllers.downloadFile);

const downloadFile = async (req, res, next) => {
  const fileId = req.params.fid;
  try {
    const filePost = await File.findById(fileId);
    if (!filePost) {
      return next(new HttpError("指定文件不存在", 404));
    }
    // 设置二进制流响应头,处理中文文件名编码
    res.setHeader('Content-Type', filePost.mimeType || 'application/octet-stream');
    res.setHeader('Content-Disposition', `attachment; filename=${encodeURIComponent(filePost.originalName)}`);
    // 直接返回数据库中存储的文件Buffer
    res.send(filePost.file);
  } catch (err) {
    return next(new HttpError("服务器异常", 500));
  }
};

注意:前后端分离跨域部署时,需要在CORS配置中添加Access-Control-Expose-Headers: Content-Disposition,否则前端无法读取响应头中的文件名信息


前端(React)实现

原生a标签的跳转请求无法携带自定义Authorization请求头,因此需要先通过fetch携带token拉取文件二进制内容,再手动创建临时a标签触发下载:

const handleDownload = async () => {
  try {
    const res = await fetch(`${process.env.REACT_APP_BACKEND_URL}/files/download/${props.id}`, {
      headers: { 'Authorization': auth.token },
      // 如果鉴权token存在cookie中,打开下面这行配置
      // credentials: 'include'
    });

    if (!res.ok) throw new Error('下载请求失败');

    // 从响应头解析文件名
    const disposition = res.headers.get('Content-Disposition');
    let fileName = '未命名文件';
    if (disposition) {
      const matchRes = disposition.match(/filename\*?=(?:UTF-8'')?([^;]+)/i);
      if (matchRes) fileName = decodeURIComponent(matchRes[1]);
    }

    // 将响应转为二进制Blob对象
    const fileBlob = await res.blob();
    // 生成临时Blob地址
    const tempUrl = window.URL.createObjectURL(fileBlob);
    // 创建临时a标签触发下载
    const tempLink = document.createElement('a');
    tempLink.href = tempUrl;
    tempLink.download = fileName;
    document.body.appendChild(tempLink);
    tempLink.click();
    // 清理临时DOM和内存地址,避免内存泄漏
    document.body.removeChild(tempLink);
    window.URL.revokeObjectURL(tempUrl);
  } catch (err) {
    console.error('下载失败:', err);
    // 此处可添加项目内的全局错误提示逻辑
  }
}

return (
  <button onClick={handleDownload}>Download File</button>
)

常见踩坑点

  • 禁止对二进制文件流调用.json()方法解析,该方法仅能处理JSON格式响应,文件类响应必须使用.blob()或.arrayBuffer()解析
  • 中文文件名必须经过encodeURIComponent编码后放在响应头,前端拿到后再解码,否则会出现乱码
  • 大文件下载场景下建议添加进度提示,避免用户重复点击
  • 鉴权失败(401状态码)的逻辑可以在fetch的响应判断中统一处理,比如跳转到登录页

内容的提问来源于stack exchange,提问作者V.T

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 04:54:17