opt_auth —— 基于 OpenResty 的用户名/密码 + TOTP 双因素认证组件
项目概述
- 提供登录页(用户名、密码、TOTP 验证码),认证成功后颁发 JWT 写入 Cookie。
- 后续请求通过校验 Cookie 中的 JWT、来源 IP 与本地用户缓存决定是否放行。
- 适合内网或简单后台的统一接入鉴权,作为 OpenResty/Nginx 的 Lua 模块使用。
主要能力
- 登录表单输出与友好的提示信息。
- 用户密码校验(当前为 MD5,比对共享缓存中的用户信息)。
- TOTP 校验(兼容 Google Authenticator 等),支持生成 otpauth/二维码 URL。
- JWT 签发与校验:包含 role、user、ip、sec 及 iat/exp 字段;Cookie 含 HttpOnly、SameSite=Lax,HTTPS 下追加 Secure。
- 用户信息热加载:从 JSON 文件加载到 lua_shared_dict 缓存。
快速开始
- 环境准备
- OpenResty(提供 ngx、ngx.hmac_sha1、lua_shared_dict 等)
- Lua 依赖:cjson、resty.jwt(可通过 opm 安装)
- 放置代码
- 将本仓库的 lib 目录加入
lua_package_path,或通过opm安装发布包。
- 将本仓库的 lib 目录加入
- 配置 Nginx(见下方"示例配置")
- 准备用户数据 JSON 文件(见下方"用户数据示例")
Nginx/OpenResty 示例配置
worker_processes 1;
events { worker_connections 1024; }
http {
# 共享字典用于缓存用户信息
lua_shared_dict jwt 10m;
# 调整 lua 包路径(按实际路径)
lua_package_path "/path/to/opt_auth/lib/?.lua;;";
init_worker_by_lua_block {
-- 周期性加载用户数据到共享缓存
local access = require("otp_access")
-- 每 30 秒从指定 JSON 文件刷新用户信息
local ok, err = ngx.timer.every(30, access.reload_user_info, "/etc/openresty/users.json")
if not ok then
ngx.log(ngx.ERR, "failed to start reload_user_info timer: ", err)
end
}
server {
listen 8080;
server_name localhost;
# 仅示例:设置 JWT 签名密钥
set $jwt_key "CHANGE_ME_TO_A_STRONG_SECRET";
location / {
access_by_lua_block {
local access = require("otp_access")
-- 调用鉴权;通过时返回 0,失败将输出登录页并结束请求
access.do_auth(ngx.var.jwt_key)
}
proxy_pass http://127.0.0.1:9000; # 业务上游
}
}
}
用户数据示例(/etc/openresty/users.json)
{
"alice": {
"passwd": "e10adc3949ba59abbe56e057f20f883e", // 123456 的 MD5,仅示例
"totp_key": "JBSWY3DPEHPK3PXP", // Base32 密钥
"role": "admin"
},
"bob": {
"passwd": "202cb962ac59075b964b07152d234b70", // 123 的 MD5,仅示例
"totp_key": "NB2W45DFOIZA", // Base32 密钥
"role": "user"
}
}
注意:当前实现使用 MD5 校验密码,建议仅用于演示或内网。生产建议升级为带盐的强哈希(如 bcrypt/argon2),并提供兼容迁移。
TOTP 使用说明
- 生成 otpauth URL 或二维码 URL(供首次绑定到 Authenticator):
local otp = require("otp")
local t = otp.totp_init("JBSWY3DPEHPK3PXP")
local url = t:get_url("MyService", "alice") -- otpauth://totp/alice?secret=...&issuer=MyService
local qr = t:get_qr_url("MyService", "alice") -- Google Chart 的二维码链接
模块接口速览
- 入口模块:lib/otp_access.lua
req_auth(origin_uri, tip):输出登录页remove_auth():清除authCookiedo_auth(jwt_key):执行鉴权逻辑(Cookie/JWT 校验、表单校验、TOTP 校验、签发 JWT)reload_user_info(premature, user_info_path):从 JSON 文件加载用户信息至共享缓存
- TOTP 模块:lib/otp.lua
totp_init(secret_key):构造 TOTP 对象verify_token(code):校验 6 位动态码get_url(issuer, account)/get_qr_url(issuer, account):生成绑定链接
安全提示
- 已在代码内设置 Cookie 的 HttpOnly 与 SameSite=Lax,并在 HTTPS 下追加 Secure。
- JWT 增加
iat/exp字段,默认有效期 2 小时;如需更严格策略,可在网关或下游对exp/nbf再次核验。 - 绑定 IP 校验:JWT 中携带
ip,并在校验时与请求来源比对;若有代理,请正确传递并信任X-REAL-IP/X_FORWARDED_FOR。
版本与发布
- 包信息见 dist.ini,主模块为
lib/otp_access.lua。***
如何使用
- 获取代码并准备用户数据
- 克隆/下载本项目,将
conf/users.json按需修改;字段说明见"用户数据示例"。 - 如需生成新的 Base32 TOTP 密钥,可在 OpenResty REPL 中:
resty -e 'local otp=require("otp"); local t=otp.totp_init(nil); print(t.key)'
- 使用 Docker Compose 运行
- 直接在项目根目录执行:
docker-compose up --pull always -d
- 访问 http://localhost:8080/ ,使用
conf/users.json中的用户(如alice / 123456)+ 对应 TOTP 动态码登录。 - 退出登录:访问 http://localhost:8080/logout 清除 Cookie。
- 关键文件说明
docker-compose.yml:启动 OpenResty;容器启动时通过opm安装lua-resty-jwt。conf/nginx.conf:最小可运行配置,映射 8080 端口,周期性加载conf/users.json。lib/:鉴权逻辑与 TOTP 实现。
- 在自有环境集成
- 将
lib放入lua_package_path,并在access_by_lua_block中调用:
local access = require("otp_access")
access.do_auth("YOUR_JWT_SECRET")
- 在
init_worker_by_lua*中周期性调用reload_user_info加载用户数据:
local ok, err = ngx.timer.every(30, access.reload_user_info, "/path/to/users.json")