如何为document类型元数据请求添加XCompanyAccessToken请求头?
我需要用JavaScript和Fetch API实现带认证的文件下载功能,但浏览器会自动发起一个document类型的元数据请求,这个请求未携带必要的自定义请求头XCompanyAccessToken中的访问令牌,导致后端无法生成待下载的文件。
我目前使用的是网上找到的代码:
function download(fileUrl) { let fileName; fetch(fileUrl, { method: 'GET', headers: { 'XCompanyAccesstoken': '<access-token>', /* 其他请求头,此处省略 */ }, mode: 'cors', }) // 下面的then块仅处理gzip压缩的文件,保留下来是为了完整展示 .then(response => { const contentEncodingHeader = response.headers?.get('Content-Encoding'); if (contentEncodingHeader === 'gzip') { // 解析Content-Disposition获取文件名 const contenDispositionHeader = response.headers?.get('Content-Disposition'); const fileNameSplit = contenDispositionHeader?.split('filename=')?.[1]; // 移除首尾双引号 fileName = JSON.parse(fileNameSplit ?? '""'); return response.blob(); } return null; }) .then(blobby => { if (blobby) { const objectUrl = window.URL.createObjectURL(blobby); anchor.href = objectUrl; anchor.download = fileName; anchor.click(); window.URL.revokeObjectURL(objectUrl); } }) }
这些请求头在fetch类型的请求中能正确设置,但在document类型的元数据请求里缺失。
后端已暴露必要的响应头:
access-control-expose-headers: Content-Disposition, Content-Encoding
且响应头设置正确:
content-disposition: attachment; filename="2022-10-12_13-12_download.csv" content-type: text/csv; charset=UTF-8
(注:未设置content-encoding头)
目前会向后端发起三次请求:
- 预检请求(正常执行)
- 元数据请求(需要令牌但未携带)
- Fetch请求(携带令牌)
我试过用各种关键词搜索,比如how can i add a custom header to the fetching of metadata for downloads?,未找到相关解决方案。
另外注意到浏览器发送元数据请求时会携带Cookie,因此尝试用代码将访问令牌添加到Cookie中,但Cookie头未包含该设置的Cookie,此方法无效。
- 是否有办法解决此问题?
- 能否让浏览器发起
document类型的元数据请求时,带上自定义的XCompanyAccessToken请求头? - 是否需要对后端进行相应修改?
方案1:跳过元数据请求,完全通过Fetch控制下载流程
浏览器发起元数据请求是因为通过anchor.click()触发了默认下载行为,浏览器会提前请求文件元数据。可以直接在Fetch拿到Blob后创建下载链接并触发下载,全程由Fetch控制,避免浏览器发起额外请求。
针对你的场景(无gzip压缩),简化后的代码如下:
function download(fileUrl) { fetch(fileUrl, { method: 'GET', headers: { 'XCompanyAccesstoken': '<access-token>' }, mode: 'cors' }) .then(response => { if (!response.ok) throw new Error('请求失败'); // 解析文件名 const disposition = response.headers.get('Content-Disposition'); let fileName = 'download.csv'; if (disposition) { const match = disposition.match(/filename="?([^"]+)"?/); if (match) fileName = match[1]; } return response.blob(); }) .then(blob => { const objectUrl = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = objectUrl; a.download = fileName; a.click(); URL.revokeObjectURL(objectUrl); }) .catch(err => console.error('下载失败:', err)); }
这种方式下,浏览器只会发起Fetch请求(含预检请求),不会触发额外的元数据请求,因为下载是通过Blob创建的本地URL触发,而非直接指向远程资源。
方案2:后端配合,改用Cookie传递认证令牌
如果必须保留浏览器的元数据请求,只能改用Cookie传递令牌。此前用JS设置Cookie失败,大概率是因为Cookie的SameSite、Secure、Domain等属性不符合要求,或域名不匹配。
需要后端配合修改:
- 后端设置Cookie时指定正确的
Domain、Path、SameSite(如SameSite=Strict或Lax)、Secure(HTTPS环境下)属性; - 后端修改认证逻辑,优先从Cookie中读取
XCompanyAccessToken的值; - 前端通过后端接口设置Cookie,而非直接用JS操作(避免跨域或属性限制)。
方案3:后端对元数据请求跳过认证检查
如果元数据请求仅用于获取文件名、文件大小等基础信息,可让后端对document类型的请求跳过认证检查,仅对实际下载的Fetch请求做认证。但此方式存在安全风险,需评估是否允许未认证用户获取文件元数据。
内容的提问来源于stack exchange,提问作者Dominik Reinert

