基于openresty实现nginx+lua鉴权功能
1、需求背景
客户请求我方接口,会带有token等参数,获取参数后,查询参数合法性与redis是否匹配,匹配则放行,不匹配则403访问拒绝,并对接口使用限流措施;
2、部署需知
目前最新版的OpenResty内置的lua_redis模块,只可用于单机版redis,这个并不复合架构的高可用性,网上也有相关开源的案例,但均不支持集群密码连接,这里就仅使用单机版进行部署,可以考虑替代方案:TwemProxy(nutcracker)集群方案
3、OpenResty简介
OpenResty是基于Nginx和Lua的高性能Web平台,OpenResty通过汇聚各种设计精良的Nginx模块,从而将Nginx有效地变成一个强大的通用Web应用平台。根据需求,本次我们利用OpenResty的lua模块,将nginx与redis进行连接,客户方发起请求后,行请求拦截。
4、安装环境介绍
平台 | IP | OpenResty版本 | Redis版本 | |
---|---|---|---|---|
CentOS 7.8 64Bit | 192.168.2.245 | openresty-1.19.9.1 | redis-6.2.5 |
5、Openresty部署
5.1、下载源码包
5.2、编译编码包
1 | mkdir -pv /home/ap/iflytek/yth-apps/system/openresty-1.19.9.1 |
5.3、启动测试
1 | cd /home/ap/iflytek/yth-apps/system/openresty-1.19.9.1/nginx/sbin |
5.3、lua测试
1 | #在nginx.conf,server段中增加如下配置 |
6、Redis部署
提示:源码安装这里不在展示,直接使用编译好的二进制文件进行启动,这里的二进制包是7下编译的,向下兼容6代系统
6.1、解压二进制文件包
提示:根据实际路径,注意logfile、pidfile、dir、requirepass等配置
1 unzip -d redis_6.2.5_builder_auth.zip /home/ap/iflytek/yth-apps/system/
6.2、验证服务
1 | [root@ecs-403089 bin]# ./redis-server ../conf/redis.conf |
7、OpenResty配置
附主配置文件:nginx.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73#user ccb-yth ccb-yth;
worker_processes 6;
worker_cpu_affinity 000001 000010 000100 001000 010000 100000;
error_log logs/error.log error;
#pid conf/pidifle/nginx.pid;
worker_rlimit_nofile 65535;
events {
use epoll;
worker_connections 65535;
}
http {
server_tokens off;
include mime.types;
default_type application/octet-stream;
add_header X-Frame-Options SAMEORIGIN;
underscores_in_headers on;
fastcgi_intercept_errors on;
tcp_nopush on;
tcp_nodelay on;
sendfile on;
keepalive_timeout 60s;
server_names_hash_bucket_size 128;
client_header_buffer_size 32k;
large_client_header_buffers 8 128k;
client_body_buffer_size 128k;
client_max_body_size 10m;
client_body_temp_path /tmp;
proxy_connect_timeout 100s;
proxy_read_timeout 100s;
proxy_send_timeout 100s;
proxy_buffer_size 64k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 128k;
#速率限制配置
limit_req_zone $server_name zone=perserver:10m rate=100r/s;
limit_req_zone $binary_remote_addr zone=perip:10m rate=100r/s;
limit_req_status 503;
limit_req_log_level error;
#连接数限制配置
limit_conn_zone $binary_remote_addr zone=peripconn:10m;
limit_conn_zone $server_name zone=perserverconn:10m;
limit_conn_status 503;
limit_conn_log_level error;
log_format access '$remote_addr - $remote_user [$time_local] "$server_name" "$request" '
'$status $body_bytes_sent "$request_body" "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'$connection $upstream_addr '
'$upstream_response_time $request_time';
access_log logs/access.log access;
##########一体化负载组##########
upstream yth_load_balancing {
server 128.196.124.222:8850;
server 128.196.124.224:8850;
#server 192.168.92.233:8850;
keepalive 10;
}
#lua模块路径,多个之间”;”分隔,其中”;;”表示默认搜索路径,默认到/usr/servers/nginx下找
lua_package_path "/home/ap/iflytek/yth-apps/system/openresty-1.19.9.1/lualib/?.lua;;/home/ap/iflytek/yth-apps/system/openresty-1.19.9.1/nginx/conf/openResty/?.lua;;";
lua_package_cpath "/home/ap/iflytek/yth-apps/system/openresty-1.19.9.1/lualib/?.so;";
include /home/ap/iflytek/yth-apps/system/openresty-1.19.9.1/nginx/conf/openResty/*.conf;
}附虚拟主机配置文件:20000_auth.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33server {
listen 20000;
server_name 0.0.0.0;
if ($request_method = GET) {
return 405;
}
#p4接入
location /yth/api/p4 {
proxy_pass http://yth_load_balancing;
include proxyhttp.conf;
#允许1秒钟不超过100个请求,最大延迟请求数量不大于100. 如果请求不需要被延迟,添加nodelay参数,服务器会立刻返回503状态码。
limit_req zone=perip burst=100;
#针对服务进行连接限制,限制并发连接数为100
#limit_conn peripconn 100;
access_log logs/p4_access.log access;
}
#redis鉴权接入
location /yth/api {
default_type 'application/json';
lua_code_cache off;
rewrite_by_lua_file conf/openResty/lua/redis/redis-util.lua;
proxy_pass http://yth_load_balancing;
include proxyhttp.conf;
#允许1秒钟不超过100个请求,最大延迟请求数量不大于100. 如果请求不需要被延迟,添加nodelay参数,服务器会立刻返回503状态码。
limit_req zone=perip burst=100;
#针对服务进行连接限制,限制并发连接数为100
#limit_conn peripconn 100;
access_log logs/redis_access.log access;
}
}
7、lua_redis脚本定制开发
开发逻辑:通过lua获取请求中的get或post参数,将参数json格式化,判断”appid”、”token”、”callid”三个参数不可缺少,则返回400报错,随后拼接参数”appid”和”token”后,去redis进行查询,如果token在redis中为空,则返回403报错,如果都满足,进入nginx location转发请求(rewrite_by_lua_file)
- 附连接redis的lua脚本:redis-util.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74local function close_redes( red )
if not red then
return
end
-- 释放连接(连接池实现)
local pool_max_idle_time = 10000 -- 毫秒
local pool_size = 100 --连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.say("set keepalive error : ", err)
end
end
local redis = require("resty.redis")
-- 创建实例
local red = redis:new()
-- 设置超时(毫秒)
red:set_timeout(2000)
-- 建立连接
local ip = "128.196.124.222"
local port = 6379
local ok, err = red:connect(ip, port)
if not ok then
return
end
local res, err = red:auth("123456")
if not res then
ngx.say("connect to redis error : ", err)
return
end
-- 选择db 0
local ok, err = red:select(0)
if not ok then
ngx.say("failed to select db: ", err)
return
end
--Nginx服务器中使用lua获取get或post参数
local request_method = ngx.var.request_method
local args = nil
local param = nil
--获取url参数的值
if "GET" == request_method then
args = ngx.req.get_uri_args()
elseif "POST" == request_method then
ngx.req.read_body()
args = ngx.req.get_post_args()
end
cjson = require "cjson"
if ( args["appid"] == nil or args["token"] == nil or args["callid"] == nil ) then
-- 如果输入参数缺少appi or token or callid,,则返回400报错
local loginfailobj = {success = false,message = "Bad request",code = 400,timestamp = os.time()}
local loginfailjson = cjson.encode(loginfailobj)
ngx.say(loginfailjson)
else
-- ngx.print(args["appid"]..":"..args["token"])
param = ("yth:"..args["appid"]..":"..args["token"])
local key_value = red:get(param)
local key_name = red:keys(param)
local res, errs = red:exists(param)
if ( res ~= 1 ) then
-- 如果token,在redis中为空,则返回403报错
local loginfailobj = {success = false,message = "Unauthorized",code = 403,timestamp = os.time()}
local loginfailjson = cjson.encode(loginfailobj)
ngx.say(loginfailjson)
end
end
-- ngx.say(cjson.encode(key_value))
-- ngx.say(cjson.encode(res))
-- ngx.say(cjson.encode(key_name))
red:close()