目录

用 Python 实现 JWT Token 校验、Nginx 转发与 Lua 插件结合实战

最近帮一个朋友优化他的 API 服务,核心需求是安全鉴权,还要性能拉满。我选了 JWT Token 来实现,因为它简单高效,配合 Python 后端和 Nginx 前端转发,再加上 Lua 插件优化,完全可以打造一个又快又安全的鉴权系统。这篇教程是我从头到尾的实战记录,带你用 Python 生成和校验 JWT,结合 Nginx 和 Lua 插件搞定 API 鉴权,代码、配置、踩坑全都有,跟着做准没错!

先从后端开始,Python 处理 JWT 超级方便,我用的是 PyJWT 库,简单又可靠。以下是我的实现思路和代码。

我们需要生成一个带用户信息的 JWT Token,包含 user_idexpire_time,用 HS256 算法签名。代码如下:

import jwt
import datetime

SECRET_KEY = "your-secret-key-keep-it-safe"  # 别真用这个,换成复杂的!

def generate_token(user_id):
    payload = {
        "user_id": user_id,
        "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=24),
        "iat": datetime.datetime.utcnow()
    }
    token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
    return token

# 示例:生成一个 token
token = generate_token(123)
print(token)

小贴士SECRET_KEY 一定要复杂且安全,别直接写在代码里,建议用环境变量(比如 os.environ)。我一开始没管这个,结果测试代码泄露,差点被朋友笑死。

校验 Token 的时候,需要验证签名和过期时间。我写了个装饰器,方便在 Flask API 里用:

from functools import wraps
from flask import request, jsonify

def verify_token(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get("Authorization")
        if not token:
            return jsonify({"error": "Missing token"}), 401
        try:
            data = jwt.decode(token.replace("Bearer ", ""), SECRET_KEY, algorithms=["HS256"])
            request.user_id = data["user_id"]
        except jwt.ExpiredSignatureError:
            return jsonify({"error": "Token expired"}), 401
        except jwt.InvalidTokenError:
            return jsonify({"error": "Invalid token"}), 401
        return f(*args, **kwargs)
    return decorated

# 示例:受保护的 API
from flask import Flask
app = Flask(__name__)

@app.route("/protected")
@verify_token
def protected():
    return jsonify({"message": f"Hello, user {request.user_id}!"})

踩坑经验:我一开始没处理 Bearer 前缀,直接 decode 报错,折腾了半小时才发现是 header 格式问题。记得检查 Authorization 头,确保去掉 Bearer 前缀再解码。

Python 后端搞定后,接下来是 Nginx 做前置网关,拦截无效请求,减轻后端压力。我用 Nginx 转发 API 请求,配合 auth_request 模块来校验 JWT。

以下是我的 Nginx 配置,监听 80 端口,转发到 Flask 后端(跑在 127.0.0.1:5000):

server {
    listen 80;
    server_name api.yourdomain.com;

    location / {
        auth_request /auth;
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /auth {
        internal;
        proxy_pass http://127.0.0.1:5000/verify;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header Authorization $http_authorization;
    }
}

解释

  • auth_request /auth:Nginx 在转发前先调用 /auth 端点校验 Token。
  • /auth 路由是内部路由,调用后端的 /verify 接口,只传递 Authorization 头。
  • proxy_pass:有效请求转发到后端 Flask。

在 Flask 里加一个 /verify 接口,专门给 Nginx 校验用:

@app.route("/verify", methods=["GET"])
def verify():
    token = request.headers.get("Authorization")
    if not token:
        return "", 401
    try:
        jwt.decode(token.replace("Bearer ", ""), SECRET_KEY, algorithms=["HS256"])
        return "", 200
    except (jwt.ExpiredSignatureError, jwt.InvalidTokenError):
        return "", 401

踩坑经验:我一开始忘了在 Nginx 配置里加 proxy_set_header Authorization,导致后端收不到 Token,Nginx 疯狂返回 401。检查日志(/var/log/nginx/error.log)救了我一命。

如果您想深入研究Nginx优化,请查看Nginx日志分析利器 GoAccess(目标访问)部署安装教程 有关监视服务器性能的提示。

Nginx 自带的 auth_request 已经够用了,但如果追求极限性能,可以用 Lua 插件直接在 Nginx 层校验 JWT,减少后端请求。我用的是 OpenResty(Nginx + LuaJIT),效率飞起。

先装 OpenResty(Ubuntu 环境为例):

sudo apt-get update
sudo apt-get install -y openresty

再装 Lua 的 JWT 库:

luarocks install lua-resty-jwt

踩坑经验:luarocks 安装可能报 SSL 错误,我切换到国内源(luarocks config variables.LUAROCKS_CONFIG https://luarocks.cn)才搞定。

在 Nginx 配置文件里加 Lua 脚本,直接校验 JWT:

http {
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    server {
        listen 80;
        server_name api.yourdomain.com;

        location / {
            access_by_lua_block {
                local jwt = require "resty.jwt"
                local auth_header = ngx.var.http_authorization
                local secret = "your-secret-key-keep-it-safe"

                if not auth_header then
                    ngx.status = 401
                    ngx.say('{"error": "Missing token"}')
                    return ngx.exit(401)
                end

                local token = string.gsub(auth_header, "Bearer ", "")
                local jwt_obj = jwt:verify(secret, token)
                if not jwt_obj.valid then
                    ngx.status = 401
                    ngx.say('{"error": "Invalid or expired token"}')
                    return ngx.exit(401)
                end

                ngx.req.set_header("X-User-ID", jwt_obj.payload.user_id)
                ngx.exec("@backend")
            }

            location @backend {
                proxy_pass http://127.0.0.1:5000;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-User-ID $http_x_user_id;
            }
        }
    }
}

解释

  • access_by_lua_block:在 Nginx 访问阶段运行 Lua 脚本,校验 JWT。
  • jwt:verify:用 lua-resty-jwt 验证 Token,失败返回 401。
  • ngx.exec("@backend"):验证通过后转发到后端,还把 user_id 传过去。

踩坑经验:Lua 脚本对 Token 格式敏感,Bearer 前缀没去干净会导致验证失败。可以用 ngx.log 打印调试信息,日志在 /usr/local/openresty/nginx/logs/error.log

我用 wrk 测了下性能(100 并发,10 秒),对比 Nginx auth_request 和 Lua 方案:

  • Nginx auth_request:平均延迟 12ms,QPS 约 8000。
  • Lua 插件:平均延迟 8ms,QPS 约 12000。

Lua 方案快了 30% 左右,因为它直接在 Nginx 层处理,省去了后端请求的开销。高并发场景下,Lua 的优势更明显。

这套 Python + Nginx + Lua 的组合适合以下场景:

  • 中小型 API 服务:需要快速实现安全鉴权,Python 后端简单好维护。
  • 高并发场景:Nginx + Lua 能扛住高流量,适合对外 API。
  • 预算有限:OpenResty 和 Python 都是开源方案,成本低。

局限性

  • Lua 脚本调试不友好,新手可能被日志整蒙。
  • JWT 本身无状态,适合轻量鉴权,但复杂权限管理还得加数据库。

如果需要挡住机器人或爬虫,请参考 利用Cloudflare Turnstile+Nginx Lua 挡住爬虫

有关优化服务器性能的详细信息,请参见rclone进阶使用教程-常用命令详解 有助于高效同步日志或备份。

  1. 安全第一SECRET_KEY 别硬编码,用环境变量或密钥管理工具(比如 AWS Secrets Manager)。我一开始硬写在代码里,差点翻车。
  2. 日志救命:Nginx 和 Lua 的错误日志是救星,遇到 401 或 500 直接去看 /var/log/nginx/error.log 或 OpenResty 的日志。
  3. 测试要充分:上线前用 curl 测 Token 失效、过期等边界情况,我第一次没测过期 Token,结果线上直接 500。

这套 Python + Nginx + Lua 的鉴权方案,简单、性能高、成本低,适合大部分中小型项目。Python 负责灵活的 Token 生成,Nginx 拦截无效请求,Lua 插件把性能拉满,整个流程像搭积木一样顺畅。你试过类似的方案没?有什么骚操作,留言分享一下吧!

相关内容