〇、必备技能
- 熟悉 Nginx
- 对 Lua 及 OpenResty 中进行交互有基本了解
- 熟悉 Linux 的基本操作
一、环境准备
- CentOS 7
- OpenResty 1.13.6.2
二、安装
1、安装 OpenResty
参考官方文档 OpenResty® Linux 包——CentOS
$ sudo yum install yum-utils
# 添加仓库
$ sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
# 安装 OpenResty
$ sudo yum install openresty
# 安装命令行工具 resty
$ sudo yum install openresty-resty
# 安装命令行工具 opm
$ sudo yum install openresty-opm
可以列出所有 openresty 仓库里头的软件包,根据需要选择安装:
$ sudo yum --disablerepo="*" --enablerepo="openresty" list available
OpenResty RPM 包 页面包含更多细节。
2、测试 OpenResty
修改 nginx.conf 配置文件:
worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("hello, world
")
}
}
}
}
启动服务,通过 cURL 或者浏览器测试:
$ curl http://localhost:8080/
返回
hello, world
表示 OpenResty 安装成功,Lua 脚本正常工作。
3、安装 JWT 的 Lua 插件
SkyLothar/lua-resty-jwt 是用于 ngx_lua 和 LuaJIT 的 Lua 实现库。在项目 README 的 Installation 部分有安装说明。本文采用的是 opm
的安装方式:
$ sudo opm get SkyLothar/lua-resty-jwt
4、测试 JWT 插件
参考配置
# nginx.conf
lua_package_path "/path/to/lua-resty-jwt/lib/?.lua;;";
server {
default_type text/plain;
location = /verify {
content_by_lua_block {
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))
}
}
location = /sign {
content_by_lua_block {
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)
}
}
}
说明:
lua_package_path
:指定 Lua 脚本位置。
注意:上述配置中搜索路径的最后出现了;;
两个半角分号,代表的是 LuaJIT 安装时的原始搜索路径,如果在前面的搜索路径里面无法搜索到需要的模块,就会依次搜索后面的路径。default_type
:指定输出为纯文本,无实际作用,只是为了在浏览器中方便查看换行等符号。/verify
:校验 JWT
$ curl http://lh:39100/verify
执行结果:
{
"signature": "VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY",
"reason": "everything is awesome~ :p",
"valid": true,
"raw_header": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9",
"payload": {
"foo": "bar"
},
"header": {
"alg": "HS256",
"typ": "JWT"
},
"verified": true,
"raw_payload": "eyJmb28iOiJiYXIifQ"
}
-
/sign
:生成 JWT
$ curl http://lh:39100/sign
执行结果:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY
得到以上执行结果,表示 JWT 插件安装成功。
三、编写验证逻辑
通过前面的步骤,已经完成了基本环境的搭建。后续编写校验逻辑,根据校验结果控制访问行为。
目标:
验证 JWT:
- 成功:将请求转发至后台服务
- 失败:返回 401,并以 JSON 形式返回错误原因
思路:
通过 Header 传 JWT:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.VxhQcGihWyHuJeHhpUiq2FU7aW2s_3ZJlY6h1kdlmJY
Lua 拿到 JWT 之后,校验合法性。
代码:
校验脚本:
-- nginx-jwt.lua
local cjson = require "cjson"
local jwt = require "resty.jwt"
--your secret
local secret = "a secret key"
local M = {}
function M.auth()
-- require Authorization request header
local auth_header = ngx.var.http_Authorization
if auth_header == nil then
ngx.log(ngx.WARN, "No Authorization header")
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
ngx.log(ngx.INFO, "Authorization: " .. auth_header)
-- require Bearer token
local _, _, token = string.find(auth_header, "Bearer%s+(.+)")
if token == nil then
ngx.log(ngx.WARN, "Missing token")
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
ngx.log(ngx.INFO, "Token: " .. token)
local jwt_obj = jwt:verify(secret, token)
if jwt_obj.verified == false then
ngx.log(ngx.WARN, "Invalid token: ".. jwt_obj.reason)
ngx.status = ngx.HTTP_UNAUTHORIZED
ngx.header.content_type = "application/json; charset=utf-8"
ngx.say(cjson.encode(jwt_obj))
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
ngx.log(ngx.INFO, "JWT: " .. cjson.encode(jwt_obj))
end
return M
nginx.conf 配置:
# nginx.conf
location /check-jwt {
default_type text/plain;
access_by_lua_block {
local obj = require('nginx-jwt')
obj.auth()
}
proxy_pass "http://backend-server/";
}
延展:
1、对于实际应用的场景,可以根据需要将 JWT 放在自己需要的位置,比如 Cookie 中。
2、对于错误时的返回值,本例只是将验证结果以 JSON 形式返回,这样暴露了很多信息。可以考虑将详细信息记入日志,将裁剪过的内容返回给请求方。
四、参考资料
- Nginx实现JWT验证-基于OpenResty实现
- OpenResty 不完全指南
- nginx_lua_cookbook
- OpenResty最佳实践 - github.io
- OpenResty 最佳实践 - 极客学院
- Openresty 执行阶段与api之间的关系(一)
- JSON Web Token (JWT) 简介
- Return JSON responses when using OpenResty + Lua
(完)