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! 打完收工😃

相关推荐
菠萝炒饭pineapple-boss1 小时前
Nginx-Ingress-Controller自定义端口实现TCP/UDP转发
tcp/ip·nginx·udp·ingress
塑遂1 小时前
Nginx+Tomcat负载均衡群集
nginx·tomcat·负载均衡
luopeng2076634362 小时前
onlyOffice通过nginx自定义前缀代理
运维·nginx
yangshuo12814 小时前
解决node项目nginx静态缓存导致项目重建后页面无变化问题
linux·nginx·缓存
susnm4 小时前
Dioxus 互动性
rust·全栈
皮皮林55117 小时前
负载均衡 LVS vs Nginx 对比!还傻傻分不清?
nginx
铃木隼.1 天前
haproxy搭建nginx网站访问
运维·nginx
IT_10241 天前
Nginx教程:概念+安装+SSL安装,通过调优Nginx来提高应用性能
运维·nginx·ssl
周六放风筝1 天前
生成https免费证书并绑定到nginx
linux·nginx·https