背景
本文介绍Nginx中Lua模块使用方式,并结合案例进行介绍。案例介绍通过lua脚本提取HTTP请求头中的token字段,经过JWT校验并提取id和name信息,设置到http请求头中发向后段服务器。
默认情况下,Nginx自身不携带lua模块,即不支持通过lua脚本进行功能扩展。需要在编译Nginx时手动引入lua模块,或者直接使用openresty,本文结合后者进行介绍。
1.openresty安装流程
1.1 安装包下载
wget https://openresty.org/download/openresty-1.25.3.1.tar.gz
tar -zxvf openresty-1.25.3.1.tar.gz
cd openresty-1.25.3.1/
1.2 配置
./configure --prefix=/usr/local/ewen/nginx --with-luajit --without-http_redis2_module --with-http_ssl_module --with-http_sub_module --with-http_stub_status_module --with-http_dav_module --with-http_mp4_module --with-http_v2_module
由于案例不涉及使用Redis,因此可以在configure阶段通过–without-http_redis2_module以最小化安装。
执行结果如下:
platform: linux (linux)
...
Type the following commands to build and install:
gmake
gmake install
1.3 编译和安装
gmake
gmake install
1.4 案例测试
1.4.1 测试nginx正常工作
修改配置,添加一个location:
location /test {
return 200 "test success";
}
运行nginx后:
[root@124 sbin]# curl http://localhost:8765/test
test success
1.4.2 测试nginx的lua插件正常工作
修改配置,添加一个location:
location /lua{
content_by_lua 'ngx.say("<h1>HELLO,Lua</h1>")';
}
运行nginx后:
[root@124 sbin]# curl http://localhost:8765/lua
<h1>HELLO,Lua</h1>
2.lua介绍
参考: Lua使用方式介绍
3.http处理流程与lua模块
如Nginx系列-12 HTTP消息处理流程文中介绍,Nginx处理HTTP消息的流程可以分为如下11个阶段:
Lua模块可以参与rewrite、access、content、log阶段,流程和对应指令如下所示:
当使用lua生成HTTP响应内容时,在content阶段处理对应content_by_lua指令,而进行请求校验时在access阶段处理,对应access_by_lua_block或者access_by_lua_file指令。
4.案例介绍
案例介绍通过lua实现校验请求是否合法:请求头中带有合法的token, 则通过校验,否则返回401响应。
案例使用jwt解析token,因此需要引入jwt依赖(lua-resty-jwt库),包括hmac.lua、evp.lua、jwt.lua、jwt-validators.lua; hmac.lua来源于lua-resty-jwt\vendor\resty,evp.lua、jwt.lua、jwt-validators.lua来源于lua-resty-jwt\lib\resty.
可以通过access_by_lua_block块的形式或者access_by_lua_file文件形式引入lua文件,本文选择后者。
4.1 lua文件介绍
ewen.lua文件内容如下:
local white_url_list = {'/open'};
-- 修改为自己的jwt密钥
public_key = "......";
function startsWith(str, prefix)
return string.sub(str, 1, string.len(prefix)) == prefix;
end
local function exit_with_code_msg(code, msg)
ngx.status = code;
ngx.say(msg);
ngx.exit(code);
end
local function get_jwt_claims(token, public_key)
local jwt = require("resty.jwt");
local jwt_obj, err = jwt:verify(public_key, token);
if not jwt_obj then
ngx.say("Failed to verify JWT: ", err);
return nil;
end
return jwt_obj["payload"];
end
local function check_token_and_fill_head()
local token = ngx.req.get_headers()["token"];
if not token then
exit_with_code_msg(ngx.HTTP_UNAUTHORIZED, "401 Unauthorized: Token not found or invalid");
end
local payload = get_jwt_claims(token, public_key)
if not payload then
exit_with_code_msg(ngx.HTTP_UNAUTHORIZED, "401 Unauthorized: Token not found or invalid");
end
ngx.req.set_header("id", tostring(payload["id"]));
ngx.req.set_header("name", tostring(payload["name"]));
ngx.req.set_header("role", tostring(payload["role"]));
end
local function need_check()
for _, path in ipairs(white_url_list) do
if startsWith(ngx.var.request_uri, path) then
return true;
end
end
return false;
end
if not need_check() then
ngx.log(ngx.INFO, "JWT: " .. tostring(ngx.var.request_uri) .. " check.");
check_token_and_fill_head()
else
ngx.log(ngx.INFO, "JWT: " .. tostring(ngx.var.request_uri) .. " not need to check.");
end
其中: ngx.status
属性表示HTTP响应状态码;ngx.say
方法用于设置响应体内容;ngx.exit(code)
用于设置状态码并直接返回给客户端(结束请求);ngx.req.set_header
方法用于设置请求头;require(“resty.jwt”)表示引入jwt库,之后jwt:verify
方法用于对token进行JWT校验和Claim信息提取。
4.2 配置lua文件
在nginx.conf文件的http块或者server块中添加:
access_by_lua_file ./lua/jwt.lua;
4.3 案例测试
分别使用带token和不带token进行测试:
[root@124 conf]# curl -X GET http://localhost:8765/lua
401 Unauthorized: Token not found or invalid
[root@124 conf]# curl -X GET -H "token:eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE3MTk5NzkyMjEsInV1aWQiOiI1Y2M4OGYwZC1hNGM5LTQyNTItODdkMC1hNzZkNmQxNzEzZTEiLCJncmFudFR5cGUiOiJzYWMiLCJidXNpbmVzcyI6ImVjaGF0OnVlOjE5NjAwMDEwMDk4Iiwic2NvcGUiOiJlY2hhdDpldHMtY2FyZXRha2VyIGVjaGF0OmV0cy1lbXBsb3llZSBlY2hhdDplZXAiLCJsb2dpbkluZm8iOiJlY2hhdDp1ZToxOTYwMDAxMDA5OCIsInVzZXJJZCI6LTEsInZlcnNpb24iOiIxLjAuMCJ9.augjMcBV7BKXOb4_JjIcZK4RGuYDoVf73DksFVR8o49F1yQWZiRn07ZH_xmt2RnJmpwRtg-fUmIGn7tNv3Q7Dg" http://localhost:8765/lua
<h1>HELLO,Lua</h1>