├── lua
├── lua_utils.lua
├── lua_dbg.lua
├── redispool.lua
├── access_limit.lua
├── ip_limit.lua
├── uid_ip_limit.lua
├── nid_limit.lua
└── uid_limit.lua
├── README.md
└── LICENSE
/lua/lua_utils.lua:
--------------------------------------------------------------------------------
1 | local _M = {}
2 |
3 | function _M.new(self)
4 | return self
5 | end
6 |
7 | function _M.get_clientip(self)
8 | local client_ip = ngx.req.get_headers()["X-Real-IP"]
9 | if client_ip == nil then
10 | client_ip = ngx.req.get_headers()["x_forwarded_for"]
11 | end
12 |
13 | if client_ip == nil then
14 | client_ip = ngx.var.remote_addr
15 | end
16 | return client_ip
17 | end
18 |
19 | return _M
20 |
--------------------------------------------------------------------------------
/lua/lua_dbg.lua:
--------------------------------------------------------------------------------
1 | ---emmylua new Debugger
2 | -- package.cpath = package.cpath .. ';C:/Users/win/.vscode/extensions/tangzx.emmylua-0.5.3/debugger/emmy/windows/x86/?.dll'
3 | package.cpath = package.cpath .. ';/Users/jerry/.vscode/extensions/tangzx.emmylua-0.5.10/debugger/emmy/mac/x64/?.dylib'
4 | local dbg = require('emmy_core')
5 | dbg.tcpConnect('localhost', 9966)
6 | ---emmylua new Debugger
7 |
8 | ngx.log(ngx.ERR, 'sdfsfsfsdfsf')
9 | -- local uri = ngx.var.uri
10 | local a = "hello world"
11 | ngx.exit(1)
12 |
13 | print(a)
14 |
--------------------------------------------------------------------------------
/lua/redispool.lua:
--------------------------------------------------------------------------------
1 | local redis = require "resty.redis"
2 |
3 | local config = {
4 | host = "172.24.32.7" or "127.0.0.1",
5 | port = 6379 or 6379,
6 | password = "e0rAZ%LP" or "adongta123",
7 | database = 12 or 0
8 | }
9 |
10 | local rds = nil
11 | local _M = {}
12 |
13 | -- 获取redis连接
14 | function _M.new(self)
15 | -- if rds ~= nil then
16 | -- return rds
17 | -- end
18 |
19 | -- 连接
20 | local red = redis:new()
21 | red:set_timeout(1000) -- one second timeout
22 | local res = red:connect(config['host'], config['port'])
23 | if not res then
24 | return nil
25 | end
26 | -- 鉴权
27 | if config['password'] ~= nil then
28 | res = red:auth(config['password'])
29 | if not res then
30 | return nil
31 | end
32 | end
33 |
34 | -- select db
35 | red:select(config['database'])
36 |
37 | red.close = close
38 | rds = red
39 | return rds
40 | end
41 | -- 归还连接到连接池 以备复用
42 | function close(self)
43 | self:set_keepalive(120000, 50)
44 | end
45 |
46 | return _M
47 |
--------------------------------------------------------------------------------
/lua/access_limit.lua:
--------------------------------------------------------------------------------
1 | ---本地emmylua(vscode)插件调试使用
2 | ---emmylua new Debugger
3 | -- package.cpath = package.cpath .. ';/Users/jerry/.vscode/extensions/tangzx.emmylua-0.5.10/debugger/emmy/mac/x64/?.dylib'
4 | -- local dbg = require('emmy_core')
5 | -- dbg.tcpConnect('localhost', 9966)
6 | ---emmylua new Debugger
7 |
8 | -- 每次脚本执行都会重复放path 避免使用
9 | -- package.path = '/usr/local/data/trt_svncodes/lua-ratelimiter/?.lua;;' .. package.path
10 |
11 | -- 白名单IP
12 | local ipTable = "103.85.172.190,"
13 | -- 工具
14 | local utils = require "lua_utils"
15 | local clientip = utils.get_clientip()
16 |
17 | -- 按uid(header设定)
18 | local uidLimit = require "uid_limit"
19 | local uidL = uidLimit:new()
20 | -- 全局ip
21 | local ipLimit = require "ip_limit"
22 | local iplimit = ipLimit:new()
23 | -- 匿名用户(header设定)
24 | local nidLimit = require "nid_limit"
25 | local nidL = nidLimit:new()
26 |
27 | if not string.find(ipTable, clientip) then -- 白单过滤
28 | -- 单IP单uid固定接口 组合限制
29 | -- 当某一uid被限流时尽量不影响该IP内的其他用户
30 |
31 | -- local ok1 = uidL.check_uid_freq()
32 | -- if ok1 and ok1 ~= 1 then
33 | -- ngx.exit(ok1)
34 | -- end
35 |
36 | -- 游客nid限流 (仿uid限流)
37 | -- local ok2 = nidL.check_nid_freq()
38 | -- if ok2 and ok2 ~= 1 then
39 | -- ngx.exit(ok2)
40 | -- end
41 |
42 | -- local headers = ngx.req.get_headers()
43 | -- local uid = headers["mxuid"]
44 | -- local nid = headers["mxnid"]
45 | -- if uid then
46 | -- ngx.log(ngx.ERR,"number ","uid: "..tonumber(uid))
47 | -- end
48 |
49 | -- if nid then
50 | -- ngx.log(ngx.ERR,"number "," nid: "..tonumber(nid))
51 | -- end
52 | -- 单IP全局限流检查
53 | -- ngx.log(ngx.ERR, package.path)
54 |
55 | local ok = iplimit.check_ip_freq()
56 | if ok and ok ~= 1 then
57 | -- 按httpcode返回
58 | -- ngx.exit(ok)
59 |
60 | -- 按内容返回
61 | ngx.header["Content-type"] = 'text/html'
62 | ngx.say('
' .. ok .. ' u r robot !?' .. '')
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/lua/ip_limit.lua:
--------------------------------------------------------------------------------
1 | ---@summary: ip 限流 - 计数器算法
2 | ---@author: jerry.D 2017-12-26
3 | ---@since: v1
4 | ---@modified: jerry.D 2022-08-27
5 | -- package.path = '/usr/local/data/trt_svncodes/lua-ratelimiter/?.lua;' .. package.path
6 |
7 | -- local ttl_timespan_s = 60 -- 封禁时长
8 | -- local ip_check_timespan_s = 60 -- 检查步长
9 | -- local access_threshold_count = 5 -- 访问频率计数阈值
10 |
11 | local ttl_timespan_s = 86400 -- 封禁时长
12 | local ip_check_timespan_s = 86400 -- 检查步长
13 | local access_threshold_count = 200 -- 访问频率计数阈值
14 |
15 |
16 |
17 | local error_status = 403
18 |
19 | local key_prefix_isdeny = "ngx:globalipfreq:isdeny:"
20 | local key_prefix_stime = "ngx:globalipfreq:stime:"
21 | local key_prefix_count = "ngx:globalipfreq:count:"
22 |
23 | local _M = {}
24 |
25 | function _M.new(self)
26 | return self
27 | end
28 |
29 | function _M.check_ip_freq(self)
30 | local utils = require "lua_utils"
31 | local client_ip = utils.get_clientip()
32 |
33 | local rds_key = client_ip
34 |
35 | -- 连接redis
36 | local redis = require "redispool"
37 | local rds = redis.new()
38 | -- 如果连接失败、redis丢失服务等 按无限制操作
39 | if not rds then
40 | return 1
41 | end
42 | -- 查询ip是否在封禁段内,若在则返回http错误码
43 | -- 封禁时间段内不再对ip时间和计数做处理
44 | local is_deny, err = rds:get(key_prefix_isdeny .. rds_key)
45 | if tonumber(is_deny) == 1 then
46 | ngx.log(ngx.ERR, "globalipfreq_deny ", "ip: " .. rds_key .. " : deny timespan : " .. ttl_timespan_s)
47 | rds:close()
48 | return error_status
49 | end
50 |
51 | local start_time, err = rds:get(key_prefix_stime .. rds_key)
52 | if start_time == nil or start_time == ngx.null then
53 | start_time = 0
54 | end
55 | local count, err = rds:get(key_prefix_count .. rds_key)
56 | -- 如果ip记录时间大于指定时间间隔或者记录时间不存在,则初始化记录时间、计数
57 | -- 如果IP访问的时间间隔小于约定的时间间隔,则ip计数正常加1,且如果ip计数大于约定阈值,则设置ip的封禁key为1,即将此IP拉黑 ,同时设置封禁IP的数据过期时间
58 | if start_time == 0 or count == nil or (os.time() - start_time) >= ip_check_timespan_s then
59 | res, err = rds:set(key_prefix_stime .. rds_key, os.time())
60 | rds:expire(key_prefix_stime .. rds_key, ttl_timespan_s)
61 | res, err = rds:set(key_prefix_count .. rds_key, 1)
62 | rds:expire(key_prefix_count .. rds_key, ttl_timespan_s)
63 | rds:close()
64 | return 1
65 | else
66 | res_cnt, err = rds:incr(key_prefix_count .. rds_key)
67 | if res_cnt >= access_threshold_count then
68 | res, err = rds:set(key_prefix_isdeny .. rds_key, 1)
69 | res, err = rds:expire(key_prefix_isdeny .. rds_key, ttl_timespan_s)
70 | ngx.log(ngx.ERR, "globalipfreq_deny ", "ip: " .. rds_key .. " : deny timespan : " .. ttl_timespan_s)
71 | rds:close()
72 | return error_status
73 | end
74 | rds:close()
75 | end
76 | end
77 |
78 | return _M
79 |
--------------------------------------------------------------------------------
/lua/uid_ip_limit.lua:
--------------------------------------------------------------------------------
1 | -----@author
2 | package.path = '/opt/verynginx/openresty/mylua/?.lua;;' .. package.path
3 |
4 | local ipuid_deny_timespan_s = 86400 -- 封禁时长
5 | local ipuid_check_timespan_s = 86400 -- 检查步长
6 | local access_threshold_count = 25 -- 指定访问频率计数最大值
7 | local error_status = 403
8 |
9 | local key_prefix_ipuidfreq_isdeny = "ngx:ipuidfreq:isdeny:"
10 | local key_prefix_ipuidfreq_count = "ngx:ipuidfreq:count:"
11 | local key_prefix_ipuidfreq_stime = "ngx:ipuidfreq:stime:"
12 |
13 | local _M = {}
14 |
15 | function _M.new(self)
16 | return self
17 | end
18 |
19 | function _M.check_ip_uid_freq(self)
20 | local utils = require "lua_utils"
21 | local client_ip = utils.get_clientip()
22 | -- 针对某些接口做限流
23 | local uri = ngx.var.uri
24 | ngx.log(ngx.ERR, "xxxxxxx", "-------" .. ngx.header.mxuid .. "----" .. ngx.header.mxnid)
25 | if not string.find(uri, "/api/doctor/") then
26 | return 1
27 | end
28 | -- 获取用户id
29 | local uid = ngx.var.arg_uid
30 | local rds_key = client_ip
31 | if uid then
32 | rds_key = rds_key .. "_" .. uid
33 | end
34 | -- 连接redis
35 | local redis = require "redispool"
36 | local rds = redis.new()
37 | -- 如果连接失败、redis丢失服务等 按无限制操作
38 | if not rds then
39 | return 1
40 | end
41 | -- 查询ip是否在封禁段内,若在则返回403错误代码
42 | -- 因封禁时间会大于ip记录时间,故此处不对ip时间key和计数key做处理
43 | local is_deny, err = rds:get(key_prefix_ipuidfreq_isdeny .. rds_key)
44 | if tonumber(is_deny) == 1 then
45 | ngx.log(ngx.ERR, "ipuidfreq_deny ", "ip: " .. rds_key .. " : deny timespan : " .. ipuid_deny_timespan_s)
46 | rds:close()
47 | return error_status
48 | end
49 |
50 | local start_time, err = rds:get(key_prefix_ipuidfreq_stime .. rds_key)
51 | local ip_count, err = rds:get(key_prefix_ipuidfreq_count .. rds_key)
52 | -- 如果ip记录时间大于指定时间间隔或者记录时间不存在,则重置记录时间、基数归1
53 | -- 如果IP访问的时间间隔小于约定的时间间隔,则ip计数正常+1,且如果ip计数大于约定阈值,则设置ip的封禁key为1,即将此IP拉黑
54 | -- 同时设置封禁IP的数据过期时间
55 | if start_time == ngx.null or (os.time() - start_time) >= ipuid_check_timespan_s then
56 | res, err = rds:set(key_prefix_ipuidfreq_stime .. rds_key, os.time())
57 | res, err = rds:set(key_prefix_ipuidfreq_count .. rds_key, 1)
58 | rds:close()
59 | return 1
60 | else
61 | if ip_count == ngx.null then
62 | rds:set(key_prefix_ipuidfreq_count .. rds_key, 1)
63 | else
64 | ip_count = ip_count + 1
65 | res, err = rds:incr(key_prefix_ipuidfreq_count .. rds_key)
66 | if ip_count >= access_threshold_count then
67 | res, err = rds:set(key_prefix_ipuidfreq_isdeny .. rds_key, 1)
68 | res, err = rds:expire(key_prefix_ipuidfreq_isdeny .. rds_key, ipuid_deny_timespan_s)
69 | ngx.log(ngx.ERR, "ipuidfreq_deny ", "ip: " .. rds_key .. " : deny timespan : " .. ipuid_deny_timespan_s)
70 | rds:close()
71 | return error_status
72 | end
73 | end
74 | rds:close()
75 | end
76 | end
77 |
78 | return _M
79 |
--------------------------------------------------------------------------------
/lua/nid_limit.lua:
--------------------------------------------------------------------------------
1 | package.path = '/usr/local/openresty-1.13.6.1/mylua/?.lua;;' .. package.path
2 |
3 | local ttl_timespan_s = 86400 -- 封禁时长
4 | local nid_check_timespan_s = 86400 -- 检查步长
5 | local access_threshold_count = 25 -- 访问频率计数阈值
6 | local error_status = 403
7 |
8 | local key_prefix_isdeny = "ngx:nidfreq:isdeny:"
9 | local key_prefix_stime = "ngx:nidfreq:stime:"
10 | local key_prefix_count = "ngx:nidfreq:count:"
11 | local key_prefix_nid = "ngx:nid:"
12 |
13 | local _M = {}
14 |
15 | function _M.new(self)
16 | return self
17 | end
18 |
19 | function _M.check_nid_freq(self)
20 | -- 只针对指定接口做限流
21 | local uri = ngx.var.uri
22 | if not string.find(uri, "/api/doctor/") then
23 | return 1
24 | end
25 |
26 | local headers = ngx.req.get_headers()
27 | local uid = tonumber(headers["mxuid"])
28 | local nid = tonumber(headers["mxnid"])
29 | if not uid then
30 | uid = 0
31 | end
32 | if not nid then
33 | nid = 0
34 | end
35 |
36 | if nid <= 0 and uid <= 0 then
37 | return error_status
38 | elseif uid > 0 then
39 | return 1
40 | end
41 |
42 | local rds_key = nid
43 | -- if not nid then
44 | -- return error_status
45 | -- end
46 | -- 连接redis
47 | local redis = require "redispool"
48 | local rds = redis.new()
49 | -- 如果连接失败、redis丢失服务等 按无限制操作
50 | if not rds then
51 | return 1
52 | end
53 | -- nid有效性验证 非法用户直接拒绝访问
54 | local niddata, err = rds:get(key_prefix_nid .. nid)
55 | if niddata == ngx.null then
56 | return error_status
57 | end
58 | -- 查询是否在封禁段内,若在则返回错误码
59 | -- 因封禁时间会大于记录时间,故此处不对时间key和计数key做处理
60 | local is_deny, err = rds:get(key_prefix_isdeny .. rds_key)
61 | if tonumber(is_deny) == 1 then
62 | ngx.log(ngx.ERR, "nidfreq_deny ", "ip: " .. rds_key .. " : deny timespan : " .. ttl_timespan_s)
63 | rds:close()
64 | return error_status
65 | end
66 |
67 | local start_time, err = rds:get(key_prefix_stime .. rds_key)
68 | local count, err = rds:get(key_prefix_count .. rds_key)
69 | -- 如果记录时间大于指定时间间隔或者记录时间不存在,则重置记录时间、计数归1
70 | -- 如果访问的时间间隔小于约定的时间间隔,则计数正常+1,且如果计数大于约定阈值,则设置封禁标识为1,即将此ID拉黑
71 | -- 同时设置封禁的数据过期时间
72 | if start_time == ngx.null or (os.time() - start_time) >= nid_check_timespan_s then
73 | res, err = rds:set(key_prefix_stime .. rds_key, os.time())
74 | rds:expire(key_prefix_stime .. rds_key, ttl_timespan_s)
75 | res, err = rds:set(key_prefix_count .. rds_key, 1)
76 | rds:expire(key_prefix_count .. rds_key, ttl_timespan_s)
77 | rds:close()
78 | return 1
79 | else
80 | if count == ngx.null then
81 | rds:set(key_prefix_count .. rds_key, 1)
82 | rds:expire(key_prefix_count .. rds_key, ttl_timespan_s)
83 | else
84 | count = count + 1
85 | res, err = rds:incr(key_prefix_count .. rds_key)
86 | ngx.log(ngx.ERR, "inc.....", "" .. nid)
87 | if count >= access_threshold_count then
88 | res, err = rds:set(key_prefix_isdeny .. rds_key, 1)
89 | res, err = rds:expire(key_prefix_isdeny .. rds_key, ttl_timespan_s)
90 | ngx.log(ngx.ERR, "nidfreq_deny ", "ip: " .. rds_key .. " : deny timespan : " .. ttl_timespan_s)
91 | rds:close()
92 | return error_status
93 | end
94 | end
95 | rds:close()
96 | end
97 | end
98 |
99 | return _M
100 |
--------------------------------------------------------------------------------
/lua/uid_limit.lua:
--------------------------------------------------------------------------------
1 | package.path = '/usr/local/openresty-1.13.6.1/mylua/?.lua;;' .. package.path
2 |
3 | local ttl_timespan_s = 86400 -- 封禁时长
4 | local uid_check_timespan_s = 86400 -- 检查步长
5 | local access_threshold_count = 25 -- 访问频率计数阈值
6 | local error_status = 403
7 |
8 | local key_prefix_isdeny = "ngx:uidfreq:isdeny:"
9 | local key_prefix_stime = "ngx:uidfreq:stime:"
10 | local key_prefix_count = "ngx:uidfreq:count:"
11 | local key_prefix_uid = "ngx:uid:"
12 |
13 | local _M = {}
14 |
15 | function _M.new(self)
16 | return self
17 | end
18 |
19 | function _M.check_uid_freq(self)
20 | -- 只针对指定接口做限流
21 | local uri = ngx.var.uri
22 | if not string.find(uri, "/api/doctor/") then
23 | return 1
24 | end
25 | -- 获取用户id
26 | -- local uid = ngx.var.arg_uid
27 | local headers = ngx.req.get_headers()
28 | local uid = tonumber(headers["mxuid"])
29 | local nid = tonumber(headers["mxnid"])
30 | if not uid then
31 | uid = 0
32 | end
33 | if not nid then
34 | nid = 0
35 | end
36 | -- ngx.log(ngx.ERR,"testlog1 ","uid: "..uid.." : nid : "..nid)
37 | if nid <= 0 and uid <= 0 then
38 | return error_status
39 | elseif nid > 0 and uid <= 0 then
40 | return 1
41 | end
42 |
43 | local rds_key = uid
44 | -- ngx.log(ngx.ERR,"testlog4 ","uid: "..uid.." : nid : "..nid)
45 | -- 连接redis
46 | local redis = require "redispool"
47 | local rds = redis.new()
48 | -- 如果连接失败、redis丢失服务等 按无限制操作
49 | if not rds then
50 | return 1
51 | end
52 | -- UID有效性验证 非法用户直接拒绝访问
53 | local uiddata, err = rds:get(key_prefix_uid .. uid)
54 | if uiddata == ngx.null then
55 | return error_status
56 | end
57 | -- 查询是否在封禁段内,若在则返回错误码
58 | -- 因封禁时间会大于记录时间,故此处不对时间key和计数key做处理
59 | local is_deny, err = rds:get(key_prefix_isdeny .. rds_key)
60 | if tonumber(is_deny) == 1 then
61 | ngx.log(ngx.ERR, "uidfreq_deny ", "ip: " .. rds_key .. " : deny timespan : " .. ttl_timespan_s)
62 | rds:close()
63 | return error_status
64 | end
65 |
66 | local start_time, err = rds:get(key_prefix_stime .. rds_key)
67 | local count, err = rds:get(key_prefix_count .. rds_key)
68 | -- 如果记录时间大于指定时间间隔或者记录时间不存在,则重置记录时间、计数归1
69 | -- 如果访问的时间间隔小于约定的时间间隔,则计数正常+1,且如果计数大于约定阈值,则设置封禁标识为1,即将此ID拉黑
70 | -- 同时设置封禁的数据过期时间
71 | if start_time == ngx.null or (os.time() - start_time) >= uid_check_timespan_s then
72 | res, err = rds:set(key_prefix_stime .. rds_key, os.time())
73 | rds:expire(key_prefix_stime .. rds_key, ttl_timespan_s)
74 | res, err = rds:set(key_prefix_count .. rds_key, 1)
75 | rds:expire(key_prefix_count .. rds_key, ttl_timespan_s)
76 | rds:close()
77 | return 1
78 | else
79 | if count == ngx.null then
80 | rds:set(key_prefix_count .. rds_key, 1)
81 | rds:expire(key_prefix_count .. rds_key, ttl_timespan_s)
82 | else
83 | count = count + 1
84 | res, err = rds:incr(key_prefix_count .. rds_key)
85 | ngx.log(ngx.ERR, "inc.....", "" .. uid)
86 | if count >= access_threshold_count then
87 | res, err = rds:set(key_prefix_isdeny .. rds_key, 1)
88 | res, err = rds:expire(key_prefix_isdeny .. rds_key, ttl_timespan_s)
89 | ngx.log(ngx.ERR, "uidfreq_deny ", "ip: " .. rds_key .. " : deny timespan : " .. ttl_timespan_s)
90 | rds:close()
91 | return error_status
92 | end
93 | end
94 | rds:close()
95 | end
96 | end
97 |
98 | return _M
99 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > 主要目的是防止爬虫、恶意访问等,爬虫本身基本防不住,所以尽量让爬虫爬取时对服务器产生压力控制在带宽和服务器性能允许的范围,并且做一个动态黑名单的规则,比如永久或一段时间内拉黑某个ip、某个用户id,禁止其再访问服务器,即保证后端服务器不受影响,也保证正常用户不受影响。
2 |
3 | > web服务器使用openresty,用它主要是出于nginx_lua模块灵活和便捷的考虑,当然使用原生nginx也可以,在安装的时候带上lua支持模块,另外 春哥的openresty里 有个限流分流的模块 [lua-resty-limit-traffic](https://github.com/openresty/lua-resty-limit-traffic) 如果想做限流分流,服务降级,灰度等等 这个模块确实也是可以用的,但是如果涉及到用户级别的缓存,比如用户ID有效性,宕机缓存失效等,所以改用redis 做缓存,没有用官方提供的模块。
4 |
5 | > 运行环境:centos + openresty + lua + redis (安装略)
6 |
7 | > openresty/nginx配置:
8 |
9 | ```
10 | 对访问本机x-service的所有请求,请求到达时执行access_limit.lua脚本,进行限流检查
11 | location /x-service {
12 | lua_code_cache on; #代码缓存,如果nginx -s reload 失效,最好执行以下 stop 再启动
13 | access_by_lua_file /usr/local/openresty-1.13.6.1/mylua/access_limit.lua; # 限流脚本入口
14 | proxy_pass http://server_ups/x-service-web;
15 | proxy_set_header X-Real-IP $remote_addr;
16 | proxy_set_header Host $http_host;
17 | }
18 | ```
19 |
20 | > ***lua_utils.lua*** 提供获取客户端ip公共方法
21 |
22 | ```
23 | local _M = {}
24 |
25 | function _M.new(self)
26 | return self
27 | end
28 | --获取客户端IP
29 | function _M.get_clientip(self)
30 | local client_ip = ngx.req.get_headers()["X-Real-IP"]
31 | if client_ip == nil then
32 | client_ip = ngx.req.get_headers()["x_forwarded_for"]
33 | end
34 |
35 | if client_ip == nil then
36 | client_ip = ngx.var.remote_addr
37 | end
38 | return client_ip
39 | end
40 |
41 | return _M
42 | ```
43 | > ***redispool.lua*** 提供redis连接池
44 |
45 | ```
46 | local redis = require "resty.redis"
47 |
48 | local config = {
49 | host = "127.0.0.1",
50 | port = 6379,
51 | password = "password"
52 | }
53 |
54 | local _M = {}
55 |
56 | --获取redis连接
57 | function _M.new(self)
58 | local red = redis:new()
59 | red:set_timeout(1000) -- one second timeout
60 | local res = red:connect(config['host'], config['port'])
61 | if not res then
62 | return nil
63 | end
64 | if config['password'] ~= nil then
65 | res = red:auth(config['password'])
66 | if not res then
67 | return nil
68 | end
69 | end
70 | red.close = close
71 | return red
72 | end
73 | --归还连接到连接池 以备复用
74 | function close(self)
75 | self:set_keepalive(120000, 50) --50个连接,每个120秒保活
76 | end
77 |
78 | return _M
79 | ```
80 | > ***ip_limit.lua*** 全局IP限制:例如单IP对本机该服务的所有访问次数达到200次,则拉黑该IP,封禁24小时,24小时候自动解封
81 | (如果你觉得代理ip一抓一大把,那这个脚本确实没啥大用,你可以考虑限制ua、固定访问header等)
82 |
83 | ```
84 | package.path = '/usr/local/openresty-1.13.6.1/mylua/?.lua;;' .. package.path
85 |
86 | local ttl_timespan_s = 86400 --封禁时长
87 | local ip_check_timespan_s = 86400 --检查步长
88 | local access_threshold_count = 200 --访问频率计数阈值
89 | local error_status = 403
90 |
91 | local key_prefix_isdeny = "globalipfreq_isdeny_"
92 | local key_prefix_stime = "globalipfreq_stime_"
93 | local key_prefix_count = "globalipfreq_count_"
94 |
95 | local _M = {}
96 |
97 | function _M.new(self)
98 | return self
99 | end
100 |
101 | function _M.check_ip_freq(self)
102 | local utils = require "lua_utils"
103 | local client_ip = utils.get_clientip()
104 |
105 | local rds_key = client_ip
106 |
107 | --获取redis连接
108 | local redis = require "redispool"
109 | local rds = redis.new()
110 | --如果连接失败、redis丢失服务等 按无限制操作
111 | if not rds then
112 | return 1
113 | end
114 | --查询ip是否在封禁段内,若在则返回http错误码
115 | --封禁时间段内不再对ip时间和计数做处理
116 | local is_deny , err = rds:get(key_prefix_isdeny..rds_key)
117 | if tonumber(is_deny) == 1 then
118 | ngx.log(ngx.ERR,"globalipfreq_deny ","ip: "..rds_key.." : deny timespan : "..ttl_timespan_s)
119 | rds:close()
120 | return error_status
121 | end
122 |
123 | local start_time , err = rds:get(key_prefix_stime..rds_key)
124 | local count , err = rds:get(key_prefix_count..rds_key)
125 | --如果ip记录时间大于指定时间间隔或者记录时间不存在,则初始化记录时间、计数
126 | --如果IP访问的时间间隔小于约定的时间间隔,则ip计数正常加1,且如果ip计数大于约定阈值,则设置ip的封禁key为1,即将此IP拉黑 ,同时设置封禁IP的数据过期时间
127 | if start_time == ngx.null or (os.time() - start_time) >= ip_check_timespan_s then
128 | res , err = rds:set(key_prefix_stime..rds_key , os.time())
129 | rds:expire(key_prefix_stime..rds_key,ttl_timespan_s)
130 | res , err = rds:set(key_prefix_count..rds_key , 1)
131 | rds:expire(key_prefix_count..rds_key,ttl_timespan_s)
132 | rds:close()
133 | return 1
134 | else
135 | if count == ngx.null then
136 | rds:set(key_prefix_count..rds_key , 1)
137 | rds:expire(key_prefix_count..rds_key,ttl_timespan_s)
138 | else
139 | count = count + 1
140 | res , err = rds:incr(key_prefix_count..rds_key)
141 | if count >= access_threshold_count then
142 | res , err = rds:set(key_prefix_isdeny..rds_key,1)
143 | res , err = rds:expire(key_prefix_isdeny..rds_key,ttl_timespan_s)
144 | ngx.log(ngx.ERR,"globalipfreq_deny ","ip: "..rds_key.." : deny timespan : "..ttl_timespan_s)
145 | rds:close()
146 | return error_status
147 | end
148 | end
149 | rds:close()
150 | end
151 | end
152 |
153 | return _M
154 | ```
155 | > ***uid_limit.lua*** 对用户ID限制: 针对业务接口比如/api/detail/接口的限制,比如一天内 同一个UID只能访问25次,超过25次立即封禁,限制服务24h,24h后自动解封
156 | (如果多个规则组合判断,能判断到确实是一个爬虫或者恶意访问就直接永久封禁,当然规则要兼顾很多地方,我这里采用一刀切的方式,后面有时间再慢慢补充,让整个规则更符合业务需要)
157 |
158 | ```
159 | package.path = '/usr/local/openresty-1.13.6.1/mylua/?.lua;;' .. package.path
160 |
161 | local ttl_timespan_s = 86400 --封禁时长
162 | local uid_check_timespan_s = 86400 --检查步长
163 | local access_threshold_count = 25 --访问频率计数阈值
164 | local error_status = 403
165 |
166 | local key_prefix_isdeny = "uidfreq_isdeny_"
167 | local key_prefix_stime = "uidfreq_stime_"
168 | local key_prefix_count = "uidfreq_count_"
169 | local key_prefix_uid = "UID_"
170 |
171 | local _M = {}
172 |
173 | function _M.new(self)
174 | return self
175 | end
176 |
177 | function _M.check_uid_freq(self)
178 | --只针对指定接口做限流,其他接口放行
179 | local uri = ngx.var.uri
180 | if not string.find(uri,"/api/detail/") then
181 | return 1
182 | end
183 | --获取用户id
184 | local uid = ngx.var.arg_uid
185 | local rds_key = uid
186 | if not uid then
187 | return error_status
188 | end
189 | --连接redis
190 | local redis = require "redispool"
191 | local rds = redis.new()
192 | --如果连接失败、redis丢失服务等 按无限制操作
193 | if not rds then
194 | return 1
195 | end
196 | --UID有效性验证 非法用户直接拒绝访问
197 | local uiddata,err = rds:get(key_prefix_uid..uid) --用户的ID需要提前预热到redis,批量操作+ 动态增加的方式
198 | if uiddata == ngx.null then
199 | return error_status
200 | end
201 | --查询是否在封禁段内,若在则返回错误码
202 | --因封禁时间会大于记录时间,故此处不对时间key和计数key做处理
203 | local is_deny , err = rds:get(key_prefix_isdeny..rds_key)
204 | if tonumber(is_deny) == 1 then
205 | ngx.log(ngx.ERR,"uidfreq_deny ","ip: "..rds_key.." : deny timespan : "..ttl_timespan_s)
206 | rds:close()
207 | return error_status
208 | end
209 |
210 | local start_time , err = rds:get(key_prefix_stime..rds_key)
211 | local count , err = rds:get(key_prefix_count..rds_key)
212 | --如果记录时间大于指定时间间隔或者记录时间不存在,则重置记录时间、计数归1
213 | --如果访问的时间间隔小于约定的时间间隔,则计数正常+1,且如果计数大于约定阈值,则设置封禁标识为1,即将此ID拉黑
214 | --同时设置封禁的数据过期时间
215 | if start_time == ngx.null or (os.time() - start_time) >= uid_check_timespan_s then
216 | res , err = rds:set(key_prefix_stime..rds_key , os.time())
217 | rds:expire(key_prefix_stime..rds_key,ttl_timespan_s)
218 | res , err = rds:set(key_prefix_count..rds_key , 1)
219 | rds:expire(key_prefix_count..rds_key,ttl_timespan_s)
220 | rds:close()
221 | return 1
222 | else
223 | if count == ngx.null then
224 | rds:set(key_prefix_count..rds_key , 1)
225 | rds:expire(key_prefix_count..rds_key,ttl_timespan_s)
226 | else
227 | count = count + 1
228 | res , err = rds:incr(key_prefix_count..rds_key)
229 | ngx.log(ngx.ERR,"inc.....",""..uid)
230 | if count >= access_threshold_count then
231 | res , err = rds:set(key_prefix_isdeny..rds_key,1)
232 | res , err = rds:expire(key_prefix_isdeny..rds_key,ttl_timespan_s)
233 | ngx.log(ngx.ERR,"uidfreq_deny ","ip: "..rds_key.." : deny timespan : "..ttl_timespan_s)
234 | rds:close()
235 | return error_status
236 | end
237 | end
238 | rds:close()
239 | end
240 | end
241 |
242 | return _M
243 | ```
244 | > ***access_limit.lua*** 入口
245 |
246 | ```
247 | package.path = '/usr/local/openresty-1.13.6.1/mylua/?.lua;;' .. package.path
248 | --白名单IP ,放入redis也可以,如果图方便直接写也可以,毕竟白单不会太多
249 | local ipTable = "11.25.17.18"
250 |
251 | local utils = require "lua_utils"
252 | local clientip = utils.get_clientip()
253 |
254 | local uidLimit = require "uid_limit"
255 | local uidL = uidLimit:new()
256 |
257 | local ipLimit = require "ip_limit"
258 | local iplimit = ipLimit:new()
259 |
260 | if not string.find(ipTable,clientip) then --白单过滤
261 | --单IP单uid固定接口 组合限制
262 | --当某一uid被限流时尽量不影响该IP内的其他用户
263 | local ok1 = uidL.check_uid_freq()
264 | if ok1 and ok1 ~= 1 then
265 | ngx.exit(ok1)
266 | end
267 | --单IP全局限流检查
268 | local ok = iplimit.check_ip_freq()
269 | if ok and ok ~= 1 then
270 | ngx.exit(ok)
271 | end
272 | end
273 | ```
274 | Enjoy!!!
275 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------