Windows版OpenResty(Nginx)+JWT鉴权安装
LiuSovia 化神

一.penResty介绍

OpenResty 是我们构建高性能、高并发 Web 架构时不可或缺的利器。它不仅仅是一个 Web 服务器,更是一个基于 Nginx 与 Lua 的高性能 Web 平台

简单来说,OpenResty 将 Nginx(高性能反向代理/负载均衡器)与 LuaJIT(高性能即时编译器)以及一系列 Lua 模块完美集成,让你能够直接在 Nginx 内部编写业务逻辑,而无需依赖后端应用服务器(如 Java, Go, Python)来处理所有请求。

1.核心架构与原理

OpenResty 的核心在于 Nginx + LuaJIT + 丰富模块 的组合:

  • Nginx 基础:继承了 Nginx 的事件驱动、异步非阻塞 I/O 模型和多进程架构(Master/Worker),能够轻松应对高并发连接 1
  • LuaJIT 加速:内置了 LuaJIT(Lua Just-In-Time Compiler),它将 Lua 脚本编译成机器码直接执行,性能比标准 Lua 快数倍,几乎消除了脚本语言的开销 3
  • 11 个执行阶段:OpenResty 完美映射了 Nginx 的 11 个 HTTP 处理阶段(如 rewrite, access, content, log 等),允许你在每个阶段注入 Lua 代码来拦截、修改或生成响应

2.核心特性与优势

  1. 超高并发性能:

    • 利用 Nginx 的非阻塞 I/O,单个 Worker 进程可处理数万并发连接。
    • LuaJIT 的即时编译使得 Lua 代码的执行效率接近 C 语言,响应时间通常减少 40-60% 1
  2. 动态内容生成:

    • 无需启动后端应用,直接在 Nginx 层通过 Lua 生成动态页面或 API 响应,极大降低延迟。
  3. 灵活的请求处理:

    • 访问控制:在 access_by_lua 阶段进行复杂的身份验证、IP 黑白名单、API 限流 2
    • 动态路由:根据请求头、参数或数据库状态,动态决定将请求转发到哪个后端服务 2
    • 协议转换:轻松实现 HTTP 到 WebSocket、TCP/UDP 的转换。
  4. 丰富的内置模块:

    • **ngx_lua**:核心模块,提供 Lua 脚本执行能力。
    • **ngx_http_redis / resty.redis**:直接连接 Redis,实现高性能缓存 3
    • **ngx_http_lua_upstream**:在 Lua 中动态代理请求到上游服务器。
    • **ngx_http_headers_more**:灵活添加/删除 HTTP 响应头。
    • **ngx_stream_lua**:支持 TCP/UDP 流量的 Lua 处理(非 HTTP 协议)3

3. 典型应用场景

作为运维,OpenResty 常用于以下场景:

  • API 网关:

    • 实现统一的鉴权(JWT 校验)、限流(基于 IP 或用户 ID)、熔断降级。
    • 动态路由:根据版本号(/api/v1, /api/v2)将请求分发到不同后端 2
  • 高性能缓存层:

    • 利用 lua_shared_dict 实现进程间共享内存缓存,减少数据库压力。
    • 直接连接 Redis 缓存热点数据,实现“边缘缓存” 2
  • 动态负载均衡:

    • 根据后端服务器的健康状态、负载情况,动态调整权重或剔除故障节点。
  • 协议适配与转换:

    • 将 HTTP 请求转换为内部 RPC 调用,或将 TCP 流量(如游戏服)进行逻辑处理。
  • 安全防御:

    • 在 Nginx 层实现 WAF(Web 应用防火墙),拦截 SQL 注入、XSS 等攻击,无需后端参与。

      二.OpenResty安装过程

第一步:下载与安装 OpenResty

  1. 下载: 前往 OpenResty 官网下载页
  2. 选择版本: 选择 Win64 版本(例如 openresty-1.29.2.1-win64.zip)。
  3. 解压: 将其解压到你服务器的路径,例如 D:\openresty-1.29.2.1-win64

第二步:准备 JWT 插件库

OpenResty 需要一个 Lua 库来处理 JWT。

  1. 下载 lua-resty-jwt 里的 jwt.lua 和 hmac.lua、string.lua。

下载地址:

https://raw.githubusercontent.com/SkyLothar/lua-resty-jwt/master/lib/resty/jwt.lua

https://raw.githubusercontent.com/openresty/lua-resty-string/master/lib/resty/string.lua

https://raw.githubusercontent.com/jkeys089/lua-resty-hmac/master/lib/resty/hmac.lua

将这些 .lua 文件放入 D:\openresty-1.29.2.1-win64\lualib\resty 目录下。

  • 注意:如果该目录没有这些文件,你可以直接新建。

第三步:配置 HTTPS 证书

在D:\openresty-1.29.2.1-win64\conf\ssl 目录下放置你的 server.crt 和 server.key。
(如果没有,参考使用 OpenSSL 生成自签名证书)。

1
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout server.key -out server.crt

第四步:编写核心配置文件 nginx.conf

编辑 D:\openresty-1.29.2.1-win64\conf\nginx.conf,清空内容并粘贴以下配置。该配置实现了:强制 HTTPS + API Key 校验 + JWT 令牌校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
worker_processes  auto;

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;

# Lua 包路径
lua_package_path "D:/openresty-1.29.2.1-win64/lualib/?.lua;D:/openresty-1.29.2.1-win64/conf/lua/?.lua;;";
lua_package_cpath "D:/openresty-1.29.2.1-win64/lualib/?.dll;;";

# 【全局优化】增加请求体大小,防止模型输入长文本报错
client_max_body_size 50m;

server {
listen 80;
server_name 192.168.11.12;
return 301 https://$host$request_uri;
}

server {
listen 443 ssl;
server_name 192.168.11.12;

ssl_certificate D:/openresty-1.29.2.1-win64/conf/ssl/server.crt;
ssl_certificate_key D:/openresty-1.29.2.1-win64/conf/ssl/server.key;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

# ----------------------------------------------------
# 1. vLLM 模型服务转发 (匹配 /v1 路径,OpenAI 标准接口)
# ----------------------------------------------------
location /v1/ {
# 开启鉴权与白名单
access_by_lua_file D:/openresty-1.29.2.1-win64/conf/lua/auth_jwt.lua;

proxy_pass http://192.168.11.15:10001;

# --- vLLM 关键优化配置 ---
proxy_buffering off; # 必须关闭缓存,支持流式输出(Stream)
proxy_http_version 1.1; # 保持长连接
proxy_set_header Connection "";
proxy_read_timeout 600s; # 调大超时时间,防止长文本推理中断

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}

# ----------------------------------------------------
# 2. 原有的 8080 业务服务转发 (匹配其他所有路径)
# ----------------------------------------------------
location / {
# 同样开启鉴权与白名单
access_by_lua_file D:/openresty-1.29.2.1-win64/conf/lua/auth_jwt.lua;

proxy_pass http://192.168.11.15:8080;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;

# 普通业务通常不需要关闭 buffering,保持默认即可
proxy_buffering on;
}

# ----------------------------------------------------
# 3. 公共路径(不需鉴权)
# ----------------------------------------------------
location /health {
access_by_lua_block { ngx.say("OK"); ngx.exit(200) }
}

location = /favicon.ico {
log_not_found off;
access_log off;
return 204;
}
}
}

第五步:编写auth_jwt.lua

在D:\openresty-1.29.2.1-win64\conf\lua下创建auth_jwt.lua,同时需要更改成自己的秘钥。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
-- ==========================================
-- 生产环境稳妥版:IP 白名单 + JWT 鉴权脚本
-- ==========================================

local jwt = require("resty.jwt")

-- 【配置区 1】JWT 签名秘钥 (必须与签发端一致)
local secret_key = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIiwicm9sZSI6ImFkbWluIiwibmFtZSI6IlRlc3QgVXNlciIsImlhdCI6MTc3NDQyMzE3NiwiZXhwIjoxNzc0NDI2Nzc2fQ.8iEvpWaOEirpgIcK20IPXEQBCGpY84EwNTBPqMwEAoA"

-- 【配置区 2】白名单:完全匹配的特定 IP 地址
local whitelist_ips = {
["127.0.0.1"] = true, -- 本机测试
["192.168.3.35"] = true, -- 示例:某台特定管理机
["192.168.3.91"] = true, -- 示例:某台监控服务器
}

-- 【配置区 3】白名单:网段前缀 (支持 1-2 个网段)
-- 只要客户端 IP 是以这些字符串开头的,就直接放行
local whitelist_prefixes = {
"192.168.13.", -- 示例网段 A:192.168.1.x 全部放行
"10.100.", -- 示例网段 B:10.10.x.x 全部放行
"172.16.5." -- 示例网段 C
}

-- ==========================================
-- 核心逻辑开始
-- ==========================================

-- 1. 获取客户端真实 IP
local client_ip = ngx.var.remote_addr

-- 2. 检查特定 IP 白名单 (完全匹配)
if whitelist_ips[client_ip] then
ngx.log(ngx.INFO, "[Auth] IP 在特定白名单中,放行: ", client_ip)
return
end

-- 3. 检查网段前缀白名单
for _, prefix in ipairs(whitelist_prefixes) do
if string.sub(client_ip, 1, string.len(prefix)) == prefix then
ngx.log(ngx.INFO, "[Auth] IP 属于白名单网段,放行: ", client_ip)
return
end
end

-- 4. 如果 IP 不在白名单,则开始 JWT 强制校验
local auth_header = ngx.var.http_authorization

-- 4.1 检查是否存在 Authorization Header
if not auth_header then
ngx.status = 401
ngx.header.content_type = "application/json; charset=utf-8"
ngx.say('{"error": "Unauthorized", "message": "Missing Authorization Header", "client_ip": "' .. client_ip .. '"}')
ngx.exit(401)
end

-- 4.2 提取 Bearer Token
local _, _, token = string.find(auth_header, "Bearer%s+(.+)")
if not token then
ngx.status = 401
ngx.header.content_type = "application/json; charset=utf-8"
ngx.say('{"error": "Unauthorized", "message": "Invalid Token Format (Bearer token required)"}')
ngx.exit(401)
end

-- 4.3 校验 JWT 签名与有效期
local jwt_obj = jwt:verify(secret_key, token)

if not jwt_obj.verified then
ngx.status = 401
ngx.header.content_type = "application/json; charset=utf-8"
-- jwt_obj.reason 会显示具体的失败原因(如:token expired, signature mismatch)
ngx.say('{"error": "Unauthorized", "message": "JWT Verification Failed", "reason": "' .. (jwt_obj.reason or "unknown") .. '"}')
ngx.exit(401)
end

-- 校验成功:Nginx 会继续转发请求到 proxy_pass

生成秘钥python脚本:

1
pip install PyJWT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/usr/bin/env python3
import jwt
import time
import json

# ⚠️ 这个密钥必须与 auth_jwt.lua 中的 SECRET 完全一致
SECRET = "your-256-bit-secret-key-change-me-12345"

# Token 有效期(秒)
EXPIRY_SECONDS = 3600 # 1 小时

def generate_token(user_id, role="user", name=""):
"""生成 JWT token"""
payload = {
"sub": user_id, # 用户 ID
"role": role, # 用户角色
"name": name, # 用户名
"iat": int(time.time()), # 签发时间
"exp": int(time.time()) + EXPIRY_SECONDS # 过期时间
}

token = jwt.encode(payload, SECRET, algorithm="HS256")
return token

if __name__ == "__main__":
print("=" * 70)
print("JWT Token Generator")
print("=" * 70)

# 生成示例 token
token = generate_token(
user_id="user123",
role="admin",
name="Test User"
)

print("\nToken:\n")
print(token)

print("\n" + "=" * 70)
print("Usage:")
print("=" * 70)

print("\n1. curl test:")
print(f' curl -k -H "Authorization: Bearer {token}" https://localhost/')

print("\n2. PowerShell test:")
print(f' $token = "{token}"')
print(f' curl -k -H "Authorization: Bearer $token" https://localhost/')

print("\n" + "=" * 70)
print("Token Info:")
print("=" * 70)

# 解码显示 payload(不验证签名)
decoded = jwt.decode(token, options={"verify_signature": False})
print(json.dumps(decoded, indent=2, ensure_ascii=False))

print(f"\nExpiry: {EXPIRY_SECONDS} seconds ({EXPIRY_SECONDS // 60} minutes)")
print("=" * 70)

配置完成后启动D:\openresty-1.29.2.1-win64中的nginx即可。

The End

 评论
评论插件加载失败
正在加载评论插件
$icon-size = 1.2rem $search-header-height = 3rem .search-pop-overlay { position fixed top 0 left 0 z-index $z-index-8 display flex width 100% height 100% background rgba(0, 0, 0, 0) visibility hidden transition-t("visibility, background", "0, 0", "0.3, 0.3", "ease, ease") &.active { background rgba(0, 0, 0, 0.35) visibility visible .search-popup { transform scale(1) } } .search-popup { z-index $z-index-6 width 70% height 80% margin auto background var(--background-color-1) border-radius 0.4rem transform scale(0) transition-t("transform", "0", "0.3", "ease") +keep-tablet() { width 80% } +keep-mobile() { width 90% } .search-header { display flex align-items center height $search-header-height padding 0 1rem background var(--text-color-6) border-top-left-radius 0.2rem border-top-right-radius 0.2rem .search-input-field-pre { margin-right 0.2rem color var(--text-color-3) font-size 1.3rem cursor pointer } .search-input-container { flex-grow 1 padding 0.2rem .search-input { width 100% color var(--text-color-3) font-size 1.2rem background transparent border 0 outline 0 &::-webkit-search-cancel-button { display none } &::-webkit-input-placeholder { color var(--text-color-4) font-size 1rem } } } .close-popup-btn { color var(--text-color-3) font-size $icon-size cursor pointer &:hover { color var(--text-color-1) } } } #search-result { position relative display flex box-sizing border-box height 'calc(100% - %s)' % $search-header-height padding 0.3rem 1.5rem overflow auto .search-result-list { width 100% height 100% font-size 1rem li { box-sizing border-box margin 0.8rem 0 padding 0.8rem 0 border-bottom 0.1rem dashed var(--border-color) &:last-child { border-bottom none } .search-result-title { position relative display flex align-items center margin-bottom 0.8rem padding-left 1rem font-weight bold &::after { position absolute top 50% left 0 width 0.4rem height 0.4rem background var(--text-color-3) border-radius 50% transform translateY(-50%) content '' } } .search-result { margin 0 padding-left 1rem line-height 2rem word-wrap break-word } a { &:hover { color var(--text-color-3) } } .search-keyword { color var(--primary-color) font-weight bold border-bottom 0.1rem dashed var(--primary-color) } } } #no-result { margin auto color var(--text-color-4) } } } }