基于openresty实现nginx+lua鉴权功能

基于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
2
3
4
mkdir -pv /home/ap/iflytek/yth-apps/system/openresty-1.19.9.1
./configure --prefix=/home/ap/iflytek/yth-apps/system/openresty-1.19.9.1
gmake
gmake install

5.3、启动测试

1
2
cd /home/ap/iflytek/yth-apps/system/openresty-1.19.9.1/nginx/sbin
./nginx

5.3、lua测试

1
2
3
4
5
6
7
8
9
10
11
12
13
#在nginx.conf,server段中增加如下配置
location /lua {
default_type text/html;
content_by_lua_block {
ngx.say("Hello Lua!")
}
}
#重载nginx
./nginx -s reload
#访问测试
curl http://127.0.0.1/lua
#如下返回表示lua安装正常
Hello Lua!

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
2
3
4
5
6
7
8
[root@ecs-403089 bin]# ./redis-server ../conf/redis.conf 
[root@ecs-403089 bin]# ./redis-cli -h 127.0.0.1 -p 6379 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> set lisir "007"
OK
127.0.0.1:6379> get lisir
"007"
127.0.0.1:6379> exit

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
    33
    server {
    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
    74
    local 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()
-------------本文结束感谢您的阅读-------------
LiGuanCheng wechat
如有问题,请与我微信交流或通过右下角“daovoice”与我联系~。
请我喝一杯咖啡~