openresty+lua做接口调用权限限制
说明:openresty可以理解为一个服务器它将nginx的核心包含了过来,并结合lua脚本语言实现一些对性能要求高的功能,该篇文章介绍了使用openresty
1.purview.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 | --调用json公共组件 cjson = require( "cjson" ) fun = require( "ttq.fun" ) -- 引用公用方法文件 conf = require( "ttq.ini" ) --引用配置文件 reds = require( "ttq.redis_pool" ) --引用redis连接池 mysqld = require( "ttq.mysql_pool" ) --引用mysql连接池 --参数校验 check_arg = fun:check_post_arg() --调用参数校验方法 arg_tables = {} --存储post的参数信息 if check_arg[ 'status' ] == 0 then --参数校验通过,获取返回的参数,并将参数拼接 arg_tables= check_arg[ 'arg_tables' ] get_info = string.format( "%s:%s:%s" ,arg_tables[ 'appid' ],arg_tables[ 'ip' ],arg_tables[ 'appkey' ]) else ngx.say(fun:resJson(- 1 ,check_arg[ 'msg' ])) return ; end -- 1 .首先通过redis查找 -- 2 .没有找到再找数据库 -- 3 .根据appid查询项目是否授权 -- 4 .项目获取权限成功,再查询ip是否被限制了 local res,err,value = reds:get_key(get_info) if not res then ngx.say(fun:resJson(- 1 ,err)) return end if value == ngx. null then --redis数据未空,根据appid查询,查询信息是否一致 local sql_appid = string.format( "select * from ttq_appid_list where appid= '%s' and appkey='%s' limit 1 " ,arg_tables[ 'appid' ],arg_tables[ 'appkey' ]) local res,msg,result = mysqld:query(sql_appid) --连接失败报错 if not res then ngx.say(fun:resJson(- 1 ,msg)) end --未查找数据报错 if table.maxn(result)== 0 then ngx.say(fun:resJson(- 1 , 'appid验证失败,被拦截' )) return end --项目权限获取成功,需要验证ip是否被允许 local sql = string.format( "select * from ttq_appid_white_list where appid='%s' and ip= '%s' limit 1 " ,arg_tables[ 'appid' ],arg_tables[ 'ip' ]) res,msg,result = mysqld:query(sql) if table.maxn(result)== 0 then ngx.say(fun:resJson(- 1 , '该项目,非法操作或没有授予权限,被拦截' )) return end --所有验证通过,最后写入redis缓存 ok, err = reds:set_key(get_info, 1 ) ngx.say(fun:resJson( 0 , '该项目鉴权成功,可以访问' )); return end -- 3 .redis找到了信息鉴权成功 ngx.say(fun:resJson( 0 , "该项目鉴权成功,可以访问!" )) |
2.ini.lua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | --配置相关方法 local _CONF = {} --返回redis配置文件 function _CONF.redis() local redis_config = {host= '127.0.0.1' ,pass= '123456' ,port= 6379 } --redis配置项 return redis_config end --返回mysql配置文件 function _CONF.mysql() local mysql_config = {host= '127.0.0.1' ,port= 3306 ,database= 'test' ,user= 'root' ,password= '123456' } --mysql的配置项 return mysql_config end return _CONF |
3.mysql_pool.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 | --连接mysql local mysql = require "resty.mysql" local mysql_pool = {} function mysql_pool:get_connect() if ngx.ctx[mysql_pool] then return true , '返回mysql连接池成功' ,ngx.ctx[mysql_pool] end local db, err_mysql = mysql: new () if not db then return false , "failed to instantiate mysql" end db:set_timeout( 1000 ) -- 1 sec local ok, err_mysql, errno, sqlstate = db:connect{ host = conf.mysql()[ 'host' ], port = conf.mysql()[ 'port' ], database = conf.mysql()[ 'database' ], user = conf.mysql()[ 'user' ], password = conf.mysql()[ 'password' ], max_packet_size = 1024 * 1024 } if not ok then --ngx.say(fun.resJson(- 1 , "mysql connect failed" )) return false , "mysql conncet failed" end --存储mysql连接池并返回 ngx.ctx[mysql_pool] = db return true , 'mysql连接成功' ,ngx.ctx[mysql_pool] end --关闭mysql连接池 function mysql_pool:close() if ngx.ctx[mysql_pool] then ngx.ctx[mysql_pool]:set_keepalive( 60000 , 1000 ) ngx.ctx[mysql_pool] = nil end end --执行sql查询 function mysql_pool:query(sql) --ngx.say(sql) local ret,msg,client = self:get_connect() --连接数据库失败,返回错误信息 if not ret then return false ,msg end --连接成功后执行sql查询,执行失败返回错误信息 local res,errmsg,errno,sqlstate = client:query(sql) --self:close() if not res then return false ,errmsg end --ngx.say(res[ 1 ][ 'appid' ]) --ngx.say(res[ 1 ][ 'ip' ]) --执行成功,返回信息 return true , "查询信息成功" ,res end return mysql_pool |
4.redis_pool.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 | local redis = require( "resty.redis" ) local redis_pool = {} --连接redis function redis_pool:get_connect() if ngx.ctx[redis_pool] then return true , "redis连接成功" ,ngx.ctx[redis_pool] end local red = redis: new () red:set_timeout( 1000 ) -- 1 sec local ok, err = red:connect(conf.redis()[ 'host' ],conf.redis()[ 'port' ]) if not ok then return false , "failed to connect redis" end --设置redis密码 local count, err = red:get_reused_times() if 0 == count then ok, err = red:auth(conf.redis()[ 'pass' ]) if not ok then return false , "redis failed to auth" end elseif err then return false , "redis failed to get reused times" end --选择redis数据库 ok, err = red:select( 0 ) if not ok then return false , "redis connect failed " end --建立redis连接池 ngx.ctx[redis_pool] = red return true , 'redis连接成功' ,ngx.ctx[redis_pool] end --关闭连接池 function redis_pool:close() if ngx.ctx[redis_pool] then ngx.ctx[redis_pool]:set_keepalive( 60000 , 300 ) ngx.ctx[redis_pool] = nil end end ---获取key的值 function redis_pool:get_key(str) local res,err,client = self:get_connect() if not res then return false ,err end local keys = client:get(str) --self:close() return true , "获取key成功" ,keys end --设置key的值 function redis_pool:set_key(str,value) local res,err,client = self:get_connect() if not res then return false ,err end client:set(str,value) --self:close() return true , "成功设置key" end return redis_pool |
5.fun.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 | local _M = {} --返回json信息公用方法 function _M:resJson(status,mes) local arr_return = {} arr_return[ 'status' ] = status arr_return[ 'msg' ] = mes return cjson.encode(arr_return) end --字符串按指定字符拆分公用方法 function _M:lua_string_split(str, split_char) local sub_str_tab = {}; while ( true ) do local pos = string.find(str, split_char); if (not pos) then local size_t = table.getn(sub_str_tab) table.insert(sub_str_tab,size_t+ 1 ,str); break ; end local sub_str = string.sub(str, 1 , pos - 1 ); local size_t = table.getn(sub_str_tab) table.insert(sub_str_tab,size_t+ 1 ,sub_str); local t = string.len(str); str = string.sub(str, pos + 1 , t); end return sub_str_tab; end --检测post过来的参数合法性 function _M:check_post_arg() local rule_count = 3 --接收POST过来的数据 ngx.req.read_body() local arg = ngx.req.get_post_args() local arg_count = 0 --存储参数个数 local arg_table = {appid,ip,appkey} local get_info --参数拼接字符串,方便redis操作 --遍历post过来的参数 for k,v in pairs(arg) do arg_count = arg_count+ 1 arg_table[k] = v end --参数赋值 appid = arg_table[ 'appid' ] ip = arg_table[ 'ip' ] appkey = arg_table[ 'appkey' ] --判断参数个数传递过来的参数要与规定的个数一致 if rule_count == arg_count then if string.len(appid) == 0 then return {status=- 1 ,msg= '参数传递错误,被拦截' } end if string.len(ip) == 0 then return {status=- 1 ,msg= '参数传递错误,被拦截' } end if string.len(appkey) == 0 then return {status=- 1 ,msg= '参数传递错误,被拦截' } end ---参数正确返回参数信息 return {status= 0 ,msg= '参数校验成功' ,arg_tables=arg_table} else return {status=- 1 ,msg= '参数传递错误,被拦截' } end end return _M |
6.配置nginx.conf文件
上面的lua文件都是放在/data/local/openresty/lualib/ttq/目录下
location /lua{
lua_code_cache on;
content_by_lua_file /data/local/openresty/lualib/ttq/purview.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 | #user nobody; worker_processes 1 ; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024 ; } http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"' ; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0 ; keepalive_timeout 65 ; #gzip on; server { listen 8083 ; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { root html; index index.html index.htm; } #默认读取body #lua_need_request_body_on; location /lua{ #default_type text/plain; default_type 'text/html' ; #content_by_lua_file /data/luacode/demo.lua; #content_by_lua 'ngx.say("fsjfssfl")' ; # content_by_lua_block{ #ngx.say( "ok" ); #local data = ngx.req.get_body_data() #ngx.say( "hello" ,data); #} lua_code_cache on; content_by_lua_file /data/wwwroot/gitwork/learning/luacode/purview.lua; } #error_page 404 / 404 .html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } } |
7.mysql数据设计
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 | /* Navicat MySQL Data Transfer Source Server : 172.168.6.6 Source Server Version : 50622 Source Host : 172.168.6.6:3306 Source Database : ttq_user_center Target Server Type : MYSQL Target Server Version : 50622 File Encoding : 65001 Date: 2016-06-21 13:43:41 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `ttq_appid_list` -- ---------------------------- DROP TABLE IF EXISTS `ttq_appid_list`; CREATE TABLE `ttq_appid_list` ( `id` int (4) unsigned NOT NULL AUTO_INCREMENT, `appid` varchar (20) NOT NULL DEFAULT 'appid相当于项目名称' , `appkey` varchar (20) NOT NULL COMMENT 'appid密码' , `create_time` int (11) NOT NULL COMMENT '生产appid时间' , PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT= '项目appid对应关系表' ; -- ---------------------------- -- Records of ttq_appid_list -- ---------------------------- -- ---------------------------- -- Table structure for `ttq_appid_white_list` -- ---------------------------- DROP TABLE IF EXISTS `ttq_appid_white_list`; CREATE TABLE `ttq_appid_white_list` ( `id` int (10) unsigned NOT NULL AUTO_INCREMENT, `appid` char (20) NOT NULL COMMENT '项目标示或名称' , `ip` varchar (15) NOT NULL COMMENT '项目允许访问对应的ip地址' , PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT= '项目权限表' ; -- ---------------------------- -- Records of ttq_appid_white_list -- ---------------------------- |
8.访问方法
<form action="http://192.168.3.128:8083/lua" method="post">
<input type="text" name="appid" value='ttq'></input>
<input type="text" name="ip" value='192.168.3.2'></input>
<input type="text" name="appkey" value='67872'></input>
<input type="submit" value="鉴权check"></input>
</form>
9.压力测试
压力测试效果非常可观
qps可以达到2225
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 分享4款.NET开源、免费、实用的商城系统
· 解决跨域问题的这6种方案,真香!
· 5. Nginx 负载均衡配置案例(附有详细截图说明++)
· Windows 提权-UAC 绕过