目录
网关统一认证中心的背景与需求
在微服务架构中,网关是统一入口,负责路由、认证、限流等功能。统一认证中心是网关的核心组件,用于验证客户端请求的合法性(通常通过 token)。本教程的目标是使用 OpenResty 实现一个高效的认证中心,满足以下需求:
- token 存储:利用 Nginx 的共享内存存储 token,确保高性能查找。
- token 推送接口:提供
/api/v1/tokens
接口,接受认证后的 token 并存入共享内存。 - token 验证:在请求处理阶段检查 header 中的 token,若有效则代理到后端,否则返回 401。
- 高并发优化:解决多个 Nginx Worker 对共享内存的竞争问题。
- 扩展性:提供优化建议,支持分布式部署和复杂场景。
OpenResty 的非阻塞架构和 Lua 的轻量高效使其非常适合实现这一功能。接下来,我们将逐一讲解实现步骤。
OpenResty 的关键技术点
在实现认证中心之前,了解以下 OpenResty 技术点将帮助你更好地理解代码:
- Nginx 共享内存 (
ngx.shared
):Nginx 提供共享内存字典,允许多个 Worker 进程共享数据,适合存储 token。 - Lua 脚本阶段:
access_by_lua*
适合认证检查,content_by_lua*
适合处理 API 请求。 - Nginx 代理:通过
proxy_pass
将验证通过的请求转发到后端服务器。 - LuaJIT:OpenResty 使用 LuaJIT 编译 Lua 脚本,性能接近 C 语言。
- 非阻塞 I/O:OpenResty 的事件驱动模型确保高并发处理能力。
实现步骤:构建认证中心
步骤 1:配置 Nginx 共享内存存储 token
作用与原理
Nginx 的共享内存通过 lua_shared_dict
指令定义,允许多个 Worker 进程访问同一块内存区域。我们将使用它存储 token,格式为 key: token 值, value: 用户信息或其他元数据
。
- 适用场景:存储高频访问的 token 数据,减少外部数据库查询。
- 注意事项:共享内存大小固定,需合理规划;操作非线程安全,需注意并发竞争。
配置共享内存
在 Nginx 配置文件(nginx.conf
)的 http
块中添加以下指令:
|
|
说明:
tokens
是共享内存的名称,10m
表示分配 10MB 内存。- 可根据 token 数量调整大小(每个 token 约占用几 KB)。
初始化共享内存
在 init_by_lua*
阶段初始化共享内存(可选,用于预加载数据):
|
|
说明:
ngx.shared.tokens
是共享内存对象。set
方法存储 key-value 对,value 可包含用户 ID、过期时间等。
步骤 2:实现 /api/v1/tokens 接口存储 token
作用与原理
/api/v1/tokens
是一个 POST 接口,接受外部系统推送的认证后 token。Lua 脚本解析请求体,将 token 存入共享内存。
- 适用场景:外部认证服务(如 OAuth 服务器)推送 token。
- 注意事项:需验证请求来源,防止未授权推送;建议设置 token 过期时间。
Nginx 配置
在 nginx.conf
的 server
块中添加 location:
|
|
Lua 脚本 (push_token.lua
)
实现 token 推送逻辑:
|
|
代码说明:
- 使用
cjson
解析 POST 请求的 JSON 体,期望包含token
、user_info
和expire
字段。 - 验证字段完整性,防止无效数据。
- 使用
ngx.shared.tokens:set
存储 token,value 为 JSON 编码的用户信息和过期时间。 - 返回 JSON 响应,指示成功或失败。
示例请求:
|
|
预期响应:
|
|
步骤 3:在 access_by_lua* 阶段验证 token
作用与原理
access_by_lua*
是访问控制阶段,适合检查请求的认证状态。我们将检查 header 中的 Authorization
字段(如 Bearer xyz123
),验证 token 是否存在于共享内存。
- 适用场景:拦截未认证请求,确保只有合法用户访问后端。
- 注意事项:快速执行,减少延迟;支持 token 过期检查。
Nginx 配置
在 nginx.conf
的 location
块中添加 access_by_lua_file
:
|
|
Lua 脚本 (check_token.lua
)
实现 token 验证逻辑:
|
|
代码说明:
- 检查
Authorization
header,提取Bearer
token。 - 使用
tokens:get
查询共享内存,验证 token 存在性。 - 解析存储的 JSON 数据,检查
expire
字段是否过期。 - 若认证通过,设置
ngx.var.user_info
供后端使用;否则返回 401。
示例请求:
|
|
预期响应(认证失败):
|
|
步骤 4:代理请求到后端服务器
作用与原理
认证通过后,OpenResty 使用 proxy_pass
将请求转发到后端服务器。可以通过 Nginx 的 upstream
模块定义后端服务器集群。
- 适用场景:将合法请求路由到微服务。
- 注意事项:确保后端服务器支持用户信息的传递(如通过 header)。
Nginx 配置
定义 upstream
和 proxy_pass
:
|
|
说明:
upstream backend
定义后端服务器集群。proxy_set_header
将user_info
传递给后端。proxy_pass
转发请求到backend
。
高并发场景下的共享内存优化
问题分析
Nginx 的共享内存(ngx.shared
)在多 Worker 进程下是非线程安全的,多个 Worker 可能同时读写同一 key,导致竞争问题。在高并发场景下(如每秒数万请求),可能出现以下问题:
- 锁竞争:共享内存操作涉及内部锁,高并发下锁竞争会导致性能下降。
- 内存溢出:若 token 数量过多,共享内存可能耗尽。
- 数据一致性:快速读写可能导致数据不一致(如覆盖有效 token)。
优化策略
以下是针对共享内存竞争的优化方案:
-
减少写操作:
- 批量写入:在
/api/v1/tokens
接口中,允许批量推送 token,减少单次写操作。 - 异步写入:使用
ngx.timer
将 token 写入操作放入后台任务,降低主请求路径的竞争。 - 示例(异步写入):
1 2 3 4 5 6 7
local function async_store_token(token, value) local tokens = ngx.shared.tokens tokens:set(token, value) end -- 在 push_token.lua 中 ngx.timer.at(0, async_store_token, token, cjson.encode({user_info = user_info, expire = expire}))
- 批量写入:在
-
使用外部存储(如 Redis):
- 场景:当 token 数量较大或需要分布式部署时,使用 Redis 替代共享内存。
- 实现:引入
lua-resty-redis
模块,存储 token 到 Redis。 - 示例:
1 2 3 4 5 6 7 8
local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) local ok, err = red:connect("127.0.0.1", 6379) if ok then red:set("token:" .. token, value) red:expire("token:" .. token, 86400) -- 过期时间 1 天 end
-
分区共享内存:
- 场景:将 token 分片存储到多个共享内存区域,减少单一字典的竞争。
- 实现:定义多个
lua_shared_dict
(如tokens_1
、tokens_2
),根据 token 的哈希值选择存储区域。 - 示例配置:
1 2
lua_shared_dict tokens_1 10m; lua_shared_dict tokens_2 10m;
1 2 3 4
local function get_dict(token) local hash = ngx.crc32_short(token) return hash % 2 == 0 and ngx.shared.tokens_1 or ngx.shared.tokens_2 end
-
缓存热点 token:
- 场景:高频访问的 token 可缓存到 Worker 本地 Lua 表,减少共享内存查询。
- 实现:在
init_worker_by_lua*
初始化本地缓存,定期同步。 - 示例:
1 2 3 4 5 6 7 8 9 10
init_worker_by_lua_block { _G.local_cache = {} } -- 在 check_token.lua 中 local token = auth_header:match("^Bearer%s+(.+)$") local value = _G.local_cache[token] or tokens:get(token) if value then _G.local_cache[token] = value -- 更新本地缓存 end
-
限流写操作:
- 场景:限制
/api/v1/tokens
接口的请求速率,减少共享内存写压力。 - 实现:在
access_by_lua*
中使用lua-resty-limit-req
模块。 - 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
lua_shared_dict limit_req 10m; location /api/v1/tokens { access_by_lua_block { local limit_req = require "resty.limit.req" local lim, err = limit_req.new("limit_req", 100, 50) -- 100 req/s, burst 50 local delay, err = lim:incoming("tokens_api", true) if not delay then ngx.status = ngx.HTTP_TOO_MANY_REQUESTS ngx.say(cjson.encode({error = "Rate limit exceeded"})) return ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS) end if delay > 0 then ngx.sleep(delay) end } content_by_lua_file /path/to/lua/push_token.lua; }
- 场景:限制
其他优化与建议
-
token 过期管理:
- 定期清理过期 token,防止共享内存溢出。
- 实现:在
timer_by_lua*
中运行清理任务。 - 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
init_by_lua_block { local function clean_expired() local tokens = ngx.shared.tokens for _, token in ipairs(tokens:get_keys()) do local value = tokens:get(token) local data = cjson.decode(value) if data.expire < os.date("%Y-%m-%d") then tokens:delete(token) end end ngx.timer.at(3600, clean_expired) -- 每小时运行 end ngx.timer.at(0, clean_expired) }
-
分布式部署:
- 使用 Redis 或数据库作为中央存储,支持多实例网关。
- 部署 Nginx 集群,结合 DNS 或负载均衡器分发流量。
-
安全加固:
- 对
/api/v1/tokens
接口添加 IP 白名单或 API 密钥验证。 - 使用 HTTPS 保护 token 传输。
- 示例(IP 白名单):
1 2 3 4 5
location /api/v1/tokens { allow 192.168.1.0/24; deny all; content_by_lua_file /path/to/lua/push_token.lua; }
- 对
-
日志与监控:
- 在
log_by_lua*
记录认证失败的请求,发送到外部系统(如 ELK)。 - 使用 Prometheus 和 Grafana 监控共享内存使用率和认证性能。
- 在
-
错误处理:
- 为所有 Lua 脚本添加错误捕获(
pcall
),防止脚本异常导致服务中断。 - 示例:
1 2 3 4 5 6 7 8 9
local ok, err = pcall(function() -- 脚本逻辑 end) if not ok then ngx.log(ngx.ERR, "Lua error: ", err) ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR ngx.say(cjson.encode({error = "Internal server error"})) return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end
- 为所有 Lua 脚本添加错误捕获(
完整 Nginx 配置文件与 Lua 脚本
Nginx 配置文件 (nginx.conf
)
|
|
Lua 脚本 (push_token.lua
)
|
|
Lua 脚本 (check_token.lua
)
|
|
部署与测试
部署步骤
- 安装 OpenResty:从 openresty.org 下载并安装,支持 Lua 模块。
- 配置 Nginx:将上述
nginx.conf
保存到 OpenResty 的配置目录。 - 创建 Lua 脚本:将
push_token.lua
和check_token.lua
保存到指定路径。 - 启动服务:运行
openresty -t
检查配置,执行openresty
启动。 - 测试接口:
- 推送 token:
curl -X POST http://auth.example.com/api/v1/tokens -H "Content-Type: application/json" -d '{"token":"xyz123","user_info":"user_id:1001","expire":"2025-12-31"}'
- 验证请求:
curl http://auth.example.com/ -H "Authorization: Bearer xyz123"
- 推送 token:
测试预期
- 推送 token 成功,返回
{"message":"Token stored successfully"}
。 - 使用有效 token 访问,请求被代理到后端。
- 使用无效 token 访问,返回 401 和
{"error":"Invalid or expired token"}
。
总结
通过 OpenResty 和 Lua,我们实现了一个高效的网关统一认证中心,利用共享内存存储 token,提供了 /api/v1/tokens
接口和 token 验证功能。针对高并发场景,我们提出了异步写入、Redis 替代、分区存储等优化策略,确保系统在高负载下的稳定性。本教程的代码示例简单易懂,适合初学者快速上手,同时为进阶用户提供了丰富的优化思路。
评论 0