必须用 rewrite_by_lua_block 阶段,因其在 uri 解析完成、location 匹配开始前执行,可安全修改 ngx.var.uri 并使新路径参与后续所有匹配与转发;access_by_lua_block 已错过路由决策时机。

在 URL 解析阶段用 Lua 规范化异常路径,核心是利用 OpenResty 的 rewrite_by_lua_block 阶段——它运行于 Nginx URI 解析完成、location 匹配开始前,能安全读写 ngx.var.uri,且修改后的路径会参与后续所有匹配与转发逻辑。
为什么必须用 rewrite_by_lua_block 而不是 access_by_lua_block
access 阶段已进入具体 location,路径匹配早已完成,此时改 uri 不影响路由走向;而 rewrite 阶段正好卡在“解析完、还没匹配”这个窗口,是唯一能真正干预路径标准化并让后续配置(如 location、root/alias)基于新路径生效的时机。
- rewrite_by_lua_block 在 server-rewrite 和 find-config 之后执行,
ngx.var.uri已完成基础解码和merge_slashes处理 - 可直接赋值
ngx.var.uri = new_path,新值立即用于 location 查找 - 不支持
ngx.exit或ngx.redirect,但可配合ngx.req.set_uri实现更底层控制(需注意副作用)
规范化常见异常路径的 Lua 实践
针对双重编码、空格、点号路径、分号截断等典型问题,可在 rewrite 阶段做递归解码与结构校验:
- 对
ngx.var.uri循环调用ngx.unescape_uri(),直到结果不再变化,彻底还原多层编码(如 %252f → %2f → /) - 用
string.gsub清除路径中非法空格或控制字符(如%00、%0A),替换为400错误或静默规整 - 显式处理
./和../:用 Lua 实现路径折叠逻辑(如/a/b/../c→/a/c),避免依赖后端解析 - 若业务要求统一去除末尾斜杠(除根路径外),可用正则
string.gsub(uri, "^(.+)/$", "%1")
配合 Nginx 原生机制协同生效
单纯 Lua 处理不够稳健,需与 Nginx 内置行为对齐:
- 保持
merge_slashes on(默认),确保 // 自动合并,Lua 不必重复处理 - 禁用分号语义:
merge_slashes off;+rewrite ^(/[^;]*)\;.*$ $1 break;,再交由 Lua 处理干净路径 - 敏感路径拦截规则(如
if ($uri ~ "^/actuator") { return 403; })必须放在 Lua 之后,否则匹配的是原始未规范化路径 - 静态资源服务建议用
alias替代root,防止 Lua 规范化后因路径拼接引发越界访问
示例:轻量级路径清洗配置
在对应 location 块中加入:
rewrite_by_lua_block {
local uri = ngx.var.uri
local decoded = uri
-- 递归解码
repeat
local tmp = ngx.unescape_uri(decoded)
if tmp == decoded then break end
decoded = tmp
until false
-- 折叠 ./ ../
local clean = string.gsub(decoded, "/%./", "/")
clean = string.gsub(clean, "/%./", "/")
clean = string.gsub(clean, "/[^/]+/%.%./", "/")
while string.find(clean, "/[^/]+/%.%./") do
clean = string.gsub(clean, "/[^/]+/%.%./", "/")
end
clean = string.gsub(clean, "/%.$", "")
clean = string.gsub(clean, "^//+", "/")
clean = string.gsub(clean, "/+$", "")
if clean ~= "/" and string.sub(clean, -1) == "/" then
clean = string.sub(clean, 1, -2)
end
ngx.var.uri = clean
}
这段逻辑在 rewrite 阶段完成解码、折叠、去冗余斜杠,确保后续所有模块看到的都是干净、一致、可预测的路径形式。











