配置脚本跑actions用referrerpolicy图片属性反防盗链
需要反防盗链的原因
文章同时在微信平台和Github Pages个人博客中发布,不想传两次图片的话,可以把微信平台当图床.
但是微信平台的图片有反盗链,不是在浏览微信文章的时候打开图片的话,无法显示.
防盗链的判断
判断请求图片时浏览器发送的referrer.
referrer是浏览器请求时自动带上的一个域名,表示是从哪个页面发起的请求.
如果referrer中带的链接是平台文章链接,则可以打开图片.
如果referrer链接是其它域名,则表示是其它网站引用的图片,会判断为盗链从而无法打开.
不过这次测试发现如果用盗链方式打开某一个链接打不开后,仅修改referrer的话,在一段时间内还是打不开,因此也可能有其它判断依据,但目前主要还是看referrer.
反防盗链的原理
有个浏览需求和防盗链有矛盾:在浏览器地址栏直接打开图片链接,需要能访问图片,而此时请求头不带referrer.
也就是说只要请求图片时候不带上referrer属性,后端会把请求识别为用户直接打开的图片链接(而不是处在某个页面中的请求),因此将其放行.
所以只要防盗链系统允许从地址栏敲链接直接浏览资源,而我们又能把页面中的图片请求时候带的referrer属性去除的话,就无法靠referrer属性防盗链.
以往的(2017年之前)反防盗链
旧的反防盗链方式,用js将图片嵌入iframe以达到不发referer的效果,之前本站使用的就是这个:https://github.com/jpgerek/referrer-killer
可能出现的问题:
- 如果img不是一开始就包在iframe中的话,浏览器在没执行js前就加载,依然是带referer请求(可能是预加载的机制)
- iframe中的格式得另外设置
- 复制到微信H5编辑器时,带的iframe识别不出来
可能还有其它办法(比如https页面带http请求不发referer,未验证此法)反防盗链,但应该还是iframe更合适通用.
用HTML新属性(最早2017年)反防盗链
Chrome 51开始为img引入了referrerpolicy属性.
具体可参考https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-referrerpolicy
因此设置referrerpolicy
为no-referrer
或same-origin
在反防盗链场景中不发送referrer.
此属性的支持情况参考:https://caniuse.com/mdn-html_elements_img_referrerpolicy
目前大部分浏览器均已支持此属性.ios safari根据这个表应该是不支持的,但实际测试了下却能使用,可能实际是支持的,也可能是因为ios safari的其它隐私保护机制导致的吧,未验证.
所以,为了反防盗链,要为img
加上属性referrerpolicy
并设置为no-referrer
.
<img src="https://mmbiz.qpic.cn/mmbiz_png/2FribXdgnhQP26F01ibXeUDJStbpb9ibmO7CYZH7dd1T8ZZ4INXOAueCNHZNvACIwGJ98r75eapc6InHHG6xSAzWA/0?wx_fmt=png" alt="img_referrerpolicy_support" referrerpolicy="no-referrer">
怎么在生成的html中加上referrerpolicy="no-referrer"
尝试过程
思考了几种加上这个属性的方案:
- 用js的逻辑来加
- markdown里面加
- 实现jekyll的插件加
- 实现Liquid的filter或者tag加
但是查阅了下,Github Pages对所使用的插件有白名单限制https://pages.github.com/versions/,因此后两种方案基本没法实现了,实际上我看了也不知道怎么实现.
接着考虑使用js来加,将之前的img放iframe,改成img都加上referrerpolicy="no-referrer"
:
window.onload = function() {
var imgs = document.querySelectorAll('img');
[].forEach.call(imgs, function(element, index, array) {
element.setAttribute("referrerpolicy","no-referrer");
});
}
不行,在网络请求中还是先看到了img请求带referer.
也许是时机不对?加上onload再试.
function killimgref() {
var imgs = document.querySelectorAll('img');
[].forEach.call(imgs, function (element, index, array) {
element.setAttribute("referrerpolicy", "no-referrer");
});
}
window.onload = killimgref;
document.onload = killimgref;
也还是不行,也许再找找其它事件能比请求img的时机更早.
但想到浏览器可能会有某种机制(预加载机制?)在接收到img的src时就去请求,那js再怎么改也就没用了.
要实现的效果更好更稳定的话,还是得从markdown生成的html入手.
jekyll使用kramdown从md生成html,因此参考kramdown也确实是这样的,缺点是inline-attributes不是标准的markdown的语法.
用kramdown的语法设置referrerpolicy
https://kramdown.gettalong.org/quickref.html#inline-attributes
很简单只要在图片后面加上{: referrerpolicy="no-referrer"}
就行.
{: referrerpolicy="no-referrer"}
如何自动设置referrerpolicy
在加图片的时候手工加上{: referrerpolicy="no-referrer"}
也不是不行,但文章可能很久才会写一次,因此这种语法下次肯定忘了.最好是写小作文时候,我只需要参考标准的markdown语法,由程序来生成额外的属性.
思考可能的方案
既然是自动的,那就要写个脚本来加.
add-noreferer-attr.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import glob
path = f'./_posts'
files = [f for f in glob.glob(path + "**/*.md", recursive=True)]
for file in files:
replacement = ""
with open(file, "r") as mdfilein:
for line in mdfilein:
if line.startswith('![') and line.find(r'{: referrerpolicy="no-referrer"}') < 0 and line.find(r'http') > 0:
if line.endswith('\r\n'):
ending = '\r\n'
else:
ending = '\n'
replacement = replacement + line.strip() + r'{: referrerpolicy="no-referrer"}' + ending
else:
replacement = replacement + line
# print(mdfile.readline())
with open(file, "w") as mdfileout:
mdfileout.write(replacement)
然后考虑在何处何时可以自动运行此脚本,比较容易想到有两种:
- git hook
- CI,如Github Actions
各有优缺点,git hook比较通用,ci的话则是根据CI平台的不同配置的语法不同.
但是git hook似乎是本地跑的脚本才行,而如果是github.dev上直接编辑的是否可以呢?
应该不行(未验证),因为如果连github.dev的编辑也可以跑git hook的脚本的话,等于可以用上github的算力做事情了,而这应该是不允许的.
所以为了以后直接在github.dev在线写文章脚本也可以直接运行,应该还是用CI比较合适,看了下免费的配额(每月2000分钟)也足够使用了,并且这边本来就是使用的Github Pages也是绑定在这个平台上了,也就无所谓多使用一个Github Actions了.实际上,Github Pages的生成发布过程,也是CI/CD.
细看了下Github Actions的文档,可以指定CI过程所挂钩的branch,而Github Pages也可以指定所发布的branch.
这样就可以区分编辑和发布所使用的branch,使用不同分支也避免了Github Pages发布流程多次运行:人类提交一次,CI触发脚本修改了又提交一次,因此只用一个分支的话就会导致Pages运行两次发布.
区分master和publish后整个发布过程比较清晰:
- 作者在master分支写文章提交
- master提交触发Github Actions
- Github Actions运行脚本将批量添加后的md文件提交到publish分支
- publish分支的提交触发Github Pages的发布
配置Github Actions跑add-noreferer-attr.py
这里的逻辑比较简单,根据 https://docs.github.com/en/actions/quickstart 改动下入门脚本就行:
name: GitHub Actions Demo
on:
push:
branches:
- master
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a $ event."
- run: echo "🐧 This job is now running on a $ server hosted by GitHub!"
- run: echo "🔎 The name of your branch is $ and your repository is $."
- name: Check out repository code
uses: actions/checkout@v2
- run: |
git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
git config user.email "GitHubActions@github.com"
git config user.name "GitHub Actions"
- run: echo "💡 The $ repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: run add-noreferer-attr.py
run: |
cd $
chmod +x add-noreferer-attr.py
./add-noreferer-attr.py
- run: |
git add -A
if [ -z "$(git status --porcelain)" ]; then
echo "No changes to commit"
else
git commit -m "auto add noreferer to publish in actions"
fi
- run: git push --force origin master:publish
- run: echo "🍏 This job's status is $."
更简单的实现
在写这篇文章的时候看到MDN的文档才发现可以直接在全局的模板html文件中加上 <meta name="referrer" content="no-referrer" />
,并且是这个项目中原本就有配置…但为啥没生效呢..?????
仔细看了下…才知道引号用错了…翻了下commit记录,发现在2019年的时候尝试使用no-referer来反盗链没成功才用的referer-killer的…但其实不是no-referer不行而是我加了引号不行…
看了下那天的谷歌搜索记录,发现今天又一次知道的东西之前就搜过了…然而现在才知道前年用no-referer不行的原因居然是因为引号太低级了,linter真的是很重要…
那天还带着错误的引号查为啥no-referer没生效…
总结
写了很多才知道同一个坑被我踩了两次才过去,但这次毕竟是第一次使用Github Actions,而以后说不定有其它更适合的场景可以使用,比如用linter规范项目避免符号错误的情况再次出现,还是有参考价值的.
参考
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Referer
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-referrerpolicy
旧的反防盗链方式,将图片嵌入iframe以达到不发referer的效果:https://github.com/jpgerek/referrer-killer
iframe反防盗链参考:https://happy123.me/blog/2017/10/31/http-referer-de-dao-lian-yu-fan-dao-lian/
https://kramdown.gettalong.org/quickref.html#inline-attributes