Vue3中用Service Worker拦截图片请求加Authorization头遇401问题求助
问题背景
我需要展示Laravel API提供的私有图片,所有请求必须携带包含Bearer token的Authorization头。目前用Quasar(基于Vue3)开发,不想把token直接拼在URL里,尝试用Service Worker拦截请求并添加头部,但始终返回401错误,请求头里并没有带上Authorization。
现有代码
Vue组件代码
<template> <img :src="file" /> </template> <script> export default { name: 'MyComponent', setup(props, context) { const file = 'http://localhost:8888/files/stream/watermark.png' onMounted(async () => { await navigator.serviceWorker .register('/service-worker.js', { scope: 'http://localhost:9999/files/stream/' }) .then(registration => { console.log('ServiceWorker registration successful with scope: ', registration.scope) }) .catch(err => { console.log('ServiceWorker registration failed: ', err); }) }) return { file } } </script>
Service Worker代码
self.addEventListener('fetch', event => { event.respondWith(customHeaderRequestFetch(event)) }) function customHeaderRequestFetch(event) { const newRequest = new Request(event.request, { mode: 'cors', headers: { 'Authorization': 'Bearer PyIlosdka73Jksdjklasd...' } }) return fetch(newRequest) }
Docker + Nginx配置
server { listen 80; server_name localhost; index index.php index.html; error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; root /var/www/public; client_max_body_size 32M; location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass api:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location / { try_files $uri $uri/ /index.php?$query_string; gzip_static on; } location /storage { add_header "Access-Control-Allow-Origin" "*"; } }
问题现象
- Service Worker注册成功,控制台输出:
ServiceWorker registration successful with scope: http://localhost:9999/files/stream/ - 图片请求返回401,请求头中无Authorization字段,完整请求头如下:
Request Headers Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: en,es;q=0.9,en-US;q=0.8,es-419;q=0.7 Connection: keep-alive DNT: 1 Host: localhost:8888 Referer: http://localhost:9999/ sec-ch-ua: "Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Linux" Sec-Fetch-Dest: image Sec-Fetch-Mode: no-cors Sec-Fetch-Site: same-site User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
解决方案与替代方案
1. 修正Service Worker作用域问题
当前Service Worker注册的作用域是http://localhost:9999/files/stream/,但图片请求的是http://localhost:8888下的资源,跨域情况下Service Worker无法拦截不同origin的请求。
- 解决方式:将Service Worker部署到Laravel API的域名(localhost:8888)下,或者调整作用域覆盖API的图片路径;如果必须在前端域名部署,需要确保CORS配置完全兼容跨域拦截逻辑。
2. 处理no-cors请求模式限制
从请求头可见Sec-Fetch-Mode: no-cors,这种模式下浏览器会禁止自定义请求头的传递,即使Service Worker修改了请求,Authorization头也不会被发送。
- 替代实现:改用
fetch主动请求图片(携带Authorization头),将响应转为Blob后生成Object URL给<img>标签:
<template> <img :src="imageUrl" /> </template> <script> import { ref, onMounted } from 'vue' export default { name: 'MyComponent', setup() { const imageUrl = ref('') const fileUrl = 'http://localhost:8888/files/stream/watermark.png' const token = 'PyIlosdka73Jksdjklasd...' onMounted(async () => { try { const response = await fetch(fileUrl, { headers: { 'Authorization': `Bearer ${token}` } }) if (!response.ok) throw new Error('图片请求失败') const blob = await response.blob() imageUrl.value = URL.createObjectURL(blob) } catch (err) { console.error('加载图片失败:', err) } }) return { imageUrl } } </script>
3. 完善CORS配置
Laravel和Nginx需要允许Authorization头跨域:
- 在Nginx的
/files/stream或全局location中添加:
add_header "Access-Control-Allow-Origin" "http://localhost:9999"; add_header "Access-Control-Allow-Methods" "GET, OPTIONS"; add_header "Access-Control-Allow-Headers" "Authorization";
- 同时在Laravel中配置CORS(例如使用
barryvdh/laravel-cors包),允许前端域名和Authorization头。
4. 替代方案:使用Laravel签名URL
如果不想用fetch转Blob的方式,可以让Laravel生成带有效期的签名URL,前端直接使用该URL请求图片,无需携带Authorization头:
use Illuminate\Support\Facades\Storage; // 生成5分钟内有效的签名URL $url = Storage::temporaryUrl( 'files/stream/watermark.png', now()->addMinutes(5), ['responseContentDisposition' => 'inline'] );
内容的提问来源于stack exchange,提问作者Luciano
相关产品推荐
相关产品推荐

