一、为什么需要请求过滤
在Web开发中,我们经常会遇到一些恶意请求或者不符合业务逻辑的请求。比如,某些爬虫会疯狂抓取数据,或者黑客尝试通过构造特殊参数进行SQL注入攻击。如果不对这些请求进行拦截,轻则影响服务器性能,重则可能导致数据泄露甚至系统崩溃。
OpenResty作为一个基于Nginx和Lua的高性能Web平台,非常适合用来做请求过滤。它可以在请求到达后端服务之前,就对URI、请求头、参数等进行快速判断,决定是否放行或者拦截。
二、OpenResty的基本请求过滤实现
OpenResty的请求过滤主要依赖Lua脚本,我们可以通过access_by_lua_block在Nginx的access阶段进行逻辑处理。下面是一个最简单的示例,拦截所有访问/admin路径的请求:
location /admin {
access_by_lua_block {
ngx.exit(403) -- 直接返回403禁止访问
}
}
这个例子虽然简单,但已经展示了OpenResty的基本能力——在Nginx层面快速拦截请求,而不需要走到后端服务。
三、基于URI的请求过滤
URI过滤是最常见的需求之一。比如,我们想要阻止某些敏感路径的访问,或者只允许特定前缀的请求通过。
location / {
access_by_lua_block {
local uri = ngx.var.uri -- 获取当前请求的URI
-- 如果URI包含"admin"或"config",则拦截
if string.find(uri, "admin") or string.find(uri, "config") then
ngx.exit(403)
end
-- 允许其他请求继续处理
}
}
这个脚本会检查URI是否包含admin或config关键字,如果包含则直接返回403。
四、基于请求头的过滤
某些恶意请求可能会携带特殊的Header,比如User-Agent是某个已知的爬虫工具,或者请求头里缺少必要的认证信息。我们可以通过检查请求头来进行过滤。
location / {
access_by_lua_block {
local headers = ngx.req.get_headers() -- 获取所有请求头
-- 检查User-Agent是否是某个爬虫
if headers["User-Agent"] == "BadBot/1.0" then
ngx.exit(403)
end
-- 检查是否缺少Authorization头
if not headers["Authorization"] then
ngx.exit(401)
end
}
}
这个脚本会检查User-Agent是否为BadBot/1.0,如果是则拦截;同时,如果请求没有Authorization头,则返回401未授权。
五、基于请求参数的过滤
请求参数(Query String或POST Body)往往是攻击者的重点目标。比如SQL注入、XSS攻击等都可能通过参数传递。我们可以对参数进行检查,拦截可疑请求。
location /api {
access_by_lua_block {
local args = ngx.req.get_uri_args() -- 获取GET参数
-- 检查是否有SQL注入特征
for key, val in pairs(args) do
if string.find(val:lower(), "select%s+.+from") then
ngx.exit(400)
end
end
-- 如果是POST请求,检查Body
if ngx.req.get_method() == "POST" then
ngx.req.read_body()
local post_args = ngx.req.get_post_args()
-- 检查是否有XSS特征
for key, val in pairs(post_args) do
if string.find(val:lower(), "<script>") then
ngx.exit(400)
end
end
end
}
}
这个脚本会检查GET和POST参数,如果发现select * from(SQL注入特征)或<script>(XSS特征),则返回400错误。
六、结合Redis实现动态规则
硬编码的过滤规则虽然简单,但不够灵活。我们可以结合Redis存储过滤规则,实现动态更新。
location / {
access_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
-- 连接Redis
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Redis连接失败: ", err)
return
end
-- 从Redis获取黑名单URI
local blocked_uris = red:smembers("blocked_uris")
for _, uri in ipairs(blocked_uris) do
if ngx.var.uri == uri then
ngx.exit(403)
end
end
-- 释放Redis连接
red:close()
}
}
这个脚本会从Redis的blocked_uris集合中读取黑名单URI,如果当前请求的URI在黑名单中,则拦截。
七、应用场景与技术优缺点
应用场景
- 防止爬虫:通过
User-Agent或请求频率拦截爬虫。 - 安全防护:拦截SQL注入、XSS等攻击请求。
- 权限控制:对未授权请求进行快速拦截。
技术优点
- 高性能:在Nginx层面拦截,不消耗后端资源。
- 灵活:Lua脚本可以编写复杂逻辑。
- 动态更新:结合Redis可实现规则热更新。
技术缺点
- 维护成本:需要编写和调试Lua脚本。
- 误杀风险:过于严格的规则可能导致合法请求被拦截。
八、注意事项
- 日志记录:拦截的请求应该记录日志,便于后续分析。
- 灰度发布:新规则建议先灰度发布,观察效果。
- 性能监控:频繁的Lua操作可能影响性能,需监控Nginx的响应时间。
九、总结
OpenResty的请求过滤功能非常强大,可以在不影响后端服务的情况下,快速拦截恶意或非法请求。无论是基于URI、请求头还是参数的过滤,都能通过Lua脚本灵活实现。结合Redis等外部存储,还能做到动态规则更新,非常适合高并发的Web应用。
评论