基于openresty防护源站

内容目录

一、背景概述二、防护方式三、基于openresty实现源站防护四、思考与总结

一、背景概述

网站是一个企业或者团队的重要资产,源码也是开发人员和团队辛苦劳作的成果,对于后端服务,用户几乎触摸不到源码,但是对于前端页面,用户访问网站时会有最直接的接触,稍微有技术经验的用户可以通过浏览器的机制来查看页面源码,也有一些黑灰团队专门用爬虫扒别人的网站,然后包装成自己的产品,实现商业效益。

如果你的网站做得好,不管是web站还是h5,一定会有人研究你的网站,爬站点源码,可谓是防不胜防,很多小网站根本没有做任何防护,有些企业会做一些简单不至于苛刻的防护,当然有心之人想扒也能扒下来,主要取决于防护收益与投入开支的平衡。

目前市面上很多网站还是使用传统的nginx来实现,那么本篇文章我们就结合openresty来实现源站防护做一下简单介绍。

二、防护方式

想要防护网站源码,可以使用在用户访问主站域名的时候,获取静态资源之前加一层防护,比如爬虫请求头限制、ip访问区域限制等,当然限制的方式有很多,也可以借助其他工具来实现,这里重点介绍下爬虫和ip限制的方式。

1.防爬虫

本质上爬虫也是一种请求,只不过是由脚本或者机器人自动发起的,那么请求就会带referer,就会带ua,一般爬虫都会带一些特殊的标识,对于referer,我们可以限制我们认可的域名,对于ua,我们可以不定期的更新爬虫ua库即可。

也可以借助其他的一些web防护工具来实现爬虫防护。比如Cloudflare机器人管理、AppTrana以及其他WAF防护能力等。

2.ip限制

可以基于openresty+LuaJIT能力,在用户防护主站的时候,拦截请求,通过Lua脚本进行ip识别和验证,对于合法请求则放过,去加载讲台资源,对于非法ip则直接返回403或者其他受限页面。

三、基于openresty实现源站防护

openresty在nginx的基础上提供了扩展能力以及很多强大的模块,此处基于openresty实现,具体openresty安装方式,不做赘述。

1.爬虫防护

设置Referer验证,只允许来自xxx.com(网站自己域名)和其子域的请求访问图片、CSS和JavaScript以及html文件,其他来源的请求会返回403Forbidden。

代码语言:javascript
复制
location ~* \.(jpg|jpeg|png|gif|ico|css|js|html)$ {
  valid_referers none blocked *.xxx.com;
  if ($invalid_referer) {
    return   403;
  }
}

设置User-Agent检查,检查User-Agent来过滤掉一些已知的爬虫User-Agent。爬虫的User-Agent容易伪造,需要定期跟新补充已知爬虫User-Agent。

代码语言:javascript
复制
#forbidden Scrapy
if ($http_user_agent ~* (Scrapy|Curl|HttpClient)) {
  return 403;
}

#forbidden UA
if (http_user_agent ~ "Bytespider|FeedDemon|JikeSpider|Indy Library|Alexa Toolbar|AskTbFXTV|AhrefsBot|CrawlDaddy|CoolpadWebkit|Java|Feedly|UniversalFeedParser|ApacheBench|Microsoft URL Control|Swiftbot|ZmEu|oBot|jaunty|Python-urllib|python-requests|lightDeckReports Bot|YYSpider|DigExt|YisouSpider|HttpClient|MJ12bot|heritrix|EasouSpider|Ezooms|^" ) {
return 403;
}

2.ip检查防护

访问主站域名时,通过lua脚本来检查ip是否可以访问和加载资源。

代码语言:javascript
复制
server {
listen 80;
server_name xxx.xxx.xxx;

location = / {
# 处理根路径请求的配置
content_by_lua_file /usr/local/openresty/nginx/conf/lua/ip_limit.lua;
}

location @success_internal {
    internal;
    root /xxx/xxx/
    index index.html;
    try_files $uri $uri/ =404;
}
location @fail_internal {
    internal;
    root /aaa/aaa/
    index index.html;
    try_files $uri $uri/ =404;
}

}

对应的lua脚本如下:

代码语言:javascript
复制
local cjson=require 'cjson'
local geo=require 'resty.maxminddb'
local redis_host = "host"
local redis_port = port

local redis_connection_timeout = 100
local redis_key = "xxx"
local redis_black_key = "xxx"
local cache_ttl = 1
local ip = ngx.var.remote_addr
local ip_whitelist = ngx.shared.ip_whitelist
local ip_blacklist = ngx.shared.ip_blacklist
local last_update_time = ip_whitelist:get("last_update_time");
local inWhiteList = false

if last_update_time == nil or last_update_time < ( ngx.now() - cache_ttl ) then
local redis = require "resty.redis";
local red = redis:new();
red:set_timeout(redis_connect_timeout);
local ok, err = red:connect(redis_host, redis_port);
if not ok then
ngx.log(ngx.INFO, "Redis connection error while retrieving ip_whitelist: " .. err);
else
ok, err = red:auth('password')
if not ok then
ngx.log(ngx.ERR,"redis auth error:"..err);
else
local new_ip_whitelist, err = red:smembers(redis_key);
if err then
ngx.log(ngx.ERR, "Redis read error while retrieving ip_whitelist: " .. err);
else
ip_whitelist:flush_all();
for index, banned_ip in ipairs(new_ip_whitelist) do
ip_whitelist:set(banned_ip, true);
end
ip_whitelist:set("last_update_time", ngx.now());
end
local new_ip_blacklist, err = red:smembers(redis_black_key);
if err then
ngx.log(ngx.ERR, "Redis read error while retrieving ip_blacklist: " .. err);
else
ip_blacklist:flush_all();
for index, black_ip in ipairs(new_ip_blacklist) do
ip_blacklist:set(black_ip, true);
end
ip_blacklist:set("last_update_time", ngx.now());
end
end
end
end
--判断ip是否在白名单中,不在则直接拒绝处理
if (ip_whitelist:get("&#34;"..ip.."&#34;")) then
-- 如果在白名单,则直接返回通过
ngx.log(ngx.ERR,"ip:"..ip..",is in whitelist,return success")
ngx.exec("@success_internal")
elseif (ip_blacklist:get("&#34;"..ip.."&#34;")) then
ngx.log(ngx.ERR,"ip:"..ip..",is in blacklist,return fail")
ngx.exec("@fail_internal")
else
-- 省略
if not geo.initted() then
geo.init("xxx/GeoLite2-Country_20240123/GeoLite2-Country.mmdb")
end
local res,err=geo.lookup(ip)
if not res then
-- ngx.say("获取客户端ip失败,或当前请求的ip不是公网ip")
ngx.log(ngx.ERR,' failed to lookup by ip , reason :',err)
else
if (如果ip是目标先定区域) then
ngx.exec("@success_internal")
else
ngx.exec("@fail_internal")
end
end

上述脚本做了以下几件事情:

  • 如果ip在redis维护的白名单中,则放过,正确加载静态文件。
  • 否则,如果ip在redis维护的黑名单中,则返回403进制访问
  • 如果ip不在黑白名单中,则使用geoip2检查访问ip所属区域,如果是目标区域则放行,否则禁止访问。

四、思考与总结

以上是基于openresty实现的网站源站防护机制,主要是从爬虫和ip区域限制两个维度实现,当然市面上也有很多其他的防护机制和技术、以及免费和商业化的工具可以使用,如下列举一些比较常用的方式和工具:

  • CAPTCHA验证:通过要求用户进行人机验证,如输入验证码或解决图片中的问题,来识别是否为人类操作而不是自动化爬虫。
  • 用户行为分析:监控网站访问者的行为模式,识别不正常的高频率、高速度或者无规律的请求,以及非人类的操作模式。
  • 动态内容生成:通过动态生成的内容或者验证码来防止简单的爬虫程序识别和获取网站内容。
  • 服务提供商的防护服务:一些云服务提供商或者CDN(内容分发网络)提供防护服务,比如cloudflare的机器人管理。
  • 反爬虫算法和技术:包括但不限于基于机器学习的算法来检测和识别爬虫和非法请求行为,例如使用模式识别技术来识别爬虫的访问模式。

以及其他还有跟多的工具和方式,这些工具和技术通常会结合使用,以提高网站的防护能力,确保合法用户可以正常访问和使用网站,同时保护网站的内容和资源不被滥用和损害。