带凭证的MJPEG流通过JavaScript的img标签无法加载的技术求助
问题根源
现代浏览器出于安全规范,已经禁止通过JavaScript代码直接设置包含user:password@格式凭证的URL作为<img>标签的src属性——即使手动在地址栏输入该URL能正常加载,JS操作时浏览器会自动剥离凭证部分,导致请求无权限,图片无法显示。另外你现有代码还存在一个明显错误:motionjpeg_stream函数中,初始获取的src已经是带凭证和协议的完整URL(比如http://user:password@192.168.1.50/snap.jpg),但后续setInterval里又重复拼接http://" + credentials + "@",最终生成的URL会变成http://user:password@http://user:password@192.168.1.50/snap.jpg?xxx,这显然是无效的。
方案1:使用Fetch API获取图片并更新(推荐)
通过Fetch API发起请求并携带凭证,将返回的图片转为Blob URL赋值给<img>,绕过浏览器对URL嵌入凭证的限制。注意需要摄像头支持CORS,或者通过后端代理解决跨域问题。
修改你的motionjpeg_stream函数如下:
function motionjpeg_stream(id, credentials) { var image = $(id); if (!image.length) return; // 提取摄像头的基础URL(去掉初始的协议和凭证,避免重复拼接) var baseSrc = image.attr("src").replace(/^http:\/\/.*@/, ''); // 定时刷新图片 setInterval(async function() { try { // 构建请求,通过Headers携带Basic Auth凭证 const headers = new Headers(); headers.set('Authorization', 'Basic ' + btoa(credentials)); const response = await fetch(`http://${baseSrc}?${new Date().getTime()}`, { method: 'GET', headers: headers, credentials: 'include' // 携带凭证,根据场景选'include'或'same-origin' }); if (!response.ok) throw new Error('请求失败'); const blob = await response.blob(); // 先释放之前的Blob URL,避免内存泄漏 const oldSrc = image.attr("src"); if (oldSrc.startsWith('blob:')) { URL.revokeObjectURL(oldSrc); } // 生成新的Blob URL赋值给img const blobUrl = URL.createObjectURL(blob); image.attr("src", blobUrl); } catch (err) { console.error('加载图片失败:', err); } }, 1000); }
同时修改HTML中初始的src设置,去掉凭证部分:
// 原来的代码去掉凭证,只保留摄像头地址和接口 $("img#Camera_MJPEG_stream").attr("src", $.urlParam('ip') + "/snap.jpg");
方案2:配置摄像头开启CORS支持
如果你的摄像头支持配置HTTP响应头,添加以下CORS相关头,确保前端请求能正常携带凭证:
Access-Control-Allow-Origin: *(或指定你的前端页面域名,更安全)Access-Control-Allow-Credentials: trueAccess-Control-Allow-Headers: Authorization
配置完成后,方案1的代码就能正常工作。
方案3:后端代理(无CORS支持时使用)
如果摄像头不支持CORS,你可以搭建一个简单的后端代理,前端请求代理服务器,由代理服务器携带凭证去请求摄像头,再将图片返回给前端。
比如用Node.js写一个简单的代理示例(依赖Express和request库):
const express = require('express'); const request = require('request'); const app = express(); app.get('/camera-proxy', (req, res) => { const cameraIp = req.query.ip; const credentials = 'user:password'; // 可改为动态传入的凭证 const cameraUrl = `http://${cameraIp}/snap.jpg${req.url.includes('?') ? '&' : '?'}${req.query.t}`; // 转发请求并携带凭证 request.get(cameraUrl, { auth: { user: credentials.split(':')[0], pass: credentials.split(':')[1] } }).pipe(res); }); app.listen(3000, () => { console.log('代理服务器运行在3000端口'); });
然后前端修改请求地址为代理地址:
// 在motionjpeg_stream中修改fetch地址 const response = await fetch(`http://localhost:3000/camera-proxy?ip=${baseSrc.split('/')[0]}&t=${new Date().getTime()}`, { method: 'GET', credentials: 'same-origin' });
内容的提问来源于stack exchange,提问作者Vasekdvor

