网关转发

功能介绍

网关转发是飞鸟快验的一个扩展功能,定位是只做鉴权的透明反向代理:

你的业务系统(比如私有api功能系统,或者任何需要用户身份的独立服务)只监听内网,客户端所有请求先打到飞鸟快验的 /wangGuan/ 路径,快验完成 Token 鉴权后,给请求签发一个 JWT(携带用户身份)再原样转发给你的业务服务。业务服务验一下 JWT 签名就能拿到用户身份,不需要对接任何快验接口,不需要管账号体系,不用写登录。 相比使用公共函数内进行转发请求,使用更简单,性能更高.

三方关系总览

客户端 (公网)
你的 App / 软件 / 网页, 持有快验登录 Token
↓ ① 请求 /wangGuan/业务路径
带 Token + WangGuanId
↑ ⑥ 业务响应原样回传
网关不读不改
飞鸟快验网关 (公网 18888)
鉴权 Token → 签发 JWT → 转发, 不读不加解密业务数据
↓ ② 替换身份头 x-wg-jwt / x-wg-ip
③ 去掉前缀, 内网转发
↑ ④ ⑤ 业务响应
业务服务器 (只监听内网 127.0.0.1)
验签 JWT 得到用户身份, 不写登录、不对接快验

怎么看这张图

客户端永远只和网关(公网)打交道;网关和业务服务器之间走内网转发;业务服务器不暴露公网,别人无法绕过网关直连,只能经网关鉴权后访问。

请求流程

1
客户端发起请求
POST /wangGuan/业务路径, Header 加入 Token(快验令牌)、WangGuanId(网关Id)
2
网关鉴权 Token
校验 Token 有效性(内存缓存, 0 查库)
✗ 鉴权失败(Token无效 / 网关禁用 / 网关不存在) → 不转发, 流程结束: 直接返回 空 body + HTTP状态码 + 响应头 x-wg-error(中文原因)
3
签发 JWT
Token 有效, 签发带用户身份的 JWT(uid / user / vipTime / rmb 等)
4
替换身份头
强制替换 x-wg-jwt / x-wg-ip, 剔除 Token / WangGuanId(防伪造)
5
原样转发到业务服务器
去掉 /wangGuan 前缀, 请求原样转发到业务服务器(内网, 如 127.0.0.1:18899)
6
业务响应原样回传
业务服务器的响应原样回传, 网关不读不改
✓
通讯完成
客户端收到业务原样响应; 只要不是 网关错误状态码, 都是业务服务器的正常响应

核心特点:

特点说明
哑管道网关不解析业务报文、不加解密、不记日志、不限流,加解密和协议由客户端与业务服务器自行协商
零查库转发网关配置与 Token 鉴权全部内存缓存,转发热路径 0 数据库查询
支持 HTTP 和 WebSocketWS 握手走同一套鉴权,握手成功后网关只做字节透传,连接归业务服务器
每网关独立密钥每个网关 Id 一个独立 Secret 签发 JWT,不同业务的 JWT 互不相认,业务之间天然隔离
防伪造x-wg-jwt、x-wg-ip 两个身份头每个请求无条件强制替换,客户端伪造不了

WARNING

网关是"新增"功能,与现有 软件用户Api(/Api)、WebSocket(/ws/:Token)、云函数 完全互不影响,旧客户端不需要任何改动。只为自有api快速对接该飞鸟快验用户管理

管理后台:创建网关

管理后台 → 二开扩展 → 网关转发,新增网关:

字段说明
网关名称仅后台管理用,如"短信系统"
业务Url业务服务根地址,必须 http:// 或 https:// 开头,如 http://127.0.0.1:18899
状态启用/禁用。业务出事故时切"禁用"秒级止血,客户端收到明确的 403 而不是超时挂死
响应超时秒,只限制"等待业务响应头"的阶段,流式响应/大文件不受限(默认60)
SecretJWT 签发密钥。新增时自动生成一个 64 位随机密钥(可直接改),复制保存并配置到业务服务器

列表页操作说明:

  • 编辑:修改名称/Url/状态/超时/Secret(明文可见,可改可重新随机生成)。修改 Secret 保存后立即生效,业务服务器需同步更新,否则验签失败
  • 连通测试:网关侧直接探测业务 Url,显示是否可达、状态码、耗时
  • 删除:批量或单个删除,不影响客户端 Token

更换 Secret

网关是单 Secret 设计:重新生成或修改 Secret 立即生效,没有过渡期。更换前请先在业务服务器做好同步准备,网关侧保存后立刻把新 Secret 配置到业务服务器,期间业务会验签失败。

使用场景

只要你有自己的 API 服务(不是快验标准功能),想直接复用快验的用户体系,网关就是为这个准备的:

场景说明
私有 API 系统你写了独立的功能服务(短信业务/查数据/下发指令等),不想自己写注册登录、会员到期、防爆破,业务只管功能本身
手机端 App 自有接口App 已用快验登录,业务接口直接挂在 /wangGuan/ 下,带 Token 就能访问,服务端零账号代码
WebSocket 业务长连接业务(实时消息/推送)同样走网关握手鉴权,连接后协议完全自定
多个业务服务每个业务建一个网关(独立 Secret),互不影响,都共享同一套快验用户

反过来说,如果你用的是快验标准的软件用户Api、卡号充值这些内置功能,不需要网关,直接走 /Api 就行。

客户端怎么请求

请求地址规则:业务路径不变,前面加 /wangGuan。

路径大小写敏感

必须是 wangGuan(W 和 G 大写),写成 /wangguan 会直接 404。

业务原始路径: /api/sms/send
网关地址:     https://你的快验域名/wangGuan/api/sms/send

请求时携带两个身份参数,请求头优先,URL 参数兜底(浏览器 WebSocket 不能带自定义头,用 URL 参数):

参数位置来源
Token头 Token: xxx 或 参数 ?Token=xxx软件用户登录后的令牌,就是调用户Api用的那个 Token(GetToken/UserLogin 返回)
WangGuanId头 WangGuanId: 1 或 参数 ?WangGuanId=1后台「网关转发」列表第一列的 网关Id,告诉网关转发给哪个业务

请求体、其他自定义头、Content-Type、query 参数:原样透传,网关一概不读。

网关注入了什么

转发前,网关会对请求头做以下处理:

头动作说明
x-wg-jwt强制替换网关签发的 JWT,删除客户端传入的同名头后写入自己的(防伪造)
x-wg-ip强制替换用户真实IP,同样删除客户端传入值后写入(防伪造)
Token / WangGuanId剔除客户端凭证不下传给业务
X-Forwarded-For 等代理头剔除防伪造,业务要拿来源IP直接用 x-wg-ip
Host改写改为业务地址的 host
Location / Content-Location(响应)替换业务若返回重定向,内网地址会自动替换回 /wangGuan 前缀,不泄露内网结构
其余所有头原样透传

x-wg-ip 的取值优先级

  1. X-Real-Ip 请求头(需要前面 nginx 配置 proxy_set_header X-Real-IP $remote_addr;,这个值客户端伪造不了)
  2. X-Forwarded-For 的最后一跳(由前置代理写入)
  3. TCP 直连地址

JWT 说明

算法 HS256,密钥就是该网关的 Secret(业务服务器配置同一个 Secret 即完成配对)。有效期 60 秒(网关侧同一用户短时间内的请求会复用同一串 JWT,业务侧无需担心重复验签开销)。

Claims 内容:

字段类型含义
uidint快验软件用户 Id(0 表示游客令牌)
appIdint用户所属应用 AppId
tokenIdint在线记录 Id(可用于日志串联)
userstring账号或卡号:账密型应用=登录账号,卡号型应用=卡号
keystring绑定信息(软件用户表 Key 字段,该应用未注册此用户时为空)
vipTimeint到期时间戳或剩余点数(用户Api GetVipTime 的同款语义)
vipNumberfloat积分单独备用字段
userClassIdint用户分类 Id(0 未分类)
rmbfloat余额,仅账密型应用(AppType 1/2)携带,卡号型应用无此字段
ipstring用户真实IP(与 x-wg-ip 头一致)
iat / expint签发/过期时间戳

user/key/vipTime/vipNumber/userClassId 来自快验的应用用户表(db_AppUser_{appId})与在线记录,rmb 来自用户表余额,全部随 Token 鉴权一起查询并缓存。业务服务器验签后直接读 claims 即可拿到用户身份/到期/余额,不用再查快验。游客令牌(Uid=0)这些字段是零值。

应用类型说明(后台应用设置里的"软件类型"):1=账号限时, 2=账号计点 为账密型(有账号密码,登录名是账号,携带 rmb);3=卡号限时, 4=卡号计点 为卡号型(只输卡号登录,user 就是卡号,无 rmb 字段)。

完整 JWT 示例

假设某网关的 Secret 为:

6f8a2c19d4e7b35a9c01f8d2e4b6a8c03f5d7b9e1a2c4d6e8f0a1b3c5d7e9f0a

某账密型应用(AppId=10021)的用户 xiaobai01 登录后请求网关,签发出的 JWT 原文(三段用 . 分隔):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6MTAwMjEsImV4cCI6MTc5MTIwNDYxOCwiaWF0IjoxNzkxMjA0NTU4LCJpcCI6IjIyMy4xMDIuODguNTkiLCJrZXkiOiJBTUQgUnl6ZW4gNyA2ODAwSCIsInJtYiI6MTI4LjUsInRva2VuSWQiOjE4OTA4OTcsInVpZCI6OCwidXNlciI6InhpYW9iYWkwMSIsInVzZXJDbGFzc0lkIjoyLCJ2aXBOdW1iZXIiOjM1LjUsInZpcFRpbWUiOjE3OTg3NjE2MDB9.XLP0v0oHFYfxcP7WVVREBGEgmbnypcRY0NeDNqq1xVU

第一段 Header(base64 解码后):

{"alg": "HS256", "typ": "JWT"}

第二段 Payload(base64 解码后,就是上面 claims 表的内容):

{
  "appId": 10021,
  "exp": 1791204618,      // 过期时间:签发后60秒
  "iat": 1791204558,      // 签发时间
  "ip": "223.102.88.59",
  "key": "AMD Ryzen 7 6800H",
  "rmb": 128.5,           // 余额,仅账密型应用携带,卡号型应用无此字段
  "tokenId": 1890897,
  "uid": 8,
  "user": "xiaobai01",    // 账号;卡号型应用这里就是卡号
  "userClassId": 2,
  "vipNumber": 35.5,
  "vipTime": 1798761600
}

第三段 Signature:对 前两段 用该网关的 Secret 做 HMAC-SHA256(base64 后即上例最后一段)。

把示例 JWT 和上面的 Secret 粘到任意在线解析工具,即可看到解码结果,自己业务里也可以用它验证验签代码写对了没有。

在线解析工具

用于快速查看 JWT 内容、验证签名(均为浏览器本地解码):

WARNING

在线工具请只在测试阶段使用;这些工具在浏览器本地解码不会上传,但仍建议不要把生产 Secret 和真实用户 Token 长期留在页面里。

业务服务器验签示例(Go + github.com/dgrijalva/jwt-go,gin 中间件;其他 JWT 库同理):

import (
	"errors"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
)

func W网关验签() gin.HandlerFunc {
	return func(c *gin.Context) {
		局_jwt文本 := c.GetHeader("x-wg-jwt")
		if 局_jwt文本 == "" {
			c.AbortWithStatus(401)
			return
		}
		局_令牌, 局_err := jwt.Parse(局_jwt文本, func(token *jwt.Token) (interface{}, error) {
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { //防算法混淆攻击
				return nil, errors.New("签名算法不正确")
			}
			return []byte("你的网关Secret"), nil //HS256
		})
		if 局_err != nil || !局_令牌.Valid {
			c.AbortWithStatus(401)
			return
		}
		局_声明 := 局_令牌.Claims.(jwt.MapClaims)
		c.Set("Uid", int(局_声明["uid"].(float64)))
		c.Set("AppId", int(局_声明["appId"].(float64)))
		c.Set("Key", 局_声明["key"].(string))       //绑定信息
		c.Set("VipTime", int64(局_声明["vipTime"].(float64))) //到期或剩余点数
		_ = c.ClientIP() //来源IP直接用 x-wg-ip 头,或 claims["ip"]
	}
}

重要

业务服务器必须只监听 127.0.0.1 或内网网卡,并且验签中间件全局强制(包括 WS 握手和静态资源,不留任何例外路由),否则任何人绕过网关直连业务端口就绕过了鉴权。

其他语言(Java/PHP/Python/Node)用任意标准 JWT 库按 HS256 验签即可,无需其他配置。

WebSocket 怎么办

WebSocket 连接的建立本来就是一个 HTTP 请求(GET + Upgrade: websocket),所以:

  1. 客户端直接对 wss://你的快验域名/wangGuan/ws路径 发起 WS 连接,头(或URL参数)带 Token 和 WangGuanId
  2. 网关对握手请求做鉴权并签 JWT,转发给业务
  3. 业务回 101 后,网关把两条连接直接对接做字节透传

之后的心跳、消息格式、在线管理、主动推送全是业务服务器与客户端之间的事,网关一个字节不看。透传期间快验侧注销该 Token 不会断开连接——业务如果需要强制用户重新鉴权,自己定时断开连接即可,客户端重连时会重新经过网关鉴权。

业务服务器拒绝握手(比如验签失败返回 401)时,该响应会原样回给客户端,客户端能立即感知,不会悬挂。

错误怎么判断

鉴权失败、网关禁用、业务不可达等,网关不转发,直接返回:空 body + HTTP 状态码 + 响应头 x-wg-error(中文原因,百分号编码,decodeURIComponent 解码):

状态码含义常见原因
401token无效Token 错误、已注销、已超时,或用了管理员/代理/WebApi 令牌(软件用户与网页用户中心的令牌均可)
403网关已禁用后台把该网关切成了"禁用"
404网关不存在WangGuanId 填错,或网关已删除
502业务服务连接失败业务进程没启动、端口不对、Url 配错
504业务服务响应超时业务处理太慢,超过网关配置的响应超时

只要不是以上状态码,就是业务服务器的正常响应(原样透传,包括业务自己的错误码)。

性能设计

  • 转发热路径:0 次数据库查询 + 0 次日志写入 + 0 次 body 解析 + 1 次内存查表 + 1 次内网转发,网关引入的额外延迟在 1ms 以内(内网)
  • Token 缓存 30 秒:鉴权查库基本消除。代价是注销一个 Token 后,最多 30 秒内它仍可通过网关(对延迟不敏感的业务完全够用)
  • 无效 Token 负缓存 10 秒:防止恶意拿乱猜的 Token 打库
  • JWT 复用:同一用户 60 秒内所有请求(含 WS 握手)复用同一串 JWT,签发开销趋近于零
  • 连接池:网关到业务服务器常驻内网长连接,每个请求只是复用连接发数据
  • WS 透传:每条连接仅 2 个协程,万级连接无压力

部署注意事项

  1. nginx 放行 WebSocket(前面有 nginx 的话):proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; + 加大 proxy_read_timeout,否则 WS 会莫名断开
  2. nginx 配置 X-Real-IP:proxy_set_header X-Real-IP $remote_addr;,否则 x-wg-ip 可能拿到的是 nginx 的内网IP
  3. 异常大包/限流交给前置 nginx:网关本身不做任何大小限制和限流(设计如此)
  4. 网关重启会断开所有透传中的 WS:属预期行为,客户端有重连机制即可,发版选低峰

常见问题

Q: 客户端 Token 是软件用户登录的 Token 吗? 是,就是调用户Api用的那个 Token。网页用户中心(用户H5页)登录得到的令牌同样可以走网关(身份与软件用户一致);管理员后台、代理后台、WebApi 令牌不允许走网关,会返回 401。

Q: Token 长期只走网关,会被快验超时注销吗? 网关不刷新令牌的最后活跃时间(保持零写库)。如果一个纯网关客户端长期不发任何用户Api请求,快验的在线超时机制会注销它,之后请求返回 401,客户端重新登录即可。想避免频繁重登,让客户端隔一段时间调一次快验心跳接口即可。

Q: 一个网关的 Secret 能给另一个网关用吗? 不能,也强烈不建议。每个网关独立 Secret,不同业务的 JWT 互不相认,这是业务之间的隔离边界。

Q: 怎么更换 Secret? 在后台编辑网关,直接修改 Secret(或点"随机生成")保存即生效,没有过渡期。更换时业务会有一小段验签失败窗口,请安排在业务低峰操作,保存后立即把新 Secret 配置到业务服务器。

Q: 业务怎么拿用户到期时间/绑定信息? 验签后直接读 JWT claims 的 vipTime / key / vipNumber / userClassId,这些来自快验的应用用户表,网关已随鉴权一起查出,业务不用再调快验接口。

Last Updated:
Contributors: XuHaoNan