-- /usr/local/openresty/nginx/conf.d/jwt-auth.lua -- JWT 认证服务 - Access Token 验证脚本(修正版) -- 功能: -- 1. 验证 JWT Token 的合法性(签名、过期时间) -- 2. 提取用户信息并传递给后端服务 -- 注意: -- - 所有时间使用北京时间(UTC+8) -- - 需在 nginx.conf 中配置 jwt_secret 变量 -- - Token 格式:Authorization: Bearer local jwt = require "resty.jwt" local cjson = require "cjson" -- ============================================================ -- 1. 配置参数(从 Nginx 变量中读取) -- ============================================================ local jwt_secret = ngx.var.jwt_secret local jwt_algorithm = ngx.var.jwt_algorithm or "HS256" -- 检查密钥是否配置(避免空密钥导致验证失效) if not jwt_secret or jwt_secret == "" then ngx.log(ngx.ERR, "[JWT Auth] JWT secret not configured (empty or missing)") ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR ngx.header["Content-Type"] = "application/json; charset=utf-8" ngx.say(cjson.encode({ detail = "JWT 配置错误:未设置有效的密钥(jwt_secret)" })) return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end -- ============================================================ -- 2. 获取并验证 Authorization 请求头 -- ============================================================ local auth_header = ngx.var.http_Authorization if not auth_header or auth_header == "" then ngx.log(ngx.WARN, "[JWT Auth] Missing Authorization header from IP: ", ngx.var.remote_addr) ngx.status = ngx.HTTP_UNAUTHORIZED ngx.header["Content-Type"] = "application/json; charset=utf-8" ngx.say(cjson.encode({ detail = "缺少 Authorization 请求头,请按格式传递:Bearer " })) return ngx.exit(ngx.HTTP_UNAUTHORIZED) end -- 提取 Bearer Token(兼容大小写和空格格式:"bearer token" 或 "Bearer token") local token = string.match(auth_header, "^[Bb][Ee][Aa][Rr][Ee][Rr]%s+(.+)$") if not token then ngx.log(ngx.WARN, "[JWT Auth] Invalid Authorization format from IP: ", ngx.var.remote_addr) ngx.status = ngx.HTTP_UNAUTHORIZED ngx.header["Content-Type"] = "application/json; charset=utf-8" ngx.say(cjson.encode({ detail = "Authorization 格式错误,正确格式:Authorization: Bearer <你的Token>" })) return ngx.exit(ngx.HTTP_UNAUTHORIZED) end -- ============================================================ -- 3. 验证 JWT Token 核心合法性(签名 + 过期时间) -- ============================================================ -- 仅验证签名和标准声明(exp/iat 等),不强制 Token 类型 local jwt_obj = jwt:verify(jwt_secret, token) -- 检查验证结果是否为空(极端异常情况) if not jwt_obj then ngx.log(ngx.ERR, "[JWT Auth] JWT verify failed: jwt_obj is nil (IP: ", ngx.var.remote_addr, ")") ngx.status = ngx.HTTP_UNAUTHORIZED ngx.header["Content-Type"] = "application/json; charset=utf-8" ngx.say(cjson.encode({ detail = "Token 验证失败:无效的 Token 格式" })) return ngx.exit(ngx.HTTP_UNAUTHORIZED) end -- 【安全关键】验证签名是否通过 if jwt_obj.verified == false then ngx.log(ngx.WARN, "[JWT Auth] JWT signature verification failed (IP: ", ngx.var.remote_addr, ")") ngx.status = ngx.HTTP_UNAUTHORIZED ngx.header["Content-Type"] = "application/json; charset=utf-8" ngx.say(cjson.encode({ detail = "Token 签名验证失败:可能是密钥不匹配或 Token 被篡改" })) return ngx.exit(ngx.HTTP_UNAUTHORIZED) end -- 验证 Token 是否有效(包含过期时间 exp 校验) if not jwt_obj.valid then local reason = jwt_obj.reason or "未知原因" ngx.log(ngx.WARN, "[JWT Auth] Invalid JWT from IP: ", ngx.var.remote_addr, ", Reason: ", reason) ngx.status = ngx.HTTP_UNAUTHORIZED ngx.header["Content-Type"] = "application/json; charset=utf-8" -- 根据错误原因返回友好提示 local error_msg = "Token 无效" if reason == "exp" or reason == "expiry" then error_msg = "Token 已过期,请重新获取新 Token" elseif reason == "signature" then error_msg = "Token 签名验证失败:密钥不匹配或 Token 被篡改" elseif reason == "iat" then error_msg = "Token 签发时间异常(iat 字段无效)" end ngx.say(cjson.encode({ detail = error_msg })) return ngx.exit(ngx.HTTP_UNAUTHORIZED) end -- ============================================================ -- 4. 提取用户信息并传递给后端(通过 Nginx 变量) -- ============================================================ local payload = jwt_obj.payload if not payload then ngx.log(ngx.ERR, "[JWT Auth] JWT payload is nil (IP: ", ngx.var.remote_addr, ")") ngx.status = ngx.HTTP_UNAUTHORIZED ngx.header["Content-Type"] = "application/json; charset=utf-8" ngx.say(cjson.encode({ detail = "Token 无效:缺少 payload 数据(用户信息字段)" })) return ngx.exit(ngx.HTTP_UNAUTHORIZED) end -- 提取 Token 中的用户信息(字段需与 Token 生成时的 payload 对应) local accountID = payload.accountID or "" local name = payload.name or "" local userCode = payload.userCode or "" local contactNumber = payload.contactNumber or "" local jti = payload.jti or "" -- 设置 Nginx 变量(后端可通过请求头 X-User-XXX 获取这些信息) ngx.var.user_accountID = accountID ngx.var.user_name = name ngx.var.user_userCode = userCode ngx.var.user_contactNumber = contactNumber ngx.var.user_jti = jti -- 【可选】调试日志:验证通过时记录用户信息(生产环境可注释,避免敏感信息泄露) -- ngx.log(ngx.INFO, "[JWT Auth] User authenticated: accountID=", accountID, ", userCode=", userCode, " (IP: ", ngx.var.remote_addr, ")") -- ============================================================ -- 5. 验证通过,放行请求 -- ============================================================ -- 脚本执行完毕后,Nginx 会继续执行后续指令(如 proxy_pass 转发到后端服务)