插件窝 干货文章 Nginx反向代理中基于时间窗口的访问控制

Nginx反向代理中基于时间窗口的访问控制

limit local ngx Redis 544    来源:    2025-04-20

Nginx反向代理中基于时间窗口的访问控制实现方案

概述

基于时间窗口的访问控制是一种限制客户端在特定时间段内访问频率的方法,常用于防止暴力破解、DDoS攻击或API滥用。在Nginx反向代理环境中,可以通过多种方式实现这种控制。

实现方案

1. 使用Nginx的limit_req模块

http {
    limit_req_zone $binary_remote_addr zone=timewindow:10m rate=10r/m;

    server {
        location / {
            limit_req zone=timewindow burst=20 nodelay;
            proxy_pass http://backend;
        }
    }
}

参数说明: - zone=timewindow:10m: 创建10MB大小的共享内存区域 - rate=10r/m: 每分钟允许10个请求 - burst=20: 允许突发20个请求 - nodelay: 立即处理突发请求而不延迟

2. 使用Nginx+Lua实现更复杂的时间窗口控制

需要安装OpenResty或ngx_http_lua_module模块:

http {
    lua_shared_dict time_window 10m;

    server {
        location / {
            access_by_lua_block {
                local limit = require "resty.limit.req"
                local lim, err = limit.new("time_window", 10, 60)  -- 每分钟10个请求

                if not lim then
                    ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
                    return ngx.exit(500)
                end

                local key = ngx.var.binary_remote_addr
                local delay, err = lim:incoming(key, true)

                if not delay then
                    if err == "rejected" then
                        return ngx.exit(503)
                    end
                    ngx.log(ngx.ERR, "failed to limit req: ", err)
                    return ngx.exit(500)
                end
            }

            proxy_pass http://backend;
        }
    }
}

3. 基于时间段的访问控制

限制特定时间段内的访问:

http {
    map $time_iso8601 $allowed {
        default 1;
        "~^2023-\d{2}-\d{2}T(09|10|11|12|13|14|15|16|17):" 0;  # 9:00-17:59禁止访问
    }

    server {
        location / {
            if ($allowed = 0) {
                return 403;
            }
            proxy_pass http://backend;
        }
    }
}

高级实现方案

4. 滑动时间窗口计数器

使用Redis+Lua实现精确的滑动时间窗口控制:

http {
    lua_shared_dict redis_connections 1m;

    server {
        location / {
            access_by_lua_block {
                local redis = require "resty.redis"
                local red = redis:new()

                red:set_timeout(1000) -- 1秒超时
                local ok, err = red:connect("redis-host", 6379)

                if not ok then
                    ngx.log(ngx.ERR, "failed to connect to Redis: ", err)
                    return ngx.exit(500)
                end

                local key = "rate_limit:" .. ngx.var.binary_remote_addr
                local limit = 100  -- 100请求
                local window = 60  -- 60秒窗口

                local current = red:get(key)
                if current == ngx.null then
                    red:setex(key, window, 1)
                elseif tonumber(current) < limit then
                    red:incr(key)
                else
                    red:close()
                    return ngx.exit(429)
                end

                red:close()
            }

            proxy_pass http://backend;
        }
    }
}

最佳实践

  1. 合理设置时间窗口大小:根据业务需求选择分钟、小时或天作为窗口单位
  2. 区分静态和动态内容:静态资源通常不需要严格限制
  3. 白名单机制:为可信IP或内部系统设置白名单
  4. 优雅降级:超过限制时返回429状态码而非直接拒绝
  5. 监控和日志:记录被限制的请求以便分析
  6. 分布式环境:在集群环境中使用共享存储(如Redis)进行计数

性能考虑

  1. 内存使用:limit_req_zone的内存区域大小需要根据预期IP数量设置
  2. 计算开销:Lua脚本会增加Nginx处理时间
  3. Redis连接:使用连接池减少Redis连接开销

以上方案可根据实际业务需求和安全要求进行组合和调整。