# 安装依赖
yum -y install make gcc gcc-c++ wget crontabs zlib zlib-devel \
openssl openssl-devel perl patch bzip2 ca-certificates
# Stable version 下载nginx,openssl,luajit2源码
wget http://nginx.org/download/nginx-1.18.0.tar.gz
wget https://www.openssl.org/source/openssl-1.1.1g.tar.gz
wget https://github.com/openresty/luajit2/archive/v2.1-20190329.tar.gz
tar xf openssl-1.1.1g.tar.gz
tar xf nginx-1.18.0.tar.gz
tar xf v2.1-20190329.tar.gz
# 安装luajit2
cd luajit2-2.1-20190329
make && make install PREFIX=/usr/local/luajit && cd
cat > /etc/ld.so.conf.d/luajit.conf<<EOF
/usr/local/luajit/lib
EOF
ln -sf /usr/local/luajit/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2
ldconfig
# 下载lua模块和nginx开发模块
wget https://github.com/openresty/lua-nginx-module/archive/v0.10.14.tar.gz
tar xf v0.10.14.tar.gz
wget https://github.com/vision5/ngx_devel_kit/archive/v0.3.1.tar.gz
tar xf v0.3.1.tar.gz
cat >/etc/profile.d/luajit.sh<<EOF
export LUAJIT_LIB=/usr/local/luajit/lib
export LUAJIT_INC=/usr/local/luajit/include/luajit-2.1
EOF
source /etc/profile.d/luajit.sh
# 安装nginx
useradd -s /sbin/nologin www
cd nginx-1.18.0
./configure --user=www --group=www --prefix=/usr/local/nginx --with-http_stub_status_module \
--with-http_ssl_module --with-http_v2_module --with-http_gzip_static_module --with-http_sub_module \
--with-stream --with-stream_ssl_module --with-openssl=/root/openssl-1.1.1g \
--with-openssl-opt='enable-weak-ssl-ciphers' --with-ld-opt=-Wl,-rpath,/usr/local/luajit/lib \
--add-module=/root/lua-nginx-module-0.10.14 --add-module=/root/ngx_devel_kit-0.3.1
make -j `grep 'processor' /proc/cpuinfo | wc -l`
make install
ln -sf /usr/local/nginx/sbin/nginx /usr/bin/nginx
mkdir -p /home/wwwroot/default
chown -R www.www /home/wwwroot/default
mkdir /home/wwwlogs && chown www.www /home/wwwlogs
mkdir /usr/local/nginx/conf/vhost
echo ok > /home/wwwroot/default/index.html
cat >/etc/systemd/system/nginx.service<<EOF
[Unit]
Description=The NGINX HTTP and reverse proxy server
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/usr/local/nginx/logs/nginx.pid
ExecStart=/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=false
[Install]
WantedBy=multi-user.target
EOF
systemctl enable nginx
systemctl start nginx
curl localhost
ss -lntp | grep 80
至此,nginx和lua安装完毕,但还有一些参数和配置需要优化
www用户打开的文件描述符数,进程数:
cat >>/etc/security/limits.conf<<eof
* soft nproc 65535
* hard nproc 65535
* soft nofile 65535
* hard nofile 65535
eof
echo "fs.file-max=65535" >> /etc/sysctl.conf
nginx配置文件/usr/local/nginx/conf/nginx.conf
,如下:
user www www;
worker_processes auto;
worker_cpu_affinity auto;
error_log /home/wwwlogs/nginx_error.log crit;
pid /usr/local/nginx/logs/nginx.pid;
worker_rlimit_nofile 51200;
events
{
use epoll;
worker_connections 51200;
multi_accept off;
accept_mutex off;
}
http
{
include mime.types;
default_type application/octet-stream;
server_names_hash_bucket_size 128;
client_header_buffer_size 32k;
large_client_header_buffers 4 32k;
client_max_body_size 50m;
sendfile on;
sendfile_max_chunk 512k;
tcp_nopush on;
keepalive_timeout 60;
tcp_nodelay on;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/xml+rss;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_disable "MSIE [1-6]\.";
server_tokens off;
access_log off;
server
{
listen 80 default_server reuseport;
server_name _;
index index.html index.htm;
root /home/wwwroot/default;
location /lua { default_type text/html; content_by_lua 'ngx.say("hello world")'; }
access_log off;
}
include vhost/*.conf;
}
检验lua是否安装成功:curl localhost/lua
更多参考:lnmp.org
参考:https://jwt.io/
PHP参考:https://github.com/firebase/php-jwt
python:https://github.com/jpadilla/pyjwt/
jwt的库有各种语言的,使用起来非常简单
jwt本身是明文的,如果使用是的http,https会加密所有内容
nbf为not before,即不早于某时间戳
exp为expired,即过期时间戳
pip install pyjwt
Python 2.7.5 (default, Apr 2 2020, 13:16:51)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import jwt
>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
>>> encoded
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
>>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
{u'some': u'payload'}
如果是HTTP协议的接口,可以将jwt放在cookie中,前端请求时会自动带上cookie
当然,也可以保存到localStorage,发送Ajax请求时主动带上
在nginx上使用lua判断jwt是否有效
如果是非HTTP的接口,也可以在nginx上使用stream反向代理tcp/udp接口。
lua和nginx搭配最好的是openresty,还有API管理工具Kong,openresty中有各种lua模块,kong中有各种api管理工具,这里以nginx为主,仅解决jwt的依赖
nginx中以lua脚本方式使用jwt的依赖有:lua-cjson、lua-resty-hmac、lua-resty-string、lua-resty-jwt
将所有的依赖放入/usr/local/luajit/lib/resty
安装lua-cjson库,cjson是一个json的解析库,lua中非常好用
wget https://www.kyne.com.au/~mark/software/download/lua-cjson-2.1.0.tar.gz
tar xf lua-cjson-2.1.0.tar.gz
sed -i 's#^LUA_INCLUDE_DIR\s*=\s*$(PREFIX)/include#LUA_INCLUDE_DIR = $(PREFIX)/luajit/include/luajit-2.1#' Makefile
# lua_cjson.c:1298:13: error: static declaration of ‘luaL_setfuncs’ follows non-static declaration
sed -i '1298s/^static void/void/' lua_cjson.c
make && make install
因为是写脚本,修改nginx.conf中error_log /home/wwwlogs/nginx_error.log info;
将错误级别改为info以便调试,结束后可以改回crit
# 在http中添加lua的path路径,注意最后是两个分号
lua_package_path "/usr/local/luajit/lib/?.lua;;";
# 在server中添加测试代码
location /jwt { default_type text/html; content_by_lua '
local cjson = require "cjson"
ngx.say(cjson.encode({3, 4, 5}))
'; }
nginx -s reload
curl localhost/jwt
# [3,4,5]
mkdir /usr/local/luajit/lib/resty
wget https://github.com/openresty/lua-resty-string/archive/v0.12.tar.gz
tar xf v0.12.tar.gz && cp lua-resty-string-0.12/lib/resty/*.lua /usr/local/luajit/lib/resty/
wget https://github.com/SkyLothar/lua-resty-jwt/releases/download/v0.1.11/lua-resty-jwt-0.1.11.tar.gz
tar lua-resty-jwt-0.1.11.tar.gz && cp lua-resty-jwt-0.1.11/lib/resty/*.lua /usr/local/luajit/lib/resty/
wget https://github.com/jkeys089/lua-resty-hmac/archive/v0.05.tar.gz
tar xf v0.05.tar.gz && cp -y lua-resty-hmac-0.05/lib/resty/hmac.lua /usr/local/luajit/lib/resty/
测试jwt是否可用,nginx.conf中添加:
location /sign {
content_by_lua '
local cjson = require "cjson"
local jwt = require "resty.jwt"
local jwt_token = jwt:sign(
"lua-resty-jwt",
{
header={typ="JWT", alg="HS256"},
payload={foo="bar"}
}
)
ngx.say(jwt_token)
';
}
location /verify {
content_by_lua '
local cjson = require "cjson"
local jwt = require "resty.jwt"
local jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" ..
".eyJmb28iOiJiYXIifQ" ..
".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY"
local jwt_obj = jwt:verify("lua-resty-jwt", jwt_token)
ngx.say(cjson.encode(jwt_obj))
';
}
结果:
nginx -s reload
curl localhost/sign
#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY
curl localhost/verify
#{"payload":{"foo":"bar"},"reason":"everything is awesome~ :p","raw_header":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9","valid":true,"header":{"alg":"HS256","typ":"JWT"},"signature":"VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY","verified":true,"raw_payload":"eyJmb28iOiJiYXIifQ"}
这里给出一个验证逻辑:
首次打开时,后端生成jwt,添加到GET参数jwt中,如果不是浏览器访问,可以直接加到header中
nginx中判断,如果有jwt,在header中设置Authorization为jwt
lua验证时,先从header中读取Authorization,如果没有从cookie中读取
验证成功后,添加cookie,并设置授权变量,反向代理到api,api中可进行自己的处理
退出时,删除cookie
因为,jwt存在cookie中,安全性不足,一般需要HTTPS
location ^~ /api/ {
if ($arg_jwt) {
set $http_Authorization "Bearer $arg_jwt";
}
set $apiUser '';
access_by_lua '
local jwt = require("resty.api-jwt")
jwt.auth()
';
proxy_set_header X-WEBAUTH-USER $apiUser;
proxy_pass http://127.0.0.1:4000/;
}
location ^~ /api/logout {
add_header Set-Cookie "api_token=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/api; HttpOnly; Secure";
proxy_pass http://127.0.0.1:4000/;
}
resty中的api-jwt.lua脚本:
local jwt = require "resty.jwt"
local cjson = require "cjson"
--fill your real secret
local secret = "lua-resty-jwt"
local M = {}
function M.auth(claim_specs)
-- require Authorization request header
local auth_header = ngx.var.http_Authorization
if auth_header == nil then
auth_header = ngx.var.cookie_api_token
end
if auth_header ~= nil then
local _, _, token = string.find(auth_header, "Bearer%s+(.+)")
if token ~= nil then
local jwt_obj = jwt:verify(secret, token)
if jwt_obj.verified == false then
ngx.log(ngx.WARN, "Invalid token: ".. jwt_obj.reason)
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
if not ngx.var.cookie_api_token or ngx.var.cookie_api_token ~= auth_header then
ngx.header["Set-Cookie"] = "api_token=Bearer "..token.."; path=/api; httpOnly=1; Secure;"
end
ngx.var.apiUser = jwt_obj["payload"]["user"]
else
ngx.log(ngx.WARN, "Missing token")
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
else
ngx.log(ngx.WARN, "require authorization request header")
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
end
return M
lua获取get参数及判断是否是数组:
local uri_args = ngx.req.get_uri_args()
local uid = 0
if type(uri_args['uid']) == 'table' then
uid= uri_args['uid'][1]
else
uid= uri_args['uid']
end
kong中有许多非常有用的lua脚本
参考:
https://www.jianshu.com/p/66d5163b9e99
https://segmentfault.com/a/1190000015677681