解决微博图床防盗链的问题

对于不少自己搭建博客的人来说,图床的选择可真是一个大难点,以前还有各种免费好用的图床工具,例如七牛云、又拍云、SM.MS、Imgur、GitHub、微博图床等,当然还有腾讯云、阿里云的云存储服务,但是免费的意味着不稳定,说不定哪天图片就没有了,有一些国外的访问速度又不行,国内的云存储服务商收费又比较高,还有的必须绑定认证的域名才能使用。本来搭建一个小小的博客,只为了记录知识,传播技术,遇到耗财或者耗精力的这种问题,都比较头疼。

后来纠结了好几天,最终决定使用免费的 微博图床 ,一是因为新浪微博这家厂商体量大,微博图床短期内应该不会出问题,二是看到好多网友说他们已经稳定使用微博图床 3-5 年了,没有出过问题。我大概使用的时间还没有一年,以前都是本地化的,没有整理成完整的文章,后来开始慢慢整理并部署上线。没想到最近【2019 年 4 月 24 日左右发现】微博图床出问题了,访问图片链接全部是返回 403 状态码,表示拒绝访问,其实是微博图床开启了防盗链,本文就记录这个现象以及可行的解决方案。

微博图床防盗链开启

初始现象

在 2019 年 4 月 24 日的时候,我发现一个严重的问题,我的博客里面的图片显示不出来了,并不是被封了,如果被封也会显示图片的,只不过是马赛克图片。发现这个问题的缘由是新写了一篇博客,本地生成测试的时候,发现图片全部不显示了,一开始还以为是网络问题。

博客里面的图片全部无法正常显示
图片全部无法正常显示

接着我随机抽了一些图片链接在浏览器中直接打开看,发现是可以看到图片的,然后在博客中还是看不到图片,如果在博客中选择图片链接,使用右键 在新标签页中打开 ,也是不能看到。这就说明微博图床开始检测请求的合法性了,对于不正常的请求统统拒绝。

当然,如果直接使用图片的链接在浏览器中单独打开,是可以看到图片的,紧接着在博客中就可以看到对应的图片了,但是这并不是说明图片可以使用了,其实是浏览器的缓存作用,如果及时清除浏览器的缓存,发现又不能使用了。

复制图片地址在浏览器中打开,图片可以正常显示
在浏览器中打开图片可以正常显示

分析现象

接着使用浏览器的调试工具查看详细的请求信息,按 F12 按键,调出调试工具,刷新网页,使用 jpg 过滤无效内容,可以看到所有的图片访问请求结果都是 403,也就是拒绝访问。

随便点开一个链接的请求信息,查看 Status Code 为 403,也就是拒绝访问,注意查看请求头的 Referer 参数,值是一个链接,表示当前请求所属的页面,即 引用来源 ,而新浪微博恰好会检测这个参数,拒绝所有的外链请求,即不是从新浪的站点发送的图片请求。
403 拒绝访问

原来,近期微博图床对图片 CDN 添加了引用来源【Referer】检测,非微博站内引用将会返回 403 错误码,即拒绝访问。那能不能伪造或者清除这个参数呢,其实是可以的,只不过伪造、清除都需要增加一些 Javascript 动态脚本来处理,需要一些技术支持。

如果选择清除 Referer 参数,可以先验证一下,把图片的链接直接复制到浏览器中访问,就不会有这个参数,发现可以正常访问,没有 403 错误。
单独在浏览器中访问

注意,一开始我发现使用浏览器能直接访问,紧着着博客里面的图片也能访问了,我还以为是需要单独访问一次图片,然后就可以任意访问了,后来发现其实是浏览器缓存的作用,空欢喜一场。
缓存欺骗了我

也看到有说法是,微博图床仅仅针对开启 SSL 的链接【即 HTTPS】实行站外禁止访问,而普通的 HTTP 链接仍旧安然无恙,这种说法是错误的【但是确实有这种现象出现】。我测试了一下,的确是有这样的现象,前提是来源页面开启了 SSL,而图床链接使用基本的 HTTP,这样的话由于 Referer 的特性,请求图片链接时不会传输 Referer 这个参数的值【即来源页面的信息不会传递给请求页面】,微博图床自然也就无法检测了。所以最简单的方案就是把所有微博图床的链接全部由 HTTPS 替换为 HTTP,但是由于我的博客全面开启了 SSL,为了加绿锁,因此不引用普通的 HTTP 链接,这种简单的方案我就无法采用了,只能遗憾舍弃。

解决方案

考虑切换图床,免费的已经基本没有了,收费的比较贵,或者找到方案先临时使用,不然会给查看博客的人带来很大困扰,毕竟没有图片的博客怎么能看,这也影响博客的质量与声誉。

尝试清除来源引用

在静态网页的 头部 代码中【即 head 标记】添加如下配置项:

1
<meta name="referrer" content="no-referrer" />

它的作用就是阻止浏览器发送 Referer 信息,对整个页面的所有链接生效【当然也有针对单个链接设置的方法:<a rel=”noreferrer” href=”your-website-url” />,这里不采用】,这样一来微博图床就不知道请求的引用来源了,可以达到和直接在浏览器中访问一样的效果。 但是要注意,不是每种浏览器都支持这种语法的,此设置对有的浏览器来说无效。

那么在 Hexo 框架中怎么增加呢,显然不会有相关配置项,只能更改源代码,而且使用了 Next 主题,应该要更改主题的源代码,以保证 Hexo 在渲染静态页面为每个页面都增加这个配置。查阅文档,了解了渲染模板所在位置,打开 themes/next/layout/_partials/head.swig 文件,在里面添加 meta 标记就行。
修改 head.swig 文件

修改完成后查看页面的源代码,已经有这个属性了,并且所有的图片都可以正常访问了,完美。
查看页面的源代码

但是我觉得这肯定不是长久之计,以后说不定还会有幺蛾子出现,所以要随时准备着。

尝试其他方案

先观察一段时间,这段时间要考虑其他方案的可行性和成本。

建议

微博图床开启防盗链,个人博客对于新浪图床的依赖时代基本要告别了,虽然有其他免费图床可以使用,但稳定性和可持续性上显然无法与大企业维护的图床相比。为了博客内容稳定考虑,还是考虑后续逐渐把图片迁移到其他云存储空间,费用方面能承受就行。

其他知识点

微博图床简单介绍

对于大多数个人博客维护者而言,免费的图床既节省成本,也能够提升页面访问的速度,而新浪微博图床则成了首选。

新浪微博由于本身体量大,其图床免费无限容量,只需要有一个微博账号就可使用。同时具备全网 CDN 加速,支持 HTTPS,无论是国内还是国外网络访问,速度都很不错。而且新浪如此企业,不会像其他个人或者团队经营的免费图床一样随时可能会关掉。

基于这些优势,不少人会优先选择新浪微博图床作为网站提供图片服务。毕竟直接挂 CDN 或者自建图床的话,也是一个持久的付费维护,如果一旦被攻击,更是造成费用暴增。

Referer

Referer 首部包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 Referer 首部识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。同时也让服务器能够发现过时的和错误的链接并及时维护。

需要注意的是 referer 实际上是 referrer 的误拼写,它可能是 HTTP 协议中第一个被拼写错误的标准头,为保持向下兼容就将错就错了。可以参见 RFC 文档的 Referer 的介绍:
https://tools.ietf.org/html/rfc2616#section-14.36 ,原文有这样的描述:

the “referrer”, although the header field is misspelled.

此外还可以参考维基百科的相关介绍:
https://zh.wikipedia.org/wiki/HTTP% E5%8F%83% E7%85% A7% E4% BD%8D% E5%9D%80

在以下几种情况下,Referer 不会被发送

  • 来源页面采用的协议为表示本地文件的 file 或者 data URI
  • 当前请求页面采用的是非安全协议,而来源页面采用的是安全协议【HTTPS】
  • 为整个页面设置 <meta name=”referrer” content=”no-referrer” />
  • 为单个链接设置 <a rel=”noreferrer” href=”your-website-url” />

注意第二种情况,如果你的博客开启了 SSL,可以使用 HTTP 的图片链接,就可以正常访问了。但是要牺牲你的博客的安全性,因为浏览器会检测到你的博客内容里面有普通的 HTTP 链接,就会导致不可信【尽管存在有效的证书,也没有用】,小绿锁会消失,并给出警告。

例如我的博客,为了测试,使用了一个 HTTP 图片链接,其它的图片都是 HTTPS 链接,可以发现 HTTP 的图片可以正常访问,其它的图片仍旧被拒绝访问了。此时,发现博客的小绿锁已经没有了,并且给出了警告提示。
不安全的方案

后记

使用上述的解决方案后,我又发现了一个严重的问题,由于清除了引用来源 referer,博客文章的地址就不会发送出去,导致我的 不蒜子 统计失效,也就是每篇文章的阅读数、整个站点的访问量【pv】、整个站点的访客数【uv】都会停止统计。这会导致整个博客的动态流量不可见,对于写博客的我来说内心会有一点点失落,所以我要想办法解决这个问题。

已经知道问题的根源了,解决起来也是很容易的,直接开启引用来源 referer 即可,但是由于和微博图床的图片防盗链冲突,不能同时开启。也就是说除了微博图床的防盗链要关闭 referer,其它的链接仍旧正常开启,看看能不能想办法只把微博图床的链接关闭 referer。

标记 a 可以增加 ref=”noreferrer” 属性,但是在 Hexo 中我无法找到合适的方式来完成这个操作。本来准备在 _macro/post.swig 中对渲染后的标记属性进行替换,示例 swig 语句:

1
{{ post.content|replace ('group', 'noreferrer', 'g') }}

ref=”group” 替换为 ref=”noreferrer”,但是测试后发现行不通,传递过来的 content 只包含 p 标记,并没有 a 标记,也就是说明 a 标记是在其它地方渲染的。

而如果直接在渲染标记 a 的地方进行选择性替换,发现微博图床的图片链接,就把 ref 属性替换掉,需要去更改 Hexo 的源代码,其中有一个 markdown (str) 方法,显然这种临时方案不合理,也很麻烦。

为了稳定地解决这个问题,我还是决定更换图床,然后使用第三方工具进行图片迁移。

更换图床

和以前一样,挑选了一圈,也是很纠结,最终还是下定决心直接使用 GitHub 了,稳定又方便。其实就是新建一个仓库,专门用来存放图片,只不过需要考虑一下图片过多、图片过大会不会被 GitHub 限制。

去 GitHub 搜索帮助文档,帮助文档信息 ,可以得知仓库最大为 100GB,但是官方建议保持在 1GB 以下,单个文件低于 100MB,因此用来存放文件绰绰有余。另外需要注意,仓库文件超过 1GB 时会收到 GitHub 的提醒邮件,超过 75GB 时,每次在提交时都会收到警告。

原文描述如下:

We recommend repositories be kept under 1GB each. Repositories have a hard limit of 100GB. If you reach 75GB you’ll receive a warning from Git in your terminal when you push. This limit is easy to stay within if large files are kept out of the repository. If your repository exceeds 1GB, you might receive a polite email from GitHub Support requesting that you reduce the size of the repository to bring it back down.

In addition, we place a strict limit of files exceeding 100 MB in size.

既然有这种限制,最好还是把图片压缩一下,推荐使用图片压缩工具:Imagine ,这个工具可以实时看到压缩效果,而且压缩率还不错,能到 50%。但是,如果想要保持图片的色彩度、还原度,压缩效果肯定是不行的,甚至有时候压缩后的图片比压缩前的还大。

压缩图片示例
压缩图片示例

迁移图片

迁移图片本来是个很麻烦的事情,要把图片迁移、博客文章里面的链接替换掉,但是还好有现成的工具可以使用,在这里推荐:PicGo ,这个工具本来不是做图片迁移的,仅仅是图片上传生成链接而已,但是有人开发了插件,专门用来迁移 markdown 文件里面的图片,会自动迁移图片并且更新 markdown 里面的图片链接。这个插件是:picgo-plugin-pic-migrater ,而且,还可以支持批量迁移,指定一个文件夹,直接迁移文件夹里面的所有 markdown 文件。

迁移过程

详细的迁移步骤就不再记录,几个重要的步骤:在 GitHub 建立仓库、使用 PicGo 工具迁移图片,重新整理 markdown 文件。操作前切记备份好自己的 markdown 文件,以免迁移出现问题导致文件丢失。

在使用 PicGo 的过程中,发现总是迁移失败,重试了多次之后确定是因为在 markdown 语法中增加了注释,相当于给图片链接增加了 alt 属性【生成时图片会有一个 img 标记】,导致 PicGo 的插件识别不了,迁移失败。我已经在 GitHub 的项目中提了 issue:https://github.com/PicGo/picgo-plugin-pic-migrater/issues/1 ,作者也回复了,后续会修复。而我比较着急,等不了,又不可能把这些注释全部清除,也不好,所以我决定自己迁移,通过 Java 写代码解决。

写代码也比较简单,主要有四个步骤:读取 markdown 文件内容并利用正则抽取微博图床的图片链接、下载所有图片并上传至 GitHub、替换内容中抽取出的所有图片链接为 GitHub 的图片链接、内容写回新文件。

使用 Java 处理不需要多少代码,主要要依赖几个 jar 包:处理文件的 io 包、处理网络请求的 httpclient 包、处理 git 的 jgit 包。详细内容可以参考我的另外一篇博客:使用 Java 代码迁移微博图床到 GitHub 图床

小细节

针对 PicGo 的使用还有一些小细节可以注意一下:自定义域名、子文件夹路径、图片压缩【不压缩针对 GitHub 速度会很慢,能压缩到 200KB 最好】、文件重命名。

未来考虑

迁移完成之后,以后新的图片就直接使用 PicGo 上传到 GitHub 图床了,同时需要注意区分子文件夹。在 GitHub 仓库中,暂时每年新建一个文件夹,以年份数字为名称。

永不止步
0%