| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- -- /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 <token>
- 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 <token>"
- }))
- 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 转发到后端服务)
|