sign参数校验必须在中间件统一实现,涵盖原始请求体解析、app_id/timestamp/noncestr/signature三处提取、防重放(ip+noncestr+timestamp三元组Redis校验)、严格对齐客户端签名逻辑,并动态加载app_secret。

sign参数校验必须在中间件里统一做,不能放控制器
Webman 的路由和生命周期决定了:签名验证属于请求入口级安全控制,必须在中间件中拦截并终止非法请求。如果写到每个 store() 或 show() 里,不仅重复造轮子,还会漏掉未覆盖的接口(比如导出、批量操作等非标准 resource 路由),更严重的是——一旦某个控制器忘了校验,整个 API 就裸奔了。
正确做法是注册一个全局中间件(如 app/middleware/SignVerifyMiddleware.php),并在 config/middleware.php 中加入它,确保所有 API 请求(或指定前缀如 /api/*)都经过它。
- 中间件里要提前读取原始请求体(
$request->rawBody()),因为 POST/PUT 的 sign 可能依赖 body 内容 - 必须先解析 query、header、body 三处可能携带的字段(
app_id、timestamp、noncestr、signature),别只从get()或post()单侧取 - 校验失败直接
return response()->json(['code' => 401, 'msg' => 'sign invalid'])->withStatus(401),别 throw 异常——异常会绕过中间件链,且错误码不明确
timestamp 和 noncestr 必须组合校验防重放
单靠 timestamp 校验(比如只判断是否超时 300 秒)不够,攻击者可截获一次合法请求,改时间戳后重发——只要在窗口期内,就能绕过。真正有效的防重放,必须绑定 noncestr + ip + timestamp 三元组。
常见错误是把 noncestr 存 Redis 时没加过期时间,或用 MySQL 没建联合索引(ip, noncestr, created_at)。结果查一次要 200ms,拖慢所有请求。
-
noncestr建议用 UUID v4 或 13 位时间戳+18 位随机数(如1678159075243872934567890123),避免纯数字被暴力枚举 - Redis key 设计为
sign:replay:{md5(ip . noncestr)},value 存timestamp,TTL 设为和超时窗口一致(如 600 秒) - 若启用重放校验,必须在签名计算前就检查该
noncestr是否已存在,存在则直接拒绝,不进后续逻辑
sign 计算逻辑必须严格对齐客户端,大小写和拼接顺序不能错
前后端 sign 不一致,90% 是因为拼接顺序或编码细节没对齐。Webman 默认用 PHP 的 ksort(),但客户端 JS/Java/Python 的排序行为未必一致;MD5 大小写、32/16 位、是否 hex 编码也常踩坑。
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
典型错误示例:signStr = "a=1&b=2×tamp=1651226218" → md5(signStr . $secret) → 全大写,但客户端用了 sha256 或漏了 & 分隔符。
- 务必约定:参数按 key 字典序升序排列,空值参数(
''或null)不参与拼接,key 和 value 都要 URL decode 后再排序 - 拼接格式统一用
key1=value1&key2=value2(不是冒号或空格),末尾不加& - 加密后统一转大写(
strtoupper(md5(...))),不要用bin2hex或 base64 - 调试时打印出服务端生成的
signStr和最终sign,让前端对照——比口头描述高效十倍
app_secret 不能硬编码,要用 driver 动态加载
把 app_secret 写死在中间件里,等于把密码贴在门上。Webman 项目上线后,不同环境(dev/staging/prod)的密钥必然不同,且需支持热更新(比如某 app_id 密钥泄露后秒级禁用)。
直接 new 数组配置驱动最简单,但生产环境必须用数据库或 Redis 驱动。否则每次改密钥都要重启服务,且无法按 app_id 动态开关。
- 推荐用
gitfei1231/webman-api-sign的DatabaseDriver,表结构含app_id、app_secret、status、expired_at - 查询时加缓存(
db_cache_time设为 3600),避免每请求都查 DB;缓存 key 用app_sign:{app_id} - 若用 ArrayDriver,确保
app_sign配置数组里app_secret字段是字符串,不是 int(PHP 会自动转,导致 md5 结果错)
真正难的不是写校验逻辑,而是让每个环节都“严丝合缝”:客户端生成 sign 的那一刻,服务端还原的每一步都得完全一致。少一个 URL decode、多一个空格、时间戳差 1 秒,全盘失效。别信“差不多”,API 安全校验没有模糊地带。

















