Nginx使用 Lua 脚本调用外部 API 验证 Token

在 Nginx 中使用 Lua 脚本调用外部 API 验证 Token 是一种灵活且强大的方式,特别适合 OpenResty 环境。下面我将详细介绍如何实现这一方案。

😃文章最后我会附上一个我实际项目中使用的完整实例,直接可以运行,有需要的小伙伴可以参考。

nginx 复制代码
server {
    listen 443 ssl;
    server_name your.domain.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        access_by_lua_block {
            -- 引入必要的库
            local http = require "resty.http"
            local cjson = require "cjson"

            -- 获取 Authorization 头
            local auth_header = ngx.var.http_Authorization
            if not auth_header then
                ngx.log(ngx.ERR, "No Authorization header provided")
                ngx.exit(ngx.HTTP_UNAUTHORIZED)
            end

            -- 提取 Bearer token
            local _, _, token = string.find(auth_header, "Bearer%s+(.+)")
            if not token then
                ngx.log(ngx.ERR, "Invalid Authorization header format")
                ngx.exit(ngx.HTTP_UNAUTHORIZED)
            end

            -- 创建 HTTP 客户端
            local httpc = http.new()
            httpc:set_timeout(2000)  -- 2秒超时

            -- 调用验证 API
            local res, err = httpc:request_uri("https://auth-service.example.com/verify", {
                method = "POST",
                headers = {
                    ["Content-Type"] = "application/json",
                    ["Authorization"] = "Bearer " .. token,
                },
                body = cjson.encode({
                    endpoint = ngx.var.request_uri,
                    method = ngx.var.request_method
                }),
                ssl_verify = true  -- 生产环境应启用 SSL 验证
            })

            -- 处理错误情况
            if not res then
                ngx.log(ngx.ERR, "Failed to request auth service: ", err)
                ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
            end

            -- 检查响应状态码
            if res.status ~= 200 then
                ngx.log(ngx.ERR, "Auth service returned status: ", res.status)
                ngx.exit(res.status == 401 and ngx.HTTP_UNAUTHORIZED or ngx.HTTP_FORBIDDEN)
            end

            -- 解析响应体
            local auth_data = cjson.decode(res.body)
            if not auth_data or not auth_data.valid then
                ngx.log(ngx.ERR, "Invalid token verification response")
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end

            -- 可选:将验证信息传递给后端
            ngx.req.set_header("X-User-ID", auth_data.user_id or "")
            ngx.req.set_header("X-User-Roles", auth_data.roles or "")
        }

        # 验证通过后转发请求
        proxy_pass http://backend_service;
        proxy_set_header X-Original-URI $request_uri;
        proxy_set_header X-Original-Method $request_method;
    }
}

关键组件解析

1. 必要的 Lua 库

  • resty.http: 用于发送 HTTP 请求到验证服务
  • cjson: 用于处理 JSON 数据

2. Token 提取逻辑

lua 复制代码
local _, _, token = string.find(auth_header, "Bearer%s+(.+)")

这段代码从 Authorization: Bearer <token> 头中提取 token 值。

3. 验证 API 调用

lua 复制代码
local res, err = httpc:request_uri("https://auth-service.example.com/verify", {
    method = "POST",
    headers = { ... },
    body = cjson.encode({ ... })
})

向验证服务发送 POST 请求,包含 token 和当前请求信息。

4. 响应处理

lua 复制代码
if res.status ~= 200 then
    ngx.exit(res.status == 401 and ngx.HTTP_UNAUTHORIZED or ngx.HTTP_FORBIDDEN)
end

根据验证服务的响应状态码决定是否放行请求。

高级优化建议

1. 添加缓存机制

lua 复制代码
-- 在 access_by_lua_block 开头添加
local cache = ngx.shared.token_cache
local cached = cache:get(token)
if cached then
    if cached == "valid" then
        return  -- 验证通过
    else
        ngx.exit(ngx.HTTP_UNAUTHORIZED)
    end
end

-- 在验证成功后添加
cache:set(token, "valid", 300)  -- 缓存5分钟

2. 超时和重试机制

lua 复制代码
httpc:set_timeout({
    connect = 500,
    send = 500,
    read = 1000
})

-- 添加重试逻辑
local retries = 3
while retries > 0 do
    local res, err = httpc:request_uri(...)
    if res then break end
    retries = retries - 1
    if retries == 0 then
        ngx.log(ngx.ERR, "Auth service unreachable after retries")
        ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
    end
    ngx.sleep(0.1)  -- 短暂延迟后重试
end

3. 验证服务响应示例

验证服务应返回类似以下结构的 JSON 响应:

json 复制代码
{
    "valid": true,
    "user_id": "12345",
    "roles": ["admin", "user"],
    "expires_in": 3600
}

4. 将Lua脚本提取成.lua文件

lua脚本从 nginx.config 配置文件剥离出来,更加直观也更易于维护。使用 access_by_lua_file 引用 token_filter.lua 文件:

lua 复制代码
location /api/ {
	 default_type 'application/json';  
	 charset utf-8;  
	 lua_code_cache on;  
	 access_by_lua_file /lua/token_filter.lua;
}

安全注意事项

  1. 始终使用 HTTPS:验证服务与 Nginx 之间的通信必须加密
  2. 验证 SSL 证书 :生产环境中应启用 ssl_verify
  3. 限制 Token 长度:防止过长的 token 导致缓冲区溢出
  4. 监控验证延迟:外部 API 调用会增加请求延迟
  5. 实施速率限制:防止暴力攻击验证端点

这种方案提供了极大的灵活性,可以根据你的具体认证需求进行调整,同时保持了良好的性能特征。

>>下面是一个完整的 Nginx Token 验证实例<<

可直接使用的配置实例,使用 Lua 脚本调用外部 API 验证 Token。我会分步骤解释每个部分的作用。

准备工作

  1. 确保已安装 OpenResty(带 Lua 支持的 Nginx)
  2. 准备一个验证 Token 的 API 接口(示例中使用 http://ip:port/gshr/gp3/commonApi/verifyToken

完整配置实例

打开文件 /usr/local/openresty/nginx/conf/nginx.conf

nginx 复制代码
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    # 共享内存区域用于缓存(可选)
    lua_shared_dict token_cache 10m;
    
    # 设置 Lua 包路径
    lua_package_path '/usr/local/openresty/lualib/?.lua;;';
    
    # 要转发到的实际后端服务
    upstream gshrserver {
        server 192.168.101.11:8888;
    }
    
    server {
        listen       80;
        server_name  localhost;

        # 示例受保护的应用路径
        location /api/ {
            # 使用 Lua 进行访问控制
            access_by_lua_block {
                -- 1. 获取 Authorization 头
                local token = ngx.var.http_Authorization
                
                -- 2. 检查是否有 Token
                if not token then
                    ngx.log(ngx.WARN, "No Authorization header found")
                    ngx.header["WWW-Authenticate"] = "Bearer realm=\"Access to API\""
                    ngx.exit(401)
                end
                
                -- 3. 提取 Bearer Token (去掉 "Bearer " 前缀)
                local jwt_token = string.match(token, "Bearer%s+(.+)")
                if not jwt_token then
                    ngx.log(ngx.WARN, "Invalid Authorization format")
                    ngx.exit(401)
                end
                
                -- 4. 调用验证 API
                local http = require "resty.http"
                local httpc = http.new()
                
                -- 设置请求参数
                local res, err = httpc:request_uri("http://ip:port/gshr/gp3/commonApi/verifyToken", {
                    method = "GET",
                    headers = {
                        ["Authorization"] = "Bearer " .. jwt_token,
                        ["Content-Type"] = "application/json"
                    },
                    keepalive_timeout = 60,
                    keepalive_pool = 10
                })
                
                -- 5. 处理验证结果
                if not res then
                    ngx.log(ngx.ERR, "Auth service request failed: ", err)
                    ngx.exit(500)
                end
                
                if res.status ~= 200 then
                    ngx.log(ngx.WARN, "Token validation failed: HTTP ", res.status)
                    ngx.exit(res.status)  -- 返回验证服务的状态码
                end
                
                -- 6. 验证通过,可选添加用户信息到请求头
                ngx.req.set_header("X-User-Validated", "true")
            }
            
            # 7. 验证通过后转发到实际后端
            proxy_pass http://gshrserver/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
        
        # 验证服务健康检查(可选)
        location /auth-health {
            proxy_pass http://ip:port/gshr/gp3/commonApi/health;
        }
        
        # 简单的公开访问路径
        location / {
            return 200 'Public access area. Use /api/ for protected resources.';
        }
    }
}

验证Token外部API

验证Token API示例 (Java)

Java文件 CommonAPIController.java

java 复制代码
/**
 * @Author: GP3
 * @Description: nginx验证token
 * @Date: 22:38 2025/6/16
 * @Modified By:
 */
@ResponseBody
@RequestMapping(value = "${adminPath}/commonApi/verifyToken")
public void verifyToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String Authorization = request.getHeader("Authorization");
    if (StringUtils.isBlank(Authorization)) {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write("No Authorization header found!");
        return;
    } else if (!Authorization.contains("Bearer ")){
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write("Invalid Authorization format!");
        return;
    } else {
        boolean success = JWTUtil.verify(Authorization.split(" ")[1]);
        if (!success) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("token校验失败或失效!");
            return;
        }
    }
}

关键点解释

  1. access_by_lua_block:在访问阶段执行 Lua 代码进行验证

  2. resty.http:OpenResty 提供的 HTTP 客户端库,需要单独引入

  3. 验证流程

    • 检查请求头是否有 Authorization
    • 提取 Bearer Token
    • 调用验证服务检查 Token 有效性
    • 根据结果允许/拒绝访问
  4. proxy_pass:验证通过后转发到实际后端服务

这个完整实例可以直接使用,你只需要:

  1. 安装 OpenResty
  2. 根据示例编写 verifyToken 验证Token接口
  3. 修改配置中的服务地址(如果不用默认的 localhost)

OK! 打完收工😃

相关推荐
2301_816374336 小时前
Nginx的源码编译
运维·nginx
西洼工作室6 小时前
安全认证全解析:从Basic到OAuth
安全·全栈·认证授权
铁皮饭盒19 小时前
第2课:5分钟!用 Trae AI 生成你的第一个后端服务(Bunjs + Elysia)
前端·后端·全栈
西洼工作室21 小时前
unipp+vue3+python h5+app极验验证码集成全流程解析
前端·uni-app·全栈·极验
TT_Close1 天前
在 VSCode 里点一下,8 个商店同时传完了
app·visual studio code·全栈
Arya_aa1 天前
四:部署前端和后端
nginx
Java面试题总结1 天前
一文搞定 Linux Nginx 从安装、启动到 nginx.conf 全配置详解(新手也能看懂)
linux·运维·nginx
MiNG MENS2 天前
nginx 代理 redis
运维·redis·nginx
珊瑚怪人2 天前
一个域名问题
nginx
dxdz2 天前
一文搞定 Linux Nginx 从安装、启动到 nginx.conf 全配置详解(新手也能看懂)
nginx