由于Gravatar官方的域名在中国大陆地区访问并不是很顺畅,所以一般自建的博客都会将地址替换成代理的地址。
例如我在友情链接页面中提到的将Gavatar头像URL中 https://secure.gravatar.com/avatar/
替换为以下任意一项代理地址
https://gravatar.loli.net/avatar/
https://sdn.geekzu.org/avatar/
https://dn-qiniu-avatar.qbox.me/avatar/
https://cravatar.cn/avatar/
这种方法虽然简单,但是可能面临不稳定的问题。因为这些第三方服务会有大量网站使用,或者服务提供者的服务器或者域名到期,都会导致无法访问。之前也有些开发者开发过typecho插件,可以缓存Gavatar头像到本地。但是目前为止,基本这类插件在几年前就处于无人维护状态了。
自建一个Gravatar代理
所以还是自建一个Gravatar代理相对靠谱(接下来的方法仅限服务器在海外)。
接下来以Nginx为例,首先在配置文件中添加:
location /avatar/ {
valid_referers none blocked .ushiromiya.com;
if ($invalid_referer) {
return 403;
}
proxy_pass https://secure.gravatar.com/avatar/;
proxy_set_header Host secure.gravatar.com;
}
便能实现指定的域名自动代理secure.gravatar.com
的avatar
目录下的头像文件,并且还能实现防盗链与盗链白名单的效果。在上述示例中,.ushiromiya.com
会匹配 ushiromiya.com
及其所有子域名,从而实现通用的防盗链规则。如果被盗链则返回403状态。
详细解释如下:
location /avatar/ {
指定了匹配 URL 路径 /avatar/ 的位置(location)块开始。valid_referers none blocked .ushiromiya.com;
设置防盗链规则。valid_referers
指令定义了允许访问的有效引用者列表。这里的规则是允许没有引用者或者被直接阻止的引用者,以及以.ushiromiya.com
结尾的引用者。if ($invalid_referer) {
开始一个条件判断块,用于检查引用者是否无效。return 403;
返回403错误码Forbidden,表示防盗链验证失败。proxy_pass https://secure.gravatar.com/avatar/;
将请求代理到目标地址https://secure.gravatar.com/avatar/
。proxy_set_header Host secure.gravatar.com;
设置代理请求的Host头部为secure.gravatar.com
,以确保请求正确路由到目标服务器。
以上总体实现了对 /avatar/
路径下的请求进行反向代理,并在防盗链验证失败时返回403错误状态码。
多个防盗链白名单
白名单如果要指定多个域名,则改为:
valid_referers none blocked .ushiromiya.com .example.com;
如果使用了Cloudflare的图片缓存功能,并且由于其他站点引用了反向代理头像而导致403错误页面也被缓存了,这是因为 Cloudflare缓存了错误响应。为了解决这个问题,可以在返回 403 错误时添加Cache-Control
指令,指示Cloudflare 不缓存错误响应。可以将以下代码添加到反向代理的配置中:
location /avatar/ {
valid_referers none blocked .ushiromiya.com;
if ($invalid_referer) {
add_header Cache-Control "no-store";
return 403;
}
proxy_pass https://secure.gravatar.com/avatar/;
proxy_set_header Host secure.gravatar.com;
}
上述配置在返回403错误时添加了Cache-Control
头部,其中设置了 "no-store"
,指示Cloudflare不缓存该响应。
防盗链还可以通过正则表达式匹配检查$http_referer
是否满足条件,如果不满足,则执行相应的操作:
if ($http_referer !~* ^https?://([^/]+\.)?ushiromiya\.com(:\d+)?$) {
add_header Cache-Control "no-store";
return 403;
}
在这里,使用了一个正则表达式来检查 $http_referer
,它匹配不符合以下条件的请求来源:
^https?://
:匹配以http://
或https://
开头的字符串([^/]+\.)?
:匹配一个或多个非斜杠字符,后跟一个点号,这部分表示可选的子域名,它允许零个或多个非斜杠字符,以及一个点号ushiromiya\.com
:匹配确切的字符串ushiromiya.com
字符串(:\d+)?
:匹配冒号后跟一个或多个数字字符的字符串,表示可选的端口号部分,它允许一个冒号和一个或多个数字字符
综合起来,该正则表达式的作用是匹配来自ushiromiya.com
及其子域名的请求来源。
因此,如果请求的来源不匹配上述正则表达式,即不是以 https?://([^/]+\.)?ushiromiya.com\.com(:\d+)?
开头的来源,那么就会触发防盗链条件,返回 403 Forbidden
错误。从而满足来源ushiromiya.com
及其子域名才被允许访问资源,其他来源将被禁止访问。
Nginx缓存头像
除此之外,使用Nginx进行反向代理时,Nginx本身也可以有缓存能力。通过在配置中启用代理缓存,Nginx可以缓存从目标服务器返回的响应,并在后续的请求中直接返回缓存的响应,而无需再次访问目标服务器。
proxy_cache cache_one;
proxy_cache_key $host$uri$is_args$args;
proxy_cache_valid 200 304 301 302 1d;
其中
proxy_cache
指定了一个缓存区名称,例如cache_one
。proxy_cache_key
指定了缓存键的组成,这里使用了$host$uri$is_args$args
,它包含了请求的主机、URI 以及查询参数。proxy_cache_valid
指定了哪些 HTTP 响应状态码应该被缓存,以及缓存的过期时间。其中状态码 200、304、301、302 都将被缓存,并设置了1天的过期时间。
这些状态码中:
- 200 正常响应
- 304 未修改,缓存不变(当客户端发送带有缓存验证的请求时,如果服务器认为客户端的缓存副本仍然有效,服务器会返回 304状态码,表示客户端可以继续使用其缓存副本,而无需重新下载)
- 302 临时重定向(当服务器希望临时将请求重定向到另一个URL时,可以返回302状态码。客户端在收到302响应后会跟随重定向,并向重定向的URL发送新的请求)
- 301 永久重定向(当服务器希望永久将请求重定向到另一个URL时,可以返回301状态码。与302类似,客户端在收到301响应后会跟随重定向,并向重定向的URL发送新的请求。不同之处在于,301响应表示该重定向是永久性的,客户端应该将请求的资源URL更新为重定向的URL)
缓存时间也可以是不同单位,s(秒)m(分钟)h(小时)w(周)
。
这样配置后,Nginx会根据指定的缓存键将响应存储在缓存区中,并在后续的请求中直接返回缓存的响应,而无需再次访问目标服务器,这有助于提高性能并减轻因为大量请求Gravatar服务器的负载。
Nginx缓存大小和空间判断
设置缓存之后,又会有一个问题,当大量不同的Gravatar文件被请求时,可能会导致缓存空间的消耗。为了控制缓存空间的使用,可以用如下的方式限制:
- 限制单个文件的最大缓存尺寸:可以使用
proxy_cache_max_size
指令来设置单个文件的最大缓存尺寸。如果超过该尺寸,将不会缓存该文件,并将其视为未缓存的请求。例如proxy_cache_max_size 10m
就是来限制单个文件的最大缓存为10M。 - 限制所有代理的总缓存量:可以使用
proxy_cache_path
指令中的max_size
参数来限制整个代理缓存的总容量。该参数指定缓存目录的最大大小。当缓存空间达到该限制时,Nginx会根据缓存策略选择要替换的缓存项。例如proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=100m
来限制代理缓存的总容量为100M。
其中:
proxy_cache_path
指定代理缓存的路径和配置。/path/to/cache
代理缓存的存储路径,根据实际情况替换levels=1:2
可选参数,指定缓存目录的层级结构,缓存目录的层级结构是1级目录和2级目录,这个参数的目的是将缓存文件分散到不同的子目录中,以提高文件查找和性能keys_zone=my_cache:10m
这是一个用于存储缓存键和元数据的内存区域(shared memory),my_cache
是缓存区域的名称,根据需要自定义,10m是分配给该缓存区域的内存大小,这个内存区域将用于存储缓存项的键和元数据,以加快缓存查找的速度max_size=100m
这是一个可选的参数,用于指定代理缓存的最大总容量,这里代表代理缓存的最大总容量为100MB
所以以上所有配置合在一起可以写成:
location /avatar/ {
valid_referers none blocked .ushiromiya.com; # 设置合法的Referer
if ($invalid_referer) {
add_header Cache-Control "no-store"; # 添加Cache-Control头部
return 403; # 验证失败返回403错误
}
proxy_pass https://secure.gravatar.com/avatar/; # 反向代理到Gravatar
proxy_set_header Host secure.gravatar.com; # 设置代理请求的Host头部
proxy_cache_valid 200 304 301 302 1d; # 设置缓存有效期
proxy_max_temp_file_size 10m; # 设置临时文件的最大大小
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=100m; # 设置代理缓存路径和容量限制
}
注意:这个设置只适用于限制Nginx创建的临时文件的大小,并不会限制代理的文件尺寸。如果源服务器返回的文件大小超过设定的限制,Nginx仍然会接收和代理该文件,只是不会将其写入临时文件进行缓存。
如果需要完全限制代理的文件大小,例如限制源服务器返回的文件大小,就需要使用其他的方法去处理,这里就不展开了。
更可靠的方法,是通过Gravatar参数判断文件基于像素的尺寸,来选择是否拒绝代理,下面我会讲到相关方法。
Gravatar头像等级判断
Gravatar的头像也区分等级,分别分为以下等级:
- G: suitable for display on all websites with any audience type.
- PG: may contain rude gestures, provocatively dressed individuals, the lesser swear words, or mild violence.
- R: may contain such things as harsh profanity, intense violence, nudity, or hard drug use.
- X: may contain hardcore sexual imagery or extremely disturbing violence.
翻译过来就是
- G:适合在任何网站上展示给任何类型的受众。 (General Audience)
- PG:可能包含粗鲁的手势、穿着挑逗性的人、轻微的脏话或轻微的暴力。 (Parental Guidance Suggested)
- R:可能包含粗俗语言、剧烈的暴力、裸露或硬性毒品使用等内容。 (Restricted)
- X:可能包含露骨的性描写或极度令人不安的暴力。 (Explicit)
有的时候可能不希望一些不好的头像进行反向代理,影响自己域名,可以选择在代理时对等级参数进行过滤。通常Gravatar的URl为:
https://secure.gravatar.com/avatar/1e4b72c9f00126cbd1c31d4254165d2e?s=200&r=G&d=https://img.ushiromiya.com/Default-Gravatar.jpg
使用正则表达式来匹配$args
变量中的等级参数r
,如果等级不是G
,则返回403 Forbidden
错误,并设置Cache-Control
头部为no-store
,防止被缓存。
if ($args !~* "(^|&)r=G(&|$)") {
add_header Cache-Control "no-store";
return 403;
}
如果要同时满足两个等级,如G
和PG
,则写成
if ($args !~* "(^|&)r=(G|PG)(&|$)") {
add_header Cache-Control "no-store";
return 403;
}
完整的写法就可以如下:
location /avatar/ {
valid_referers none blocked .ushiromiya.com;
if ($invalid_referer) {
add_header Cache-Control "no-store";
return 403;
}
if ($args !~* "(^|&)r=(G|PG)(&|$)") {
add_header Cache-Control "no-store";
return 403;
}
proxy_pass https://secure.gravatar.com/avatar/;
proxy_set_header Host secure.gravatar.com;
proxy_cache_valid 200 304 301 302 1d;
proxy_max_temp_file_size 500k;
}
限制可代理图片尺寸(像素)
一般Gravatar有非常多种尺寸,请求哪种尺寸的图片纯粹是靠参数s=数字
或者size=数字
来实现。如果你输入很大的数字,请求到的图片也会很大。也可以同时限制可代理的最大尺寸。
在这里我就用不能大于600px
举例:
if ($arg_s !~* "^(?!0+$)[1-5]?[0-9]{1,2}$|^600$") {
add_header Cache-Control "no-store";
return 403;
}
在这段正则中,对于数字1
到600
,不能直接使用[1-600]
的形式进行匹配,这样的表达式会被解释为匹配数字1
到6
或0
。要匹配数字1
到600
,需要使用更复杂的正则表达式。
目前我用的方法是先通过使用零宽度负预测先行断言(?!0+$)
来排除只包含零的情况,然后使用[1-5]?[0-9]{1,2}
来匹配1
到599
之间的数字,最后使用^600$
匹配600
。这样可以确保$s
的值在1
到600
之间,而不匹配大于600
的数字。如果请求中的s
或size
参数不符合范围1-600
,就会触发条件判断,返回403 Forbidden
错误。
零宽度负预测先行断言(zero-width negative lookahead assertion)详细说明:
在正则表达式中,零宽度负预测先行断言是一种特殊的语法结构,用于在匹配内容时向前查看,而不会真正匹配任何字符。它可以用来在匹配之前排除某些模式。
(?!0+$)
:(?!...)
表示零宽度负预测先行断言的开始,0+$
匹配一个或多个零并以字符串结束,(?!0+$)
表示排除只包含零的情况。[1-5]?[0-9]{1,2}
:[1-5]?
表示数字1
到5
出现零次或一次,[0-9]{1,2}
表示数字0
到9
出现一次或两次。这个部分用于匹配1
到599
之间的数字。- 最后使用
|^600$
来匹配数字600
。
同理,如果要表示1-900
,则改为:
if ($arg_s !~* "^(?!0+$)[1-9]?[0-9]{1,2}$|^900$") {
add_header Cache-Control "no-store";
return 403;
}
先用零宽度负预测先行断言(?!0+$)
来排除只包含零的情况。然后使用[1-9]?[0-9]{1,2}
来匹配1
到899
之间的数字,以及^900$
来匹配数字900
。
当然也存在更好的正则写法去表述,这里也不详细展开了。
注意:Gravatar支持的最大尺寸为2048px(过去是512px),然而通常用户上传的原始图像尺寸并没这么高。如果强行请求大尺寸文件,只会导致文件无意义变大、图像出现像素化。数据来源于官方文档
重新启动Nginx服务
配置完成后,重新启动Nginx服务以应用新的配置。
sudo service nginx restart
如果支持systemctl,则使用
sudo systemctl restart nginx
替换Typecho所有评论头像地址
参考这篇:《Typecho一些奇奇怪怪但是好用的设置》
以上,完工。
这个技术可以,会经常遇到。我这反向代理到内网穿透的443端口 怎么就不行呢
这个可以有,我也用上了
首先是你得有一个海外服务器,先收藏了。
太真实了 (/ω\)
这么一说我才意识到,你网站竟然是双备案齐全
不错不错,博友圈也用到了这个。