一:IP校验功能点
1:只有被添加白名单的客户端才能调用,否则返回403
2:容错机制,如果Redis宕机等异常,IP校验失效,所有客户端请求放行。
3:动态添加白名单,增加nginx缓存,60s生效。
二:设计思路
1:在Reids中把白名单IP添加到set中存储,Nginx把其加载到内存中,每隔60s刷新一次。
2:获取客户端请求IP,在Nginx内存中读取白名单IP清单,如果不存在,即拦截请求返回403,否则方向通过。
3:如果在连接Redis和从Redis中读取数据时发生异常,则放行请求,不再进行IP校验
三:设计原理图
四:附源码
--access_by_lua_file /home/lws/soft/openResty/nginx/conf/lua/IP_check.lua;
--lua_shared_dict ip_white_list 10m;
--需要先在redis中增加白名单:sadd 'white.ip' member
--如果redis宕机,不影响业务,全部放行通过,不进行ip校验
--增加缓存机制,减少redis数据读取消耗,缓存刷新时间60s
function ip_check(redis_ip, redis_port, redis_pwd, cli_IP, redis_ip_key)
local redis = require "resty.redis"
local ip_white_list = ngx.shared.ip_white_list;
local last_update_time = ip_white_list:get("last_update_time")
if last_update_time == nil or last_update_time < (ngx.now()-60) then
--连接redis
local red = redis.new()
red:set_timeout(1000)
local ok, err = red.connect(red, redis_ip, redis_port)
if not ok then
errmsg = string.format("connect redis faild %s %s %s",redis_ip,redis_port,err)
return -1, errmsg
end
--添加redis的auth认证,如果无auth,则不需要
local count
count, err = red:get_reused_times()
if 0 == count then
ok, err = red:auth(redis_pwd)
if not ok then
errmsg = string.format("failed to auth %s",err)
return -2, errmsg
end
elseif err then
errmsg = string.format("failed to get reused times %s",err)
return -3, errmsg
end
local new_ip_white_list, err = red:smembers(redis_ip_key)
if err then
errmsg = string.format("Read Redis ip_white_list failed %s",err)
return -4, errmsg
else
ip_white_list:flush_all() --清空缓存
for index, ip_white in ipairs(new_ip_white_list) do
ip_white_list:set(ip_white,true)
end
end
ip_white_list:set("last_update_time",ngx.now())
-- 连接池大小是100个,并且设置最大的空闲时间是 10 秒
local ok, err = red:set_keepalive(10000, 100)
if not ok then
errmsg = string.format("failed to set keepalive %s",err)
return -5, errmsg
end
end
if not ip_white_list:get(cli_IP) then
errmsg = string.format("%s not in ip_white_list",cli_IP)
return 1,errmsg
end
return 0, string.format("%s in ip_white_list", cli_IP)
end
local cli_IP = ngx.var.remote_addr
local redis_ip = '127.0.0.1'
local redis_port = '6379'
local redis_pwd = 'liws'
local redis_ip_key = 'white.ip'
ret, err = ip_check(redis_ip, redis_port, redis_pwd, cli_IP, redis_ip_key)
if ret == 1 then
ngx.exit(ngx.HTTP_FORBIDDEN)
return
elseif ret < 0 then
ngx.log(ngx.ERR,err)
return