如何在前端为 <img> 和 PDF 资源请求添加自定义请求头

在现代 Web 开发中,我们经常需要加载受权限保护的资源,比如带有身份认证(如 Bearer Token)的图片或 PDF 文件。然而,标准 HTML 中的 <img> 标签、<iframe><object> 等元素无法直接配置 HTTP 请求头。这意味着你不能像使用 fetch 那样,在标签层面传递 AuthorizationX-API-Key 等头部信息。

但这并不意味着无解!本文将为你详细拆解如何通过“曲线救国”的方式——先用 JavaScript 获取受保护资源,再将其转换为可嵌入页面的数据 URL 或 Blob URL,从而实现带请求头加载图片和 PDF 的需求。


一、为什么 <img> 不能设置请求头?

HTML 原生标签(如 <img src="...">)发起的请求是由浏览器自动处理的,开发者无法干预其请求头。这是出于安全和简洁性的设计考量。因此,若服务器要求特定请求头(如 JWT Token),直接使用 <img> 将导致 401/403 错误。

解决方案的核心思路

使用 fetchXMLHttpRequest 手动发起带请求头的请求 → 获取二进制数据 → 转换为 Blob URL → 赋值给 <img><iframe>src


二、加载带认证头的图片

实现步骤

  1. 使用 fetch 请求图片资源,并在 headers 中添加认证信息;
  2. 将响应转为 Blob 对象;
  3. 通过 URL.createObjectURL(blob) 生成本地 URL;
  4. 将该 URL 赋给 <img>src 属性;
  5. 图片加载完成后,调用 URL.revokeObjectURL() 释放内存。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
async function loadImageWithAuth(url, token, imgElement) {
try {
const res = await fetch(url, {
headers: {
Authorization: 'Bearer ' + token
}
});

if (!res.ok) throw new Error('Failed to load image');

const blob = await res.blob();
const objectUrl = URL.createObjectURL(blob);

imgElement.src = objectUrl;

// 清理内存:图片加载完成后释放 Blob URL
imgElement.onload = () => {
URL.revokeObjectURL(objectUrl);
};
} catch (error) {
console.error('Error loading image:', error);
imgElement.src = ''; // 可选:显示占位图或错误提示
}
}

// 使用示例
const img = document.getElementById('protected-image');
loadImageWithAuth('https://api.example.com/image.jpg', 'your-jwt-token', img);

优点:兼容性好(现代浏览器均支持 fetchBlob URL);
⚠️ 注意:频繁创建未释放的 Blob URL 会导致内存泄漏,务必在 onload 或组件销毁时调用 revokeObjectURL


三、加载带认证头的 PDF 并在页面预览

PDF 文件通常通过 <iframe><embed> 或 PDF.js 渲染。与图片类似,这些标签也无法携带请求头。我们可以用相同思路:先获取 PDF 的二进制流,再生成可嵌入的 URL。

实现步骤

  1. fetch 获取 PDF 资源(带 Authorization 头);
  2. 读取 response.bodyReadableStream
  3. 将流数据分块收集并合并为 Blob
  4. 创建 Blob URL 并赋给 <iframe>src

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
async function loadPdfWithAuth(url, token, iframeElement) {
try {
const res = await fetch(url, {
headers: {
Authorization: 'Bearer ' + token
}
});

if (!res.ok) throw new Error('Failed to load PDF');

const reader = res.body.getReader();
const chunks = [];

while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}

const blob = new Blob(chunks, { type: 'application/pdf' });
const objectUrl = URL.createObjectURL(blob);

iframeElement.src = objectUrl;

// 清理内存
iframeElement.onload = () => {
URL.revokeObjectURL(objectUrl);
};
} catch (error) {
console.error('Error loading PDF:', error);
iframeElement.src = ''; // 或跳转到错误页面
}
}

// 使用示例
const pdfIframe = document.getElementById('pdf-preview');
loadPdfWithAuth('https://api.example.com/report.pdf', 'your-jwt-token', pdfIframe);

🔍 替代方案:如果你使用 PDF.js,可以直接传入 Uint8ArrayArrayBuffer,无需生成 Blob URL,更灵活且支持分页、缩放等高级功能。


四、注意事项与最佳实践

项目 建议
内存管理 务必在资源加载完成后调用 URL.revokeObjectURL(),避免内存泄漏
错误处理 网络失败、认证过期等情况需有用户友好的反馈
CORS 问题 确保后端设置了正确的 CORS 头(如 Access-Control-Allow-Origin
大文件性能 对于超大图片/PDF,考虑分块加载或懒加载,避免阻塞主线程
缓存策略 fetch 默认不走浏览器缓存,如需缓存可配合 Cache API 或服务端协商缓存

五、总结

虽然 HTML 原生标签无法直接设置请求头,但借助现代 Web API(fetch + Blob + URL.createObjectURL),我们完全可以优雅地绕过这一限制。无论是私有图片、加密 PDF,还是其他二进制资源,只要能通过 JavaScript 获取,就能安全地嵌入页面。

记住:这不是 hack,而是标准 Web 平台能力的合理组合。

希望本文能帮你解决实际开发中的权限资源加载难题。如有更复杂的场景(如流式渲染 PDF、图片懒加载 + 认证),也欢迎进一步探讨!


延伸阅读