核心是将鉴权逻辑置于access_by_lua_block阶段,在location匹配后、请求转发前执行JWT校验或远程调用,结合前缀匹配(如^~ /api/)确保时机正确、范围精准,避免init_by_lua等错误阶段。

在 Nginx 中结合 location 和 Lua 脚本实现动态鉴权,核心是利用 ngx_lua 模块(通常通过 OpenResty 提供)在请求处理的不同阶段执行 Lua 逻辑,对请求头、参数、Token 或后端服务返回结果做实时校验。关键在于选对执行阶段、安全传递上下文、避免阻塞,并与 location 的匹配逻辑协同工作。
1. 确保环境支持 Lua(OpenResty 是首选)
Nginx 官方版不内置 Lua 支持,必须使用 OpenResty(基于 Nginx + ngx_lua + 其他增强模块)。安装后可通过 nginx -V 2>&1 | grep lua 确认是否含 --with-http_lua_module。
- 推荐从 openresty.org 下载预编译包或源码编译
- 验证配置:在
nginx.conf中加一行lua_code_cache off;(开发时方便热重载,生产务必设为on)
2. 在 location 块中嵌入 Lua 鉴权逻辑
最常用的是 access_by_lua_block(或 access_by_lua_file),它在 access_phase 执行,早于代理转发,适合做准入控制。
示例:对 /api/ 下所有路径做 JWT Token 校验
location ^~ /api/ {
access_by_lua_block {
local jwt = require "resty.jwt"
local jwt_obj = jwt:new()
<pre class="brush:php;toolbar:false;"> local auth_header = ngx.req.get_headers()["Authorization"]
if not auth_header or not string.match(auth_header, "^Bearer%s+") then
ngx.status = 401
ngx.say('{"error":"missing or invalid Authorization header"}')
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
local token = string.sub(auth_header, 8)
local res, err = jwt_obj:verify_jwt_obj(token, {
secret = "your-secret-key",
algorithm = "HS256"
})
if not res then
ngx.status = 401
ngx.say('{"error":"invalid token"}')
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
-- 可选:将解析后的用户信息存入 ngx.ctx,供后续 handler 使用
ngx.ctx.user = res.payload
}
proxy_pass http://backend;
proxy_set_header X-User-ID res.payload.user_id; -- 示例透传}
-
^~ /api/确保前缀匹配优先,避免正则干扰 - 用
access_by_lua_block内联逻辑适合简单场景;复杂逻辑建议拆到外部文件,用access_by_lua_file /path/to/auth.lua; - 注意:Lua 中不能直接访问 C 层变量(如
$arg_token),需用ngx.var.arg_token或ngx.req.get_uri_args()
3. 动态决策:调用外部鉴权服务(如 HTTP API)
当鉴权规则复杂(如查数据库、调用风控系统),可在 Lua 中发起非阻塞 HTTP 请求:
access_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
<pre class="brush:php;toolbar:false;">local user_id = ngx.ctx.user and ngx.ctx.user.user_id or ""
local res, err = httpc:request_uri("http://auth-service/check", {
method = "POST",
body = ngx.encode_json({ user_id = user_id, path = ngx.var.uri }),
headers = { ["Content-Type"] = "application/json" }
})
if not res or res.status ~= 200 then
ngx.status = 403
ngx.say('{"error":"access denied by remote policy"}')
ngx.exit(ngx.HTTP_FORBIDDEN)
end
local data = ngx.decode_json(res.body)
if not data.allowed then
ngx.exit(ngx.HTTP_FORBIDDEN)
end}
- 使用
resty.http(非标准 Lua socket)保证非阻塞,避免拖慢整个 worker - 务必设置超时:
httpc:set_timeout(3000),并处理连接失败、空响应等边界情况 - 敏感信息(如密钥、服务地址)建议通过
env或lua_shared_dict注入,而非硬编码
4. 注意事项与避坑点
动态鉴权看似灵活,但易引入性能和安全风险:
- 不要在 init_by_lua* 中做网络请求:该阶段只运行一次,用于初始化全局对象(如 Redis 连接池、JWT 密钥缓存)
- 慎用 cosocket 在 rewrite_by_lua* 阶段:该阶段尚未确定最终 location,可能造成逻辑错乱
- Token 解析后务必校验 exp、nbf、aud 等字段,仅验签名不够
- 错误响应体保持格式统一(如 JSON),便于前端识别;状态码严格对应语义(401/403/400)
- 高并发下,考虑用
lua_shared_dict缓存频繁访问的策略或黑名单,减少重复查询
不复杂但容易忽略:location 匹配顺序影响 Lua 执行范围,鉴权逻辑应放在最外层通用 location,或用 internal + content_by_lua* 做二次分发。核心是让 Lua 在正确时机介入,既守住入口,又不影响 Nginx 的高效路由能力。


















