Nginx - 整合lua 实现对POST请求的参数拦截校验(不使用Openresty)

文章目录

  • 概述
  • [步骤 1: 安装 Nginx 和 Lua 模块](#步骤 1: 安装 Nginx 和 Lua 模块)
  • [步骤 2: 创建 Lua 脚本用于参数校验](#步骤 2: 创建 Lua 脚本用于参数校验)
  • [步骤 3: 配置 Nginx 使用 Lua 脚本](#步骤 3: 配置 Nginx 使用 Lua 脚本)
    • [写法二: 状态码](#写法二: 状态码)
    • [写法三 : 返回自定义JSON](#写法三 : 返回自定义JSON)
    • 复杂的正则校验
  • [步骤 4: 测试和验证](#步骤 4: 测试和验证)
  • [ngx.HTTP_* 枚举值](#ngx.HTTP_* 枚举值)

概述

一个不使用 OpenResty 的 Nginx 集成 Lua 脚本的方案,用于对 POST 请求参数进行校验。

指令 所处处理阶段 使用范围 解释
init_by_lua / init_by_lua_file loading-config http Nginx Master进程加载配置时执行;通常用于初始化全局配置/预加载Lua模块
init_worker_by_lua / init_worker_by_lua_file starting-worker http 每个Nginx Worker进程启动时调用的计时器,如果Master进程不允许则只会在init_by_lua之后调用;通常用于定时拉取配置/数据,或者后端服务的健康检查
set_by_lua / set_by_lua_file rewrite server, server if, location, location if 设置Nginx变量,可以实现复杂的赋值逻辑;此处是阻塞的,Lua代码要做到非常快。
rewrite_by_lua / rewrite_by_lua_file rewrite http, server, location, location if rewrite阶段处理,可以实现复杂的转发/重定向逻辑。
access_by_lua / access_by_lua_file access http, server, location, location if 请求访问阶段处理,用于访问控制。
content_by_lua / content_by_lua_file content location, location if 内容处理器,接收请求处理并输出响应。
header_filter_by_lua / header_filter_by_lua_file output-header-filter http, server, location, location if 设置header和cookie。
body_filter_by_lua / body_filter_by_lua_file output-body-filter http, server, location, location if 对响应数据进行过滤,比如截断、替换。
log_by_lua / log_by_lua_file log http, server, location, location if log阶段处理,比如记录访问量/统计平均响应时间。

步骤 1: 安装 Nginx 和 Lua 模块

玩转 Nginx 之:使用 Lua 扩展 Nginx 功能


步骤 2: 创建 Lua 脚本用于参数校验

创建一个 Lua 脚本,位于 /opt/nginx/lib/lua/validate_params.lua(路径根据nginx.conf中的位置调整):

lua 复制代码
 -- validate_params.lua
local function validate_post_params()
    ngx.req.read_body()
    local args = ngx.req.get_post_args()

    if not args.username or #args.username < 3 then
        ngx.status = ngx.HTTP_BAD_REQUEST
        ngx.say("Username must be at least 3 characters long")
        return false
    end

    if not args.password or #args.password < 6 then
        ngx.status = ngx.HTTP_BAD_REQUEST
        ngx.say("Password must be at least 6 characters long")
        return false
    end

    return true
end

return {
    validate_post_params = validate_post_params
}

步骤 3: 配置 Nginx 使用 Lua 脚本

在 Nginx 配置文件中,使用 Lua 脚本来校验 POST 请求参数。修改 nginx.conf` ,增加 Lua 配置:

java 复制代码
 
user  root;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
    
    lua_package_path "/opt/nginx/lib/lua/?.lua;;";

    lua_need_request_body on;  # 启用请求体读取
    
    

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;
    error_log  logs/error.log ;
    
    
    sendfile        on;

    keepalive_timeout  65;

    
    server {
        listen       28443;
        server_name  localhost;

        access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }
 
        
   location /api {
    content_by_lua_block {
        local validator = require("validate_params")
        if validator.validate_post_params() then
            ngx.say("Validation passed")
        end
        }
      }
   }
}

写法二: 状态码

lua

lua 复制代码
-- validate_params.lua
local function validate_post_params()
    ngx.req.read_body()
    local args = ngx.req.get_post_args()

    -- 验证用户名
    if not args.username or #args.username < 3 then
        return ngx.HTTP_BAD_REQUEST, "Invalid username"
    end

    -- 验证密码
    if not args.password or #args.password < 6 then
        return ngx.HTTP_BAD_REQUEST, "Invalid password"
    end

    -- 验证通过
    return ngx.HTTP_OK, "Validation successful"
end

return {
    validate = validate_post_params
}

nginx.conf location config

java 复制代码
  location /api {
    content_by_lua_block {
        local validator = require("validate_params")
        local status, message = validator.validate()
        
        ngx.status = status
        ngx.say(status)
        ngx.say(message)
        
        if status ~= ngx.HTTP_OK then
            ngx.exit(status)
        end
        
        -- 继续后续业务逻辑
    }
  }

写法三 : 返回自定义JSON

如果不使用 cjson 库,我们可以手动构造 JSON 字符串。这种方法虽然不如使用专门的 JSON 库灵活,但对于简单的情况来说是足够的。

lua 复制代码
 -- validate_params.lua
local function validate_post_params()
    ngx.req.read_body()
    local args = ngx.req.get_post_args()

    if not args.username or #args.username < 3 then
        ngx.status = ngx.HTTP_BAD_REQUEST
        ngx.say("Username must be at least 3 characters long")
        return false
    end

    if not args.password or #args.password < 6 then
        ngx.status = ngx.HTTP_BAD_REQUEST
        -- 创建 JSON 字符串
        local json_response = string.format('{"status":"%s","message":"%s"}',ngx.status,  "Password must be at least 6 characters long")

        -- 设置响应头为 JSON
        ngx.header.content_type = "application/json"
        
        -- 输出 JSON 响应
        ngx.say(json_response)

        return false
    end

    return true
end

return {
    validate_post_params = validate_post_params
}

这段代码会返回相同格式的 JSON:

json 复制代码
{
    "status": "400",
    "message": "Password must be at least 6 characters long"
}

几点说明:

  1. 使用 Lua 的 string.format() 函数来构造 JSON 字符串。这种方法适用于简单的 JSON 结构。

  2. 注意要正确处理字符串中的特殊字符,特别是引号。在这个例子中,我们的消息没有特殊字符,但在实际应用中可能需要进行转义。

  3. 仍然设置响应头的 content-type 为 "application/json"。

  4. 使用 ngx.say() 输出构造的 JSON 字符串。

这种方法的优点是不依赖额外的库,缺点是对于复杂的 JSON 结构可能会变得难以维护。如果需要处理更复杂的 JSON 数据,或者需要频繁地进行 JSON 操作,最好还是使用专门的 JSON 库(如 cjson 或 dkjson)。


java 复制代码
 location /api {
    content_by_lua_block {
        local validator = require("validate_params")
        if validator.validate_post_params() then
            ngx.say("Validation passed")
        end
        }
      }

复杂的正则校验

java 复制代码
-- validate_params.lua

-- 用户名验证函数
local function validate_username(username)
    -- 基础检查
    if not username or username == "" then
        return false, "Username cannot be empty"
    end

    -- 长度检查
    local length = string.len(username)
    if length < 3 then
        return false, "Username is too short (minimum 3 characters)"
    end
    if length > 20 then
        return false, "Username is too long (maximum 20 characters)"
    end

    -- 检查是否包含空格
    if string.find(username, "%s") then
        return false, "Username cannot contain spaces"
    end

    -- 正则表达式检查(只允许字母、数字、下划线,必须以字母开头)
    local pattern = "^[A-Za-z][A-Za-z0-9_]*$"
    if not ngx.re.match(username, pattern) then
        return false, "Username must start with a letter and can only contain letters, numbers and underscore"
    end

    -- 检查保留字
    local reserved_words = {
        "admin", "root", "system", "user",
        "moderator", "administrator"
    }
    
    local username_lower = string.lower(username)
    for _, word in ipairs(reserved_words) do
        if username_lower == word then
            return false, "This username is reserved"
        end
    end

    return true, nil
end

-- JSON响应函数
local function send_json_response(status, message)
    ngx.status = status
    ngx.header.content_type = "application/json"
    local json_response = string.format('{"status":"%s","message":"%s"}', status, message)
    ngx.say(json_response)
end

-- 主验证函数
local function validate_post_params()
    ngx.req.read_body()
    local args = ngx.req.get_post_args()

    -- 验证用户名
    if not args.username then
        send_json_response(ngx.HTTP_BAD_REQUEST, "Username is required")
        return false
    end

    local is_valid, error_message = validate_username(args.username)
    if not is_valid then
        send_json_response(ngx.HTTP_BAD_REQUEST, error_message)
        return false
    end

    -- 验证密码
    if not args.password or #args.password < 6 then
        send_json_response(ngx.HTTP_BAD_REQUEST, "Password must be at least 6 characters long")
        return false
    end

    return true
end

-- 导出模块
return {
    validate_post_params = validate_post_params
}
java 复制代码
    location /api {
    content_by_lua_block {
        local validator = require("validate_params")
        if validator.validate_post_params() then
            ngx.say("Validation passed")
        end
        }
      }
  1. 有效的用户名和密码:
bash 复制代码
curl -X POST http://localhost:28443/api -d "username=validuser&password=validpass123"

预期结果:成功(具体响应取决于你的成功处理逻辑)

  1. 用户名太短:
bash 复制代码
curl -X POST http://localhost:28443/api -d "username=ab&password=validpass123"

预期结果:

json 复制代码
{"status":"400","message":"Username is too short (minimum 3 characters)"}
  1. 用户名太长:
bash 复制代码
curl -X POST http://localhost:28443/api -d "username=thisusernameiswaytoolong&password=validpass123"

预期结果:

json 复制代码
{"status":"400","message":"Username is too long (maximum 20 characters)"}
  1. 用户名包含空格:
bash 复制代码
curl -X POST http://localhost:28443/api -d "username=invalid user&password=validpass123"

预期结果:

json 复制代码
{"status":"400","message":"Username cannot contain spaces"}
  1. 用户名不以字母开头:
bash 复制代码
curl -X POST http://localhost:28443/api -d "username=1invaliduser&password=validpass123"

预期结果:

json 复制代码
{"status":"400","message":"Username must start with a letter and can only contain letters, numbers and underscore"}
  1. 用户名包含非法字符:
bash 复制代码
curl -X POST http://localhost:28443/api -d "username=invalid@user&password=validpass123"

预期结果:

json 复制代码
{"status":"400","message":"Username must start with a letter and can only contain letters, numbers and underscore"}
  1. 用户名是保留字:
bash 复制代码
curl -X POST http://localhost:28443/api -d "username=admin&password=validpass123"

预期结果:

json 复制代码
{"status":"400","message":"This username is reserved"}
  1. 密码太短:
bash 复制代码
curl -X POST http://localhost:28443/api -d "username=validuser&password=short"

预期结果:

json 复制代码
{"status":"400","message":"Password must be at least 6 characters long"}
  1. 缺少用户名:
bash 复制代码
curl -X POST http://localhost:28443/api -d "password=validpass123"

预期结果:

json 复制代码
{"status":"400","message":"Username is required"}
  1. 缺少密码:
bash 复制代码
curl -X POST http://localhost:28443/api -d "username=validuser"

预期结果:

json 复制代码
{"status":"400","message":"Password must be at least 6 characters long"}

步骤 4: 测试和验证

  1. 重启 Nginx

    bash 复制代码
    sudo systemctl restart nginx
  2. 发送 POST 请求

    可以使用 curl 或 Postman 测试:

bash 复制代码
[root@hcss-ecs-917b sbin]#    curl -X POST http://localhost:28443/api -d "username=test&password=123456"
Validation passed
[root@hcss-ecs-917b sbin]#
[root@hcss-ecs-917b sbin]#
[root@hcss-ecs-917b sbin]#
[root@hcss-ecs-917b sbin]#    curl -X POST http://localhost:28443/api -d "username=test&password=1"
Password must be at least 6 characters long
[root@hcss-ecs-917b sbin]#
[root@hcss-ecs-917b sbin]#
[root@hcss-ecs-917b sbin]#
[root@hcss-ecs-917b sbin]#    curl -X POST http://localhost:28443/api -d "username=t&password=1"
Username must be at least 3 characters long
[root@hcss-ecs-917b sbin]#
[root@hcss-ecs-917b sbin]#
[root@hcss-ecs-917b sbin]#
[root@hcss-ecs-917b sbin]#
[root@hcss-ecs-917b sbin]#
[root@hcss-ecs-917b sbin]#    curl -X POST http://localhost:28443/api
Username must be at least 3 characters long
[root@hcss-ecs-917b sbin]#
[root@hcss-ecs-917b sbin]#

ngx.HTTP_* 枚举值

在 OpenResty/Nginx 中,ngx.HTTP_* 常用的状态码枚举值如下:

  1. 成功类状态码:
lua 复制代码
ngx.HTTP_OK                 -- 200 成功
ngx.HTTP_CREATED            -- 201 已创建
ngx.HTTP_NO_CONTENT         -- 204 无内容
  1. 重定向类:
lua 复制代码
ngx.HTTP_MOVED_TEMPORARILY  -- 302 临时重定向
ngx.HTTP_MOVED_PERMANENTLY  -- 301 永久重定向
ngx.HTTP_SEE_OTHER          -- 303 其他位置
ngx.HTTP_NOT_MODIFIED       -- 304 未修改
  1. 客户端错误类:
lua 复制代码
ngx.HTTP_BAD_REQUEST        -- 400 错误请求
ngx.HTTP_UNAUTHORIZED       -- 401 未授权
ngx.HTTP_FORBIDDEN          -- 403 禁止访问
ngx.HTTP_NOT_FOUND          -- 404 未找到
ngx.HTTP_METHOD_NOT_ALLOWED -- 405 方法不允许
ngx.HTTP_REQUEST_TIMEOUT    -- 408 请求超时
ngx.HTTP_CONFLICT           -- 409 冲突
ngx.HTTP_GONE               -- 410 资源已不存在
  1. 服务器错误类:
lua 复制代码
ngx.HTTP_INTERNAL_SERVER_ERROR  -- 500 内部服务器错误
ngx.HTTP_NOT_IMPLEMENTED        -- 501 未实现
ngx.HTTP_BAD_GATEWAY            -- 502 网关错误
ngx.HTTP_SERVICE_UNAVAILABLE    -- 503 服务不可用
ngx.HTTP_GATEWAY_TIMEOUT        -- 504 网关超时

常用示例:

lua 复制代码
-- 成功响应
ngx.status = ngx.HTTP_OK
ngx.say("Success")

-- 参数错误
ngx.status = ngx.HTTP_BAD_REQUEST
ngx.say("Invalid parameters")

-- 未授权
ngx.status = ngx.HTTP_UNAUTHORIZED
ngx.say("Authentication required")

-- 服务器错误
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
ngx.say("Server error occurred")

推荐使用场景:

  1. 验证类:使用 400-409
  2. 身份认证:使用 401-403
  3. 资源相关:使用 404-410
  4. 服务器错误:使用 500-504

建议根据具体业务场景选择最精准的状态码。

相关推荐
chen1108____1 小时前
Nginx 实用知识大全:从模块作用到高并发承载
运维·nginx
慢慢沉2 小时前
Lua元表(Metatable)
lua
狒狒的代码王国2 小时前
Nginx简单介绍
运维·nginx
DreamJia914 小时前
Nginx 服务器 SSL 证书安装部署并配置(已实测)
服务器·nginx·ssl
慢慢沉21 小时前
Lua(字符串)
开发语言·lua
慢慢沉1 天前
Lua(数组)
开发语言·lua
慢慢沉1 天前
Lua(迭代器)
开发语言·lua
慢慢沉1 天前
Lua基本语法
开发语言·lua
土豆丶杨1 天前
linxu CentOS 配置nginx
linux·nginx·centos
Feng.Lee1 天前
接口测试Postman工具高级使用技巧
功能测试·测试工具·lua·postman·可用性测试