WEBKT

Nginx Lua 限流实战:IP 与用户频率控制教程

170 0 0 0

在 Web 应用安全中,限流是一种重要的防御手段,可以有效防止恶意攻击,例如 DDoS 攻击。Nginx 结合 Lua 模块,可以灵活地实现各种限流策略。本文将介绍如何使用 Nginx Lua 模块实现基于 IP 地址和用户标识的限流功能,并提供具体的 Lua 脚本和 Nginx 配置示例。

1. 准备工作

在开始之前,请确保已经安装了 Nginx,并且 Nginx 已经集成了 Lua 模块(例如 ngx_http_lua_module)。如果没有安装,请参考相关文档进行安装。

  • 安装 OpenResty: OpenResty 是一个基于 Nginx 的 Web 应用平台,集成了大量的 Lua 库和 Nginx 模块,可以方便地进行 Lua 开发。推荐使用 OpenResty,因为它已经包含了 ngx_http_lua_module

    # 以 CentOS 为例
    yum install yum-utils
    yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
    yum install openresty
    
  • 确认 Lua 模块已启用: 检查 Nginx 配置文件中是否已经加载了 Lua 模块。通常,OpenResty 会自动配置好。

2. 基于 IP 地址的限流

2.1 实现原理

基于 IP 地址的限流,是指限制来自同一个 IP 地址的请求频率。其核心思想是:

  1. 使用 Redis 存储每个 IP 地址的访问次数和时间戳。
  2. 每次收到请求时,检查 Redis 中该 IP 地址的访问次数。
  3. 如果访问次数超过设定的阈值,则拒绝请求。
  4. 如果访问次数未超过阈值,则更新 Redis 中该 IP 地址的访问次数和时间戳。

2.2 Lua 脚本 (rate_limit_ip.lua)

local redis = require "resty.redis"
local leaky_bucket = require "resty.limit.leakybucket"

local limit_zone_prefix = "rate_limit_ip:"
local rate_limit_interval = 1  -- 1 second
local rate_limit_reqs = 5       -- 5 requests per second

local redis_host = "127.0.0.1"
local redis_port = 6379

local function access()
  local ip = ngx.var.remote_addr
  local limit_zone = limit_zone_prefix .. ip

  local red = redis:new()
  red:set_timeout(1000) -- 1 second
  local ok, err = red:connect(redis_host, redis_port)
  if not ok then
    ngx.log(ngx.ERR, "failed to connect to redis: ", err)
    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  end

  local leaky, err = leaky_bucket.new(rate_limit_reqs, rate_limit_interval)
  if not leaky then
    ngx.log(ngx.ERR, "failed to create leaky bucket: ", err)
    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  end

  local delay, err = leaky:consume(red, limit_zone)
  if not delay then
    ngx.log(ngx.ERR, "failed to consume leaky bucket: ", err)
    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  end

  if delay == 0 then
    -- allow
    red:close()
    return
  end

  -- deny
  red:close()
  ngx.log(ngx.WARN, "rate limiting, client: ", ip)
  ngx.status = ngx.HTTP_TOO_MANY_REQUESTS
  ngx.header["Content-Type"] = "application/json; charset=utf-8"
  ngx.say(string.format("{\"code\":429,\"message\":\"Too Many Requests, retry in %.3f seconds\"}", delay))
  ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end

return access

2.3 Nginx 配置

在 Nginx 配置文件(例如 nginx.conf)中,添加以下配置:

http {
  lua_shared_dict rate_limit_data 10m; # 共享内存区域,用于存储限流数据

  server {
    listen 80;
    server_name your_domain.com;

    location / {
      access_by_lua_file /path/to/rate_limit_ip.lua; # 指定 Lua 脚本路径

      # 其他配置...
      content_by_lua_block {
        ngx.say("Hello, World!")
      }
    }
  }
}

配置说明:

  • lua_shared_dict rate_limit_data 10m;:定义一个共享内存区域,用于存储限流数据。10m 表示分配 10MB 的内存。
  • access_by_lua_file /path/to/rate_limit_ip.lua;:指定 Lua 脚本的路径。请将 /path/to/rate_limit_ip.lua 替换为实际的 Lua 脚本路径。

2.4 原理解析

  1. Redis 连接: Lua 脚本首先连接到 Redis 服务器。你需要确保 Redis 服务器已经启动,并且 Lua 脚本可以访问到 Redis 服务器。
  2. Leaky Bucket 算法: 使用 resty.limit.leakybucket 库实现漏桶算法。漏桶算法是一种常用的限流算法,它可以平滑请求流量,防止突发流量对服务器造成冲击。
  3. consume 函数: leaky:consume(red, limit_zone) 函数尝试从漏桶中消费一个请求。如果漏桶中有足够的容量,则允许请求通过,并返回 delay = 0。如果漏桶已满,则拒绝请求,并返回一个 delay 值,表示需要等待的时间。
  4. 错误处理: Lua 脚本中包含了完善的错误处理机制,可以处理 Redis 连接失败、漏桶创建失败、漏桶消费失败等异常情况。
  5. Nginx 配置: Nginx 配置文件中,使用 access_by_lua_file 指令指定 Lua 脚本的路径。当 Nginx 收到请求时,会先执行 Lua 脚本,然后再执行其他的 Nginx 指令。

3. 基于用户标识的限流

3.1 实现原理

基于用户标识的限流,是指限制同一个用户的请求频率。其核心思想与基于 IP 地址的限流类似,只不过将 IP 地址替换为用户标识。用户标识可以是用户 ID、用户名、Session ID 等。

3.2 Lua 脚本 (rate_limit_user.lua)

local redis = require "resty.redis"
local leaky_bucket = require "resty.limit.leakybucket"

local limit_zone_prefix = "rate_limit_user:"
local rate_limit_interval = 1  -- 1 second
local rate_limit_reqs = 10       -- 10 requests per second

local redis_host = "127.0.0.1"
local redis_port = 6379

local function access()
  -- 获取用户标识,例如从 Cookie 中获取
  local user_id = ngx.var.cookie_user_id
  if not user_id then
    -- 如果没有用户标识,则拒绝请求
    ngx.log(ngx.WARN, "no user id found")
    ngx.status = ngx.HTTP_UNAUTHORIZED
    ngx.header["Content-Type"] = "application/json; charset=utf-8"
    ngx.say("{\"code\":401,\"message\":\"Unauthorized\"}")
    ngx.exit(ngx.HTTP_UNAUTHORIZED)
  end

  local limit_zone = limit_zone_prefix .. user_id

  local red = redis:new()
  red:set_timeout(1000) -- 1 second
  local ok, err = red:connect(redis_host, redis_port)
  if not ok then
    ngx.log(ngx.ERR, "failed to connect to redis: ", err)
    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  end

  local leaky, err = leaky_bucket.new(rate_limit_reqs, rate_limit_interval)
  if not leaky then
    ngx.log(ngx.ERR, "failed to create leaky bucket: ", err)
    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  end

  local delay, err = leaky:consume(red, limit_zone)
  if not delay then
    ngx.log(ngx.ERR, "failed to consume leaky bucket: ", err)
    return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
  end

  if delay == 0 then
    -- allow
    red:close()
    return
  end

  -- deny
  red:close()
  ngx.log(ngx.WARN, "rate limiting, user: ", user_id)
  ngx.status = ngx.HTTP_TOO_MANY_REQUESTS
  ngx.header["Content-Type"] = "application/json; charset=utf-8"
  ngx.say(string.format("{\"code\":429,\"message\":\"Too Many Requests, retry in %.3f seconds\"}", delay))
  ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end

return access

3.3 Nginx 配置

在 Nginx 配置文件(例如 nginx.conf)中,添加以下配置:

http {
  lua_shared_dict rate_limit_data 10m; # 共享内存区域,用于存储限流数据

  server {
    listen 80;
    server_name your_domain.com;

    location / {
      access_by_lua_file /path/to/rate_limit_user.lua; # 指定 Lua 脚本路径

      # 其他配置...
      content_by_lua_block {
        ngx.say("Hello, World!")
      }
    }
  }
}

配置说明:

  • 与基于 IP 地址的限流配置类似,只需要将 Lua 脚本路径替换为 rate_limit_user.lua 的路径即可。
  • 在 Lua 脚本中,需要根据实际情况修改获取用户标识的方式。例如,从 Cookie 中获取用户 ID,或者从 Header 中获取 Token 等。

3.4 注意事项

  • 用户标识的唯一性: 确保用户标识的唯一性,否则可能导致限流策略失效。
  • 用户标识的安全性: 保护用户标识的安全性,防止用户标识被篡改或伪造。
  • 缓存: 考虑使用缓存来提高性能。例如,可以使用 Nginx 的缓存机制来缓存用户标识。

4. 总结

本文介绍了如何使用 Nginx Lua 模块实现基于 IP 地址和用户标识的限流功能,并提供了具体的 Lua 脚本和 Nginx 配置示例。通过本文的学习,你可以快速上手并应用到实际项目中,有效防止恶意攻击,保障 Web 应用的安全稳定运行。

核心要点:

  • Nginx 结合 Lua 模块,可以灵活地实现各种限流策略。
  • 基于 IP 地址的限流,可以限制来自同一个 IP 地址的请求频率。
  • 基于用户标识的限流,可以限制同一个用户的请求频率。
  • 漏桶算法是一种常用的限流算法,它可以平滑请求流量,防止突发流量对服务器造成冲击。
  • 需要根据实际情况选择合适的限流策略,并根据业务需求调整限流参数。

希望本文对你有所帮助!

Nginx专家 NginxLua限流

评论点评