19、屏幕共享安全:权限验证、HTTPS强制、屏幕内容水印、防截屏措施
屏幕共享这个功能,说白了就是把你的整个桌面或者某个窗口,实时地“直播”给别人看。你想想看,这中间传递的可能有公司内部文档、客户资料、甚至你还没来得及关掉的银行页面。所以,安全这块要是没做好,那简直就是把家门钥匙直接交给别人了。
我个人习惯,在开始写任何屏幕共享代码之前,先想清楚一个问题:“如果这个画面被恶意截取或篡改,后果是什么?” 想清楚这个,你自然就知道安全措施要做到什么程度了。
权限验证:别让不该看的人进来
屏幕共享的第一步,不是技术实现,而是权限验证。我记得有一次做项目,对方要求共享画面只能给特定部门的几个人看。我当时第一反应就是:“这必须在信令层面就卡死。”
你不能等到媒体流都建立起来了,才发现对方没权限。那太晚了。正确的做法是:
- 房间级权限:用户加入共享房间前,服务端校验其身份和角色。比如只有“主持人”才能发起共享,“观众”只能看。
- Token 机制:生成一个有时效性的 Token,携带用户信息和权限范围。WebRTC 连接建立前,先验证 Token 是否有效。
- 动态踢人:如果发现某个用户行为异常(比如短时间内多次请求不同屏幕),服务端可以主动断开其连接。
核心原则:权限验证必须在服务端完成,不能依赖客户端。客户端的数据,永远不要轻信。
HTTPS强制:不是选项,是底线
WebRTC 的 getUserMedia 和 getDisplayMedia 这两个 API,在非安全上下文(也就是 HTTP)下是根本不能用的。这是浏览器的硬性规定。
为什么会这样?因为屏幕共享涉及用户隐私,如果走 HTTP,中间人攻击可以轻松篡改或窃取你的屏幕数据。你想想看,要是有人在咖啡厅的公共 WiFi 上截获了你的屏幕共享流,那画面太美我不敢看。
我建议你在项目初期就把 HTTPS 配好。别想着“开发环境先用 HTTP,上线再改”。我见过太多因为开发环境混用 HTTP 和 HTTPS 导致的各种奇葩 bug,比如摄像头打不开、屏幕共享按钮点了没反应。
小技巧:开发时可以用 mkcert 工具生成本地信任的 SSL 证书,这样 localhost 也能跑 HTTPS,完美模拟线上环境。
屏幕内容水印:给画面打上“烙印”
权限和 HTTPS 解决了“谁能看”和“传输安全”的问题。但还有一个问题:“如果接收方直接拿手机对着屏幕拍,怎么办?”
嗯,这就是水印要干的事。水印不是为了阻止截屏,而是为了事后追溯。我在项目中用过两种方式:
- 静态水印:在共享的 canvas 或 video 元素上,叠加一个半透明的文字或 Logo。比如显示“用户张三 - 2025-01-15 14:30”。
- 动态水印:水印内容随时间或用户操作变化。比如每隔几秒变换位置,或者包含一个不断滚动的序列号。
实现上,我习惯在发送端做水印。也就是在 getDisplayMedia 获取到屏幕流之后,通过一个 canvas 把水印画上去,然后再把这个 canvas 的流作为最终的媒体流发送出去。
// 伪代码示例:在屏幕共享流上叠加水印
const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true });
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 将屏幕流绘制到 canvas
const video = document.createElement('video');
video.srcObject = screenStream;
await video.play();
function drawWatermark() {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
ctx.font = '20px Arial';
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fillText(`用户: ${userId} | ${new Date().toLocaleString()}`, 20, canvas.height - 20);
requestAnimationFrame(drawWatermark);
}
drawWatermark();
// 用 canvas.captureStream() 替换原始屏幕流
const watermarkedStream = canvas.captureStream(30);
// 然后把这个 watermarkedStream 传给 RTCPeerConnection
注意:水印会增加客户端的 CPU 开销。如果用户机器性能较差,可以考虑降低水印的刷新频率,或者只在关键帧上绘制。
防截屏措施:能防多少防多少
说实话,纯前端想完全防止用户截屏,是不可能的。操作系统层面就有截屏快捷键,你管不了。但我们可以增加截屏的难度,或者让截屏后的画面失去价值。
我常用的几个手段:
- 监听 visibilitychange 事件:当用户切换到其他应用或浏览器标签页时,自动暂停屏幕共享或模糊画面。这样即使对方想截屏,也只能截到一张模糊图。
- 使用 CSS pointer-events: none:在共享画面上覆盖一层透明遮罩,防止用户通过开发者工具直接操作 DOM 元素。
- 禁止右键菜单:虽然防不了系统截屏,但可以防止用户通过右键“另存为”来保存画面。
- 检测屏幕录制 API:部分浏览器支持 MediaRecorder API,你可以尝试检测是否有录制行为,但这个方法不太可靠,容易被绕过。
我的经验:防截屏最好的办法,其实是让用户觉得“没必要截屏”。比如在共享内容中实时更新数据,或者加入动态的交互元素。静态画面才容易被截,动态内容截了也没用。
知识体系总览
下面这张图,是我对屏幕共享安全这块的总结。你可以把它当作一个检查清单,做项目时对照着来。
最后说一句,安全这件事,永远没有“做完”的时候。你只能不断地根据新的威胁去加固。我曾经在一个项目中,上线后才发现有人用虚拟机跑屏幕共享,绕过了我们的设备指纹校验。嗯,从那以后,我又加了一层行为分析。
做屏幕共享,胆子要小一点,心要细一点。