云存储里的图片和视频突然被外部网站批量引用,当月账单流量费翻了好几倍,这是很多团队接入小程序云存储后遇到的真实现状。云开发存储和腾讯云COS默认生成的文件链接,如果没有做任何访问控制,拿到链接的人都可以直接嵌入自己的网站或App使用。要堵住这个口子,核心手段就是两件事:一是配置Referer防盗链白名单,二是正确处理跨域访问的CORS策略。这篇文章把两套机制的原理、配置步骤和容易踩的坑完整讲一遍。

Referer防盗链的工作原理与小程序的特殊性
防盗链的本质是服务端校验请求来源。浏览器在加载图片、视频等资源时,会在HTTP请求头里带上Referer字段,标明这个请求是从哪个页面发起的。服务端拿到Referer后,与预先配置的白名单做匹配:匹配成功就放行,匹配失败或者字段为空,则返回403拒绝访问。这套机制对普通的Web站点非常有效,因为浏览器行为是标准化的。
但小程序环境有个关键差异需要先搞清楚:微信小程序的<image>、wx.downloadFile等原生请求,默认情况下不会携带Referer头,或者携带的头信息无法与你的域名白名单对应上。这就导致很多开发者照着Web经验配了Referer白名单,结果自己小程序里的图片全部加载失败。所以在动手之前,必须先确认你的资源访问路径里,哪些请求来自H5页面,哪些来自小程序原生组件。
对于H5页面(比如公众号网页、内嵌Web-view),Referer防盗链可以正常生效。而对于小程序原生请求,更稳妥的做法是使用签名URL,也就是带时效签名的临时链接。云开发的getTempFileURL接口和COS的签名工具都会生成这类链接,即使链接被第三方拿到,过期之后也就无法访问了,防盗效果比Referer更可靠。
腾讯云COS防盗链与CORS的实战配置
小程序云开发的存储底层就是COS对象存储,很多项目也会直接使用COS桶。登录腾讯云控制台,进入对应存储桶的管理页面,在「安全管理」或「域名与传输管理」里可以找到防盗链设置项。配置时建议遵循最小授权原则:白名单里只放自己业务域名,不要贪图方便使用通配符放行过宽的范围。
CORS跨域的配置在另一个入口:存储桶的「跨域访问CORS设置」。当你的网页前端通过JavaScript直接调用COS接口上传或下载文件时,浏览器会先发起OPTIONS预检请求,服务端必须在响应里返回正确的Access-Control-Allow-Origin等头信息,否则请求直接被浏览器拦截。一个典型的CORS规则配置如下:
// 使用COS的js-sdk配置跨域上传,需在控制台先设置CORS规则
cos.putObject({
Bucket: 'example-1250000000', // 存储桶名称
Region: 'ap-shanghai', // 地域
Key: 'images/photo.png', // 上传后的文件路径
Body: file, // 文件对象
}, function(err, data) {
if (err) {
console.error('上传失败', err);
return;
}
console.log('上传成功', data.Location);
});配置CORS规则时有三个字段容易出错。来源Origin要写完整的协议加域名,例如https://www.ipipp.com,漏写协议会导致匹配失败;允许Methods要根据实际操作勾选,上传场景至少包含PUT和POST,删除文件需要DELETE;允许Headers里要包含content-type和authorization,否则签名请求的预检会报错。最大过期时间保持默认300秒即可,没有必要调大。
防盗链与CORS冲突排查及替代方案
两套机制同时启用时,经常出现一种隐蔽的冲突:CORS配置完全正确,但接口依然报跨域错误。原因往往是防盗链先一步把OPTIONS预检请求拦截了,返回的403响应缺少CORS响应头,浏览器便判定为跨域失败。排查方法是看浏览器Network面板,如果OPTIONS请求状态码是403而非204或200,就要回头检查防盗链规则,把预检请求放行,或者在白名单中加入发起跨域请求的域名。
对于小程序原生请求无法利用Referer的场景,还有几种替代方案值得考虑。第一种是云函数中转:前端先调云函数换取临时签名URL,再拿签名URL访问存储,控制权完全在自己手里。第二种是私有读存储桶,直接关闭公有读权限,所有访问必须带签名,安全等级最高,代价是所有访问都要走签名流程。第三种是自建网关校验,在Nginx层做Referer白名单加签名双保险:
# Nginx配置Referer白名单示例
location /storage/ {
valid_referers none blocked server_names
*.ipipp.com
www.ipipp.com;
if ($invalid_referer) {
return 403;
}
proxy_pass https://your-bucket.cos.ap-shanghai.myqcloud.com/;
}这套Nginx配置里,none表示允许Referer为空的请求通过,blocked表示允许被代理隐藏了Referer的请求。如果你的业务全部来自自家H5页面,建议把这两项去掉,只保留具体域名,防盗效果最强。最后再提一点运维习惯:无论采用哪种方案,都要定期查看存储桶的访问日志,发现异常流量来源及时更新白名单,防盗链配置不是一次性的工作,而是需要随业务变化持续调整的动态策略。