Nginx + Lua 实现每日访问次数限制与防盗链校验

🧾 Nginx + Lua 实现每日访问次数限制与防盗链校验(以 /cmap 图片接口为例)

一、应用场景

/cmap 是一个图片接口(通过 proxy_pass 转发到后端),
需要实现:

  1. 每日最多访问 1000 次
  2. 防盗链检查(仅允许特定来源 Referer)
  3. 当返回 403 / 429 时输出 JSON 格式提示
  4. 正常访问时返回图片内容(不影响 Content-Type)

二、依赖模块

要启用 access_by_lua_block,Nginx 需要编译 ngx_http_lua_module 模块。
如果是自己构建镜像,请确保编译参数中包含:

--add-module=/path/to/lua-nginx-module

并在 Nginx 配置文件中引入 Lua 共享内存:

lua_shared_dict access_limit 10m;

三、核心配置示例

location /cmap {
    proxy_pass http://10.18.55.96/cmap/layer;

    access_by_lua_block {
        -- 每日访问上限
        local limit = 1000
        local key = "cmap_access_count"
        local dict = ngx.shared.access_limit
        local day = os.date("%Y%m%d")
        local count_key = key .. ":" .. day

        -- ======================
        -- 防盗链检查
        -- ======================
        local headers = ngx.req.get_headers()
        local referer = headers["referer"]

        -- 允许的来源列表(使用正则匹配)
        local allowed_domains = {
            "^https?://10%.18%.55%.98:30001/",
            "^https?://yourdomain%.com/"
        }

        -- 是否允许空 Referer(例如浏览器直接访问)
        local allow_empty_referer = true

        local valid = false
        if referer then
            for _, pattern in ipairs(allowed_domains) do
                if referer:match(pattern) then
                    valid = true
                    break
                end
            end
        elseif allow_empty_referer then
            valid = true
        end

        if not valid then
            ngx.status = 403
            ngx.header.content_type = "application/json; charset=utf-8"
            ngx.say('{"code":403,"message":"Forbidden: Invalid Referer"}')
            return ngx.exit(403)
        end

        -- ======================
        -- 每日访问次数统计
        -- ======================
        local current = dict:get(count_key)
        if not current then
            dict:set(count_key, 0, 86400)  -- 有效期1天
            current = 0
        end

        if current >= limit then
            ngx.status = 429
            ngx.header.content_type = "application/json; charset=utf-8"
            ngx.say(string.format(
                '{"code":429,"message":"Daily access limit exceeded","count":%d,"limit":%d}',
                current, limit
            ))
            return ngx.exit(429)
        end

        local new_count = dict:incr(count_key, 1)
        if not new_count then
            dict:set(count_key, current + 1, 86400)
            new_count = current + 1
        end

        ngx.log(ngx.INFO, string.format("cmap accessed %d/%d times today", new_count, limit))
    }
}

四、逻辑说明

功能 说明
防盗链 校验 Referer 是否来自白名单域名或允许为空
访问计数 使用 ngx.shared.DICT 记录每日访问次数
时间粒度 以当天日期为 key(如 20251031
响应格式 正常请求 → 返回图片;403 / 429 → 返回 JSON
过期机制 每天自动重置计数(缓存过期 86400 秒)

五、Referer 匹配规则

写法 匹配示例 说明
"http://10.18.55.98:30001/" 仅匹配该路径开头(string.find 简单匹配
"^https?://10%.18%.55%.98:30001/" 匹配 http 或 https 前缀 推荐正则匹配
"^https?://yourdomain%.com/" 匹配自定义域名 正则匹配更安全

六、其他建议

  • 若服务器被直接访问(无 Referer),可启用 allow_empty_referer = true
  • 若需更精细限制(如按 IP 统计、按小时限流),可在 key 中加上 ngx.var.remote_addros.date("%H")
  • 若使用 Docker 运行,需要在镜像中包含 lua-nginx-moduleluajit
posted @ 2025-10-31 16:08  槑孒  阅读(4)  评论(0)    收藏  举报