在Nginx服务器上解码JWT Token并记录用户信息的实现方案咨询
当然可以实现这个需求!既然你已经接受基于Lua的方案,那我们可以借助Nginx的ngx_lua模块(OpenResty自带这个模块,用起来很方便)来解码JWT Token,提取用户信息后写入日志。下面是具体的步骤和配置示例:
前置准备
首先你需要确保Nginx环境支持Lua:
- 最省心的方式是使用OpenResty(Nginx的增强发行版,默认集成了
ngx_lua和常用Lua库)。 - 如果是自行编译Nginx,编译时需添加
--add-module=path/to/ngx_lua参数(路径替换为你的ngx_lua模块位置)。
实现思路
核心逻辑分为三步:
- 从请求的
Authorization头中提取JWT Token(去掉Bearer前缀)。 - 解码JWT的Payload部分(JWT由Header/Payload/Signature三部分组成,用户信息通常存在Payload里)。
- 将提取到的用户信息存入Nginx变量,最后在自定义日志格式中引用这些变量。
具体配置示例
1. 定义自定义日志格式
先在Nginx主配置或Server块中定义包含JWT用户信息的日志格式:
log_format jwt_combined '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' '"jwt_sub:$jwt_sub" "jwt_username:$jwt_username"';
这里的$jwt_sub和$jwt_username是我们后续要通过Lua脚本设置的变量,对应JWT Payload里的用户ID和用户名(根据你的实际JWT字段调整)。
2. 编写Lua处理逻辑并配置反向代理
在Server块中加入Lua脚本处理JWT,并配置反向代理规则:
server { listen 80; server_name your-domain.com; # 提前声明要使用的Nginx变量(可选,但能避免潜在的变量未定义警告) set $jwt_sub ""; set $jwt_username ""; # 处理JWT解码与信息提取 access_by_lua_block { -- 获取Authorization请求头 local auth_header = ngx.req.get_headers()["Authorization"] if auth_header then -- 提取Bearer后的Token部分 local jwt_token = string.match(auth_header, "^Bearer%s+(.+)") if jwt_token then -- 分割JWT的三个组成部分 local parts = {} for part in string.gmatch(jwt_token, "[^.]+") do table.insert(parts, part) end if #parts == 3 then -- 处理Payload的Base64URL解码(JWT用的是Base64URL编码,需转成标准Base64) local payload = parts[2] payload = payload:gsub("-", "+"):gsub("_", "/") -- 补全Base64的padding字符 local padding = #payload % 4 if padding > 0 then payload = payload .. string.rep("=", 4 - padding) end -- 解码为JSON字符串并解析成Lua表 local decoded_payload = ngx.decode_base64(payload) if decoded_payload then local cjson = require("cjson") local payload_data = cjson.decode(decoded_payload) -- 将需要的用户信息存入Nginx变量 ngx.var.jwt_sub = payload_data.sub or "" ngx.var.jwt_username = payload_data.username or "" end end end end } # 反向代理到你的微服务集群 location / { proxy_pass http://your-microservice-upstream; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 使用自定义日志格式记录日志 access_log /var/log/nginx/jwt_access.log jwt_combined; }
优化方案:使用
lua-resty-jwt库简化代码 如果不想自己处理Base64URL解码和JSON解析的细节,可以用官方维护的lua-resty-jwt库,它封装了更完善的JWT处理逻辑:
- 安装库(OpenResty环境下可通过
opm安装):
opm install openresty/lua-resty-jwt
- 修改Lua脚本部分:
access_by_lua_block { local jwt = require("resty.jwt") local auth_header = ngx.req.get_headers()["Authorization"] if auth_header then local jwt_token = string.match(auth_header, "^Bearer%s+(.+)") if jwt_token then -- 仅解析JWT不验证签名(如果需要验证,传入你的签名密钥作为第二个参数) local jwt_obj = jwt:load_jwt(jwt_token) if jwt_obj.valid then ngx.var.jwt_sub = jwt_obj.payload.sub or "" ngx.var.jwt_username = jwt_obj.payload.username or "" end end end }
注意事项
- 上述基础方案不验证JWT签名,如果需要确保Token的合法性,可以在
lua-resty-jwt的verify方法中传入你的签名密钥(比如jwt:verify(your_secret_key, jwt_token))。 - 日志中的字段要和你的JWT Payload实际字段对应,比如有的JWT用
user_id代替sub,记得调整变量赋值逻辑。 - 脚本中已处理了多种异常情况(比如无Authorization头、JWT格式错误),不会因解码失败阻断请求,保证反向代理的正常转发。
内容的提问来源于stack exchange,提问作者Raeesaa




