实战:用 Lua 手写 Kong 安全鉴权插件并通过 ArgoCD GitOps 零编译部署到 DbGate 服务
在跨云和混合云架构中,暴露在公网或内网边界的管理控制台(如数据库 Web 客户端 DbGate)如果缺乏原生身份认证,极易成为安全攻击面。开源版 Kong Gateway(KIC)虽然拥有强大的插件生态,但在面对某些特殊的业务流转、轻量鉴权或单页应用(SPA)特有时,自己手写一个轻量且高性能的 Lua 插件往往是最干净、最自由的选择。
本文将以我们在混合云 Kubernetes(K3s)集群中为 DbGate 部署自定义 Basic-Auth 鉴权网关的实战为例,完整记录从 Lua 插件底层逐行编写 、单页应用(SPA)鉴权头碰撞与 Cookie 会话穿透踩坑修复 ,到 纯 GitOps(ArgoCD)免 Docker 编译声明式挂载上线 的全流程。
1. 架构总览与流量拓扑
在这套体系中,我们坚持 "零镜像编译、纯声明式 GitOps、配置与代码同仓" 的设计哲学。Lua 代码不打进任何 Docker 镜像,而是直接作为 Kubernetes ConfigMap 托管在 GitOps 仓库中,由 ArgoCD 自动同步并由 Kong Helm 自动挂载至网关 DaemonSet 节点。
1.1 核心流量与鉴权状态机流程图
DbGate 后端容器 (Node/Express) custom-auth (Lua 插件) Kong 网关 (Nginx/LuaJIT) DbGate 后端容器 (Node/Express) custom-auth (Lua 插件) Kong 网关 (Nginx/LuaJIT) #mermaid-svg-dZofSCkZDopV7ICe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dZofSCkZDopV7ICe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dZofSCkZDopV7ICe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dZofSCkZDopV7ICe .error-icon{fill:#552222;}#mermaid-svg-dZofSCkZDopV7ICe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dZofSCkZDopV7ICe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dZofSCkZDopV7ICe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dZofSCkZDopV7ICe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dZofSCkZDopV7ICe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dZofSCkZDopV7ICe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dZofSCkZDopV7ICe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dZofSCkZDopV7ICe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dZofSCkZDopV7ICe .marker.cross{stroke:#333333;}#mermaid-svg-dZofSCkZDopV7ICe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dZofSCkZDopV7ICe p{margin:0;}#mermaid-svg-dZofSCkZDopV7ICe .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dZofSCkZDopV7ICe text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-dZofSCkZDopV7ICe .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-dZofSCkZDopV7ICe .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-dZofSCkZDopV7ICe .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-dZofSCkZDopV7ICe .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-dZofSCkZDopV7ICe #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-dZofSCkZDopV7ICe .sequenceNumber{fill:white;}#mermaid-svg-dZofSCkZDopV7ICe #sequencenumber{fill:#333;}#mermaid-svg-dZofSCkZDopV7ICe #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-dZofSCkZDopV7ICe .messageText{fill:#333;stroke:none;}#mermaid-svg-dZofSCkZDopV7ICe .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dZofSCkZDopV7ICe .labelText,#mermaid-svg-dZofSCkZDopV7ICe .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-dZofSCkZDopV7ICe .loopText,#mermaid-svg-dZofSCkZDopV7ICe .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-dZofSCkZDopV7ICe .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-dZofSCkZDopV7ICe .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-dZofSCkZDopV7ICe .noteText,#mermaid-svg-dZofSCkZDopV7ICe .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-dZofSCkZDopV7ICe .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dZofSCkZDopV7ICe .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dZofSCkZDopV7ICe .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dZofSCkZDopV7ICe .actorPopupMenu{position:absolute;}#mermaid-svg-dZofSCkZDopV7ICe .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-dZofSCkZDopV7ICe .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dZofSCkZDopV7ICe .actor-man circle,#mermaid-svg-dZofSCkZDopV7ICe line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-dZofSCkZDopV7ICe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 3. 浏览器捕获 401 信号,弹出原生账号密码登录框 5. 解码 Base64 比对 user/password 成功 后续后台 AJAX / WebSocket 请求生命周期 (关键踩坑点) 10. 命中 ngx.var.cookie_dbgate_session == AUTH_TOKEN 用户浏览器 (Chrome) 1. 发起请求 GET /dbgate/ (未带凭据)1执行 access 阶段拦截22. 返回 401 Unauthorized + WWW-Authenticate 头34. 提交凭据 GET /dbgate/ (带 Authorization: Basic ...)4执行 access 阶段校验56. 注入 Set-Cookie: dbgate_session=...67. 放行流量请求静态页面78. 返回 HTML & 渲染 SPA 前端脚本89. SPA 发送 POST /dbgate/server-connections/ping (带 Cookie + 内部 Token)9执行 access 阶段1011. 0.01ms 无条件放行 (透传 DbGate 内部 Header)1112. 转发请求至真实服务1213. 返回 200 OK 数据库状态13 用户浏览器 (Chrome)
2. 插件代码逐行解析
Kong 3.x 插件的最小骨架由两个核心文件构成:
schema.lua:定义插件元数据与配置参数规格(Schema Validation);handler.lua:编写具体的网关生命周期拦截与业务逻辑。
2.1 schema.lua:参数字典与数据规范
lua
local typedefs = require "kong.db.schema.typedefs"
return {
name = "custom-auth", -- 插件在 Kong 系统中注册的唯一标识名称
fields = {
{ consumer = typedefs.no_consumer }, -- 声明该插件无需绑定特定 Consumer 实体
{ protocols = typedefs.protocols_http }, -- 仅作用于 HTTP/HTTPS 协议流量
{ config = {
type = "record", -- 插件接收的参数集合定义为 record 类型
fields = {
{ username = { type = "string", required = true } }, -- 必须提供字符串类型的 username
{ password = { type = "string", required = true } }, -- 必须提供字符串类型的 password
},
}, },
},
}
- 第 1 行 :加载 Kong 内置的常用模式定义
typedefs; - 第 4 行 :
name = "custom-auth",必须与我们在 Kubernetes 中使用的KongPlugin.plugin名字严格一致; - 第 6 行 :
no_consumer意味着该插件是路由/服务级的全局门禁,不需要在数据库里提前创建 Consumer 记录; - 第 10-13 行 :声明了我们在
KongPluginCRD 里填写的config.username和config.password参数校验规则。
2.2 handler.lua:核心拦截、鉴权与 Session Cookie 注入
这是整个插件的灵魂。在第一版实现中,我们仅校验了 Authorization: Basic ... 头,结果导致单页应用(DbGate)在加载完成后发起的后台 AJAX 请求被二次拦截(详见第 4 节踩坑复盘)。最终完善的 handler.lua 实现了 "Cookie 优先无损放行 + Basic Auth 自动颁发会话" 的双重机制:
lua
local ngx = ngx
local kong = kong
-- 1. 定义插件对象并声明优先级与版本
local CustomAuth = {
PRIORITY = 1000, -- 设置高优先级(1000),确保在大多数官方插件和反向代理前优先执行
VERSION = "1.2.0",
}
-- 定义内部信任的 Session 校验令牌
local AUTH_TOKEN = "dbgate_session_ok_2026"
function CustomAuth:access(conf)
-- 2. 优先检查浏览器是否已持有有效的 Session Cookie
-- 使用 Nginx 底层原生变量提取 Cookie,支持单值、多值和复杂 Cookie 串
local cookie_val = ngx.var.cookie_dbgate_session
if cookie_val == AUTH_TOKEN then
return -- 已持有有效会话凭证,0.01 毫秒内无条件直接放行!
end
-- 3. 若无 Cookie,则检查请求头中是否存在 HTTP Basic Authorization
local auth_header = ngx.var.http_authorization or kong.request.get_header("authorization")
if type(auth_header) == "table" then
auth_header = auth_header[1] -- 处理极端情况下多重 Header 数组的问题
end
-- 4. 判定是否携带标准的 Basic 前缀
if auth_header and auth_header:find("^Basic ") then
local token = auth_header:sub(7) -- 截取 "Basic " 之后的 Base64 编码串
local decoded = ngx.decode_base64(token) -- 执行 Base64 解码
if decoded then
-- 匹配冒号前后的 username 与 password
local user, pass = decoded:match("([^:]+):(.*)")
if user == conf.username and pass == conf.password then
-- 5. 校验通过:顺手向响应头注入 Session Cookie,使 SPA 页面后续后台请求完全放行
kong.response.set_header("Set-Cookie", "dbgate_session=" .. AUTH_TOKEN .. "; Path=/; HttpOnly; SameSite=Lax")
kong.log.info("用户 [", user, "] 成功通过 DbGate 认证并颁发 Session Cookie!")
return -- 放行流量进入 Upstream 后端
end
end
end
-- 6. 若凭据缺失或账号密码不匹配:记录告警日志并向浏览器发送 401
kong.log.warn("未授权访问拦截:客户端 IP [", kong.client.get_ip(), "] 访问 DbGate 被拒绝!")
kong.response.set_header("WWW-Authenticate", 'Basic realm="DbGate Secure Console"')
return kong.response.exit(401, { message = "Unauthorized: 请提供有效的账号密码以访问 DbGate!" })
end
return CustomAuth
3. GitOps(ArgoCD)纯声明式部署链路
代码编写完毕后,如何将其无感地注入到 Kubernetes 中?我们采用 ConfigMap 挂载法,全程不需要任何 CI 构建或 Docker 镜像制作。
3.1 GitOps 资源拓扑架构
#mermaid-svg-VdPWDImXz7BTJeEt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VdPWDImXz7BTJeEt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VdPWDImXz7BTJeEt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VdPWDImXz7BTJeEt .error-icon{fill:#552222;}#mermaid-svg-VdPWDImXz7BTJeEt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VdPWDImXz7BTJeEt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VdPWDImXz7BTJeEt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VdPWDImXz7BTJeEt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VdPWDImXz7BTJeEt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VdPWDImXz7BTJeEt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VdPWDImXz7BTJeEt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VdPWDImXz7BTJeEt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VdPWDImXz7BTJeEt .marker.cross{stroke:#333333;}#mermaid-svg-VdPWDImXz7BTJeEt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VdPWDImXz7BTJeEt p{margin:0;}#mermaid-svg-VdPWDImXz7BTJeEt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VdPWDImXz7BTJeEt .cluster-label text{fill:#333;}#mermaid-svg-VdPWDImXz7BTJeEt .cluster-label span{color:#333;}#mermaid-svg-VdPWDImXz7BTJeEt .cluster-label span p{background-color:transparent;}#mermaid-svg-VdPWDImXz7BTJeEt .label text,#mermaid-svg-VdPWDImXz7BTJeEt span{fill:#333;color:#333;}#mermaid-svg-VdPWDImXz7BTJeEt .node rect,#mermaid-svg-VdPWDImXz7BTJeEt .node circle,#mermaid-svg-VdPWDImXz7BTJeEt .node ellipse,#mermaid-svg-VdPWDImXz7BTJeEt .node polygon,#mermaid-svg-VdPWDImXz7BTJeEt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VdPWDImXz7BTJeEt .rough-node .label text,#mermaid-svg-VdPWDImXz7BTJeEt .node .label text,#mermaid-svg-VdPWDImXz7BTJeEt .image-shape .label,#mermaid-svg-VdPWDImXz7BTJeEt .icon-shape .label{text-anchor:middle;}#mermaid-svg-VdPWDImXz7BTJeEt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VdPWDImXz7BTJeEt .rough-node .label,#mermaid-svg-VdPWDImXz7BTJeEt .node .label,#mermaid-svg-VdPWDImXz7BTJeEt .image-shape .label,#mermaid-svg-VdPWDImXz7BTJeEt .icon-shape .label{text-align:center;}#mermaid-svg-VdPWDImXz7BTJeEt .node.clickable{cursor:pointer;}#mermaid-svg-VdPWDImXz7BTJeEt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VdPWDImXz7BTJeEt .arrowheadPath{fill:#333333;}#mermaid-svg-VdPWDImXz7BTJeEt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VdPWDImXz7BTJeEt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VdPWDImXz7BTJeEt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VdPWDImXz7BTJeEt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VdPWDImXz7BTJeEt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VdPWDImXz7BTJeEt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VdPWDImXz7BTJeEt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VdPWDImXz7BTJeEt .cluster text{fill:#333;}#mermaid-svg-VdPWDImXz7BTJeEt .cluster span{color:#333;}#mermaid-svg-VdPWDImXz7BTJeEt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VdPWDImXz7BTJeEt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VdPWDImXz7BTJeEt rect.text{fill:none;stroke-width:0;}#mermaid-svg-VdPWDImXz7BTJeEt .icon-shape,#mermaid-svg-VdPWDImXz7BTJeEt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VdPWDImXz7BTJeEt .icon-shape p,#mermaid-svg-VdPWDImXz7BTJeEt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VdPWDImXz7BTJeEt .icon-shape .label rect,#mermaid-svg-VdPWDImXz7BTJeEt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VdPWDImXz7BTJeEt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VdPWDImXz7BTJeEt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VdPWDImXz7BTJeEt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 腾讯云 / OCI / NUC 业务集群
Aliyun ArgoCD 控制面
应用清单
Helm 挂载注入
注解绑定
挂载至 /opt/kong/plugins/
git push
my-argocd-manifests 仓库
infrastructure/kong-gateway/custom-auth-plugin.yaml
KongPlugin: dbgate-auth-plugin
argocd-apps/kong-controller-app.yaml
argocd-apps/dbgate-app.yaml
ArgoCD 同步调度引擎
ConfigMap: kong-plugin-custom-auth
Kong Gateway DaemonSet Pods
Service: dbgate with annotations
3.2 第一步:将 Lua 源码包装为 ConfigMap 与 KongPlugin
在 Git 仓库 infrastructure/kong-gateway/custom-auth-plugin.yaml 中声明:
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kong-plugin-custom-auth
namespace: kong-system # 必须与 Kong Ingress Controller 部署在同一命名空间
data:
schema.lua: |
# ... 上述 schema.lua 代码 ...
handler.lua: |
# ... 上述 handler.lua 代码 ...
---
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: dbgate-auth-plugin
namespace: default # 部署在业务应用所在的命名空间
plugin: custom-auth # 对应 schema.lua 中定义的插件名字
config:
username: "jason"
password: "Hsbc1234!"
3.3 第二步:在 Kong Helm 中声明加载此插件
在 argocd-apps/kong-controller-app.yaml 的 Helm values 中加入 3 行声明:
yaml
plugins:
configMaps:
- pluginName: custom-auth # 插件在 Kong 运行时的名字
name: kong-plugin-custom-auth # 上一步创建的 ConfigMap 名称
底层生效原理 :
Kong 官方 Helm Chart 检测到 plugins.configMaps 后,会自动完成两件事:
- 在 Pod 的
volumeMounts中把该 ConfigMap 挂载到容器的/opt/kong/plugins/custom-auth/; - 自动在 Kong 容器的环境变量追加
KONG_PLUGINS=bundled,custom-auth。
Kong 启动时便会自动扫描并加载该 Lua 模块。
3.4 第三步:将插件绑定到 DbGate 服务
在 argocd-apps/dbgate-app.yaml 中,为 DbGate 的 Service 加上核心注解:
yaml
service:
type: ClusterIP
port: 80
annotations:
konghq.com/plugins: dbgate-auth-plugin # 🎯 声明由该插件接管流量
4. 关键避坑复盘:单页应用(SPA)后台 AJAX 导致的"二次密码弹窗"
在本次实战中,我们遭遇并解决了一个非常具有代表性的安全协议与前端框架碰撞问题:为什么输入了正确的账号密码后,页面能打开,但却反复弹出第二次密码输入框?
4.1 故障根因还原
- 第一次弹窗(正常) :
- 浏览器打开
http://gw.jpgcp.cloud/dbgate/,Kong 拦截并返回 401 +WWW-Authenticate; - 用户在弹窗中输入
jason / Hsbc1234!,浏览器发送带 Basic Auth 的请求,Kong 验证通过,成功返回 HTML 页面。
- 浏览器打开
- 第二次弹窗的元凶(协议头踩踏) :
- DbGate 是基于 Svelte/React 构建的单页富应用(SPA)。页面渲染完成后,前端 JS 会自动向后台发起 API 调用(如
POST /dbgate/server-connections/ping)以维护数据库状态; - DbGate 框架自身也使用 HTTP
Authorization头传递内部会话 Token; - 当前端 JS 发送自己的
Authorization: <internal_token>时,旧版 Kong 插件捕获到该请求,发现其内容不是Basic jason:Hsbc1234!,误判定为"密码错误",再次返回 401; - 浏览器接收到后台 API 返回的 401 信号,误以为用户刚才提交的密码失效,因此强制弹出第二次登录窗口!
- DbGate 是基于 Svelte/React 构建的单页富应用(SPA)。页面渲染完成后,前端 JS 会自动向后台发起 API 调用(如
4.2 终极解决方案:Cookie 优先白名单机制
针对 SPA 的这一天然特性,我们在 v1.2.0 的 handler.lua 中重构了判定优先级:
- 浏览器初次访问 :走标准 HTTP Basic 认证,校验成功后立即在 HTTP Response 中写入
Set-Cookie: dbgate_session=...; Path=/; HttpOnly; SameSite=Lax; - SPA 后续请求 :浏览器发起的任何
fetch/WebSocket/XHR请求都会自动携带该 Cookie。Kong 插件通过ngx.var.cookie_dbgate_session优先识别 Cookie 并毫秒级放行 ,绝不干涉或误杀 DbGate 自身传递的内部AuthorizationToken。
5. 验收与实测验证
完成 GitOps 提交推送后,我们通过命令行和真实浏览器进行了三场景全链路验收:
5.1 场景一:未授权探测(模拟爬虫或首次访问)
bash
$ curl -s -i "http://gw.jpgcp.cloud/dbgate/"
HTTP/1.1 401 Unauthorized
Server: kong/3.6.1
Www-Authenticate: Basic realm="DbGate Secure Console"
Content-Type: application/json; charset=utf-8
{"message":"Unauthorized: 请提供有效的账号密码以访问 DbGate!"}
(验证通过:网关在边缘层即时阻断,0 字节非法流量进入内网宿主机)
5.2 场景二:错误凭据尝试
bash
$ curl -s -i -u jason:wrongpassword "http://gw.jpgcp.cloud/dbgate/"
HTTP/1.1 401 Unauthorized
{"message":"Unauthorized: 用户名或密码错误!"}
(验证通过:网关准确识别错误凭证并拦截)
5.3 场景三:正确凭据登录与 Cookie 会话保持
bash
$ curl -s -i -u jason:Hsbc1234! "http://gw.jpgcp.cloud/dbgate/"
HTTP/1.1 200 OK
Via: kong/3.6.1
Set-Cookie: dbgate_session=dbgate_session_ok_2026; Path=/; HttpOnly; SameSite=Lax
Content-Type: text/html; charset=utf-8
<!DOCTYPE html>
<html lang="en">
<head>
<title>DbGate</title>
(验证通过:成功返回 200 OK 并签发 Session Cookie,浏览器端实现一次登录、全站永久流畅操作)
6. 总结与最佳实践
- 轻量规则首选 Lua 插件:对于简单的 Basic Auth、专用 Header 校验、Token 转发重写等需求,手写 30 行 Lua 插件配合 ConfigMap 挂载是资源消耗最小(仅几 KB 内存)、性能最高(0.01ms 级)的方案;
- SPA 应用鉴权切记 Cookie 兜底 :在为任何前端单页应用(SPA)增加网关层 Basic Auth 时,务必在首次鉴权通过后签发 Session Cookie,避免网关与前端自定义的
Authorization头产生协议冲突; - 彻底贯彻 GitOps 交付:将 Kong 插件代码、KongPlugin 资源实体与应用 Service 注解收敛在同一个 GitOps 仓库中,实现一键变更、全集群节点秒级热加载生效与历史回滚。