小程序请求不带 Referer,需用微信签名机制(sign/timestamp/nonce)校验+Redis 按 openid 限流+idempotency_key 幂等+ Nginx 兜底防护,且时间单位须统一。
小程序发起的 wx.request 请求默认不带 Referer,服务端读到的 $_SERVER['HTTP_REFERER'] 是空或不可信值。靠它做来源校验等于没设防。
真正有效的识别依据是微信签名机制——每次请求都应携带 sign、timestamp、nonce,且服务端需用约定密钥重算签名比对。
md5(timestamp + nonce + secret) 生成 sign
timestamp 是否在 5 分钟有效窗口内(防重放)sign,严格区分大小写和拼接顺序401 Unauthorized
小程序用户登录态是 openid,不是 IP。按 IP 限流会误伤同一 WiFi 下多个用户;按 openid 限流才合理。
推荐使用 Redis 的 INCR + EXPIRE 原子组合,避免竞态条件:
// 示例:每分钟最多 30 次接口调用
$openid = $this->getOpenidFromToken(); // 从 Authorization header 或 POST 中提取
$key = "rate_limit:{$openid}:api_v1_submit";
$redis->incr($key);
$redis->expire($key, 60);
if ($redis->get($key) > 30) {
throw new Exception('Request limit exceeded', 429);
}
$redis->expire() 而非 SET key val EX 60,因 INCR 可能创建 key 导致过期失效expire() 对不存在 key 返回 false 易漏判GET 再 INCR,这会产生竞态,必须依赖原子操作防止恶意脚本反复提交(比如抽奖、下单),光靠频率限制不够,得靠业务层幂等控制。

要求小程序在请求头或参数中带上唯一 idempotency_key(如 UUID v4),服务端用它作为 Redis 键存处理状态:
SETNX idempotency_key:xxx processing 300(5 分钟过期)idempotency_key:xxx:result
PHP 层限流失效时(比如 FPM 崩溃、代码绕过),Nginx 的 limit_req 是最后一道防线。
配置示例(按 $http_x_wx_openid 限流,需小程序主动透传):
map $http_x_wx_openid $openid_key {
default $http_x_wx_openid;
}
limit_req_zone $openid_key zone=api_per_user:10m rate=30r/m;
location /api/ {
limit_req zone=api_per_user burst=5 nodelay;
fastcgi_pass php-fpm;
}
map 提取 header,不能直接用 $http_x_wx_openid 做 zone key,否则空值会打满一个桶burst=5 允许短时突发,但 nodelay 表示不延迟排队,超了直接 503,避免请求堆积拖垮 PHP最易被忽略的是时间窗口一致性:Redis 过期时间、签名 timestamp 容差、Nginx limit_req 的 rate 单位,三者必须统一用秒或分钟,混用会导致限流形同虚设。