7.9.7 安全与限流鉴权实践
安全与限流鉴权实践#
1. 安全模型与风险点#
- OpenResty 位于网关/反向代理层,重点防护:恶意扫描、暴力破解、爬虫滥用、注入与协议异常、盗链与接口滥用。
- 安全原则:最小暴露、默认拒绝、分层防御、日志可追溯。
原理草图(网关安全流)
2. 基础安全加固#
安装与启用基础模块(OpenResty)
# CentOS/RHEL 示例
yum install -y openresty openresty-resty
openresty -V | head -n 5
systemctl enable --now openresty
请求校验与目录防护示例
文件:/usr/local/openresty/nginx/conf/nginx.conf
http {
server {
listen 80;
server_name example.com;
# 只允许 GET/POST
if ($request_method !~ ^(GET|POST)$) { return 405; }
# Host 校验
if ($host !~ ^example\.com$) { return 444; }
# 禁用隐藏文件与敏感路径
location ~* (\.git|\.svn|\.env|/etc/|/proc/|/root/) { return 403; }
# 防盗链
location /static/ {
valid_referers none blocked example.com *.example.com;
if ($invalid_referer) { return 403; }
}
location / {
proxy_pass http://backend;
}
}
}
预期效果
- 非法方法返回 405;伪造 Host 返回 444;敏感路径返回 403;盗链返回 403。
排错要点
nginx -t
tail -f /usr/local/openresty/nginx/logs/error.log
curl -I -X PUT http://example.com/ # 期望 405
3. 访问控制与黑白名单#
共享字典动态黑名单(示例)
文件:/usr/local/openresty/nginx/conf/nginx.conf
lua_shared_dict blacklist 10m;
server {
listen 80;
access_by_lua_block {
local dict = ngx.shared.blacklist
local ip = ngx.var.remote_addr
if dict:get(ip) then
ngx.log(ngx.WARN, "deny ip=", ip)
return ngx.exit(403)
end
}
location /admin/ { return 403; }
location / { proxy_pass http://backend; }
}
动态加入黑名单(命令示例)
# 临时通过 resty 命令注入
resty -e 'local d=ngx.shared.blacklist; d:set("1.2.3.4", true, 3600)'
排错
resty -e 'local d=ngx.shared.blacklist; print(d:get("1.2.3.4"))'
4. 限流与熔断策略#
limit_req 限流(按 IP)
http {
limit_req_zone $binary_remote_addr zone=perip:10m rate=5r/s;
server {
listen 80;
location /api/ {
limit_req zone=perip burst=10 nodelay;
proxy_pass http://backend;
}
}
}
Lua 实现令牌桶(简化示例)
lua_shared_dict tokens 20m;
location /api/ {
access_by_lua_block {
local dict = ngx.shared.tokens
local key = ngx.var.remote_addr
local rate, burst = 5, 10
local now = ngx.now()
local last = dict:get(key .. ":t") or now
local tokens = dict:get(key .. ":c") or burst
tokens = math.min(burst, tokens + (now - last) * rate)
if tokens < 1 then
ngx.status = 429
ngx.say("Too Many Requests")
return ngx.exit(429)
end
tokens = tokens - 1
dict:set(key .. ":t", now)
dict:set(key .. ":c", tokens)
}
proxy_pass http://backend;
}
熔断与降级(后端异常)
location /api/ {
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
proxy_read_timeout 3s;
proxy_pass http://backend;
}
location /fallback/ {
return 200 "service degraded";
}
排错
curl -i http://example.com/api/ # 触发 429 时应返回 Too Many Requests
grep "limiting requests" /usr/local/openresty/nginx/logs/error.log
5. 鉴权与认证#
JWT 鉴权(lua-resty-jwt)安装
opm get SkyLothar/lua-resty-jwt
ls /usr/local/openresty/site/lualib/resty/jwt.lua
JWT 校验示例
location /api/ {
access_by_lua_block {
local jwt = require "resty.jwt"
local auth = ngx.var.http_authorization
if not auth then return ngx.exit(401) end
local token = auth:gsub("Bearer ", "")
local jwt_obj = jwt:verify("secret_key", token)
if not jwt_obj.verified then return ngx.exit(401) end
}
proxy_pass http://backend;
}
HMAC 签名校验(Header + 时间戳)
location /sign/ {
access_by_lua_block {
local hmac = require "resty.hmac"
local secret = "s3cr3t"
local ts = ngx.var.http_x_ts
local sign = ngx.var.http_x_sign
if not ts or not sign then return ngx.exit(401) end
local hm = hmac:new(secret, hmac.ALGOS.SHA256)
hm:update(ts .. ngx.var.request_uri)
local calc = ngx.encode_base64(hm:final(), true)
if calc ~= sign then return ngx.exit(401) end
}
proxy_pass http://backend;
}
排错
curl -H "Authorization: Bearer <token>" http://example.com/api/
tail -f /usr/local/openresty/nginx/logs/error.log
6. 防重放与幂等控制#
时间戳窗口 + nonce 防重放
lua_shared_dict nonce 50m;
location /pay/ {
access_by_lua_block {
local dict = ngx.shared.nonce
local ts = tonumber(ngx.var.http_x_ts)
local nonce = ngx.var.http_x_nonce
if not ts or not nonce then return ngx.exit(401) end
if math.abs(ngx.time() - ts) > 300 then return ngx.exit(401) end
if dict:get(nonce) then return ngx.exit(409) end
dict:set(nonce, true, 300)
}
proxy_pass http://backend;
}
幂等校验(订单号)
lua_shared_dict idem 50m;
location /order/submit {
access_by_lua_block {
local dict = ngx.shared.idem
ngx.req.read_body()
local args = ngx.req.get_post_args()
local oid = args["order_id"]
if not oid then return ngx.exit(400) end
if dict:get(oid) then return ngx.exit(409) end
dict:set(oid, true, 600)
}
proxy_pass http://backend;
}
7. 安全日志与审计#
自定义日志格式
log_format security '$request_id $remote_addr $http_user_agent '
'$request $status $http_authorization '
'$upstream_status $request_time';
access_log /usr/local/openresty/nginx/logs/security.log security;
日志脱敏(简单示例)
map $http_authorization $auth_masked {
default "***";
"" "-";
}
log_format security2 '$request_id $remote_addr $auth_masked $status';
access_log /usr/local/openresty/nginx/logs/security2.log security2;
排错
tail -f /usr/local/openresty/nginx/logs/security.log
8. 实践建议#
- 策略集中化:统一网关层完成鉴权/限流,降低业务复杂度。
- 规则灰度:新规则先小流量试运行。
- 压测验证:在高并发下验证限流与鉴权性能。
- 监控闭环:限流命中率、鉴权失败率、异常响应率指标持续观察。
9. 练习与自测#
- 启用
limit_req并设置rate=2r/s,用ab或wrk观察 429 返回比例。 - 写一个 Lua 规则:拒绝 UA 包含
curl的请求,并记录到安全日志。 - 实现 JWT 校验:过期后返回 401,验证日志是否记录用户标识。
- 配置黑名单动态更新,写脚本每 10 秒向共享字典写入一个 IP。
练习命令
# 简单压测
ab -n 100 -c 20 http://example.com/api/
# 写入黑名单
resty -e 'ngx.shared.blacklist:set("8.8.8.8", true, 60)'