Nginx的rewrite指令仅支持字符串匹配与替换,不支持算术运算或时间解析,因此无法直接用$arg_sign做时间校验;时间验证需借助map提取$arg_t并结合$sec比较,签名校验必须由后端完成。

为什么 rewrite 指令里不能直接用 $arg_sign 做时间校验
因为 Nginx 的 rewrite 是纯字符串匹配与替换阶段,不支持算术运算或时间解析。你看到的 $arg_sign 只是 URL 查询参数的原始字符串,比如 abc123,它本身不携带时间信息——除非你把时间戳和签名一起编码进这个值,且服务端能解码验证。
真正可行的路径是:先用 map 提前提取并解析 URL 中的时间参数(如 t=1717027200),再结合当前时间做差值判断;签名验证则必须交给后端(如 Python/Go 服务)完成,Nginx 只负责拦截过期请求。
-
map必须定义在http块,不能放在server或location内 - 提取
$arg_t后要用~^(\d+)$捕获纯数字,避免被注入非数字字符绕过 - 用
$time_iso8601或$sec获取秒级时间,别用$date_gmt(精度为秒但格式是字符串,没法直接减)
如何用 map + if 实现 URL 时间戳过期拦截
核心逻辑是:从 URL 中取 t 参数,转成整数,和 $sec(当前 Unix 秒)比较,超时就返回 403。注意 if 在 location 中才生效,且只适合简单判断。
map $arg_t $is_expired {
default 1;
~^(\d+)$ "$1";
}
<p>server {
location /dl/ {
if ($is_expired = 1) {
return 403;
}
if ($sec - $is_expired > 3600) {
return 403;
}
proxy_pass <a href="https://www.php.cn/link/65b5b8d1f89bf53a5713bc3afdd83e9e">https://www.php.cn/link/65b5b8d1f89bf53a5713bc3afdd83e9e</a>;
}
}</p>
这里有两个关键点:
-
map的default 1表示没传t或格式非法时,默认标记为“已过期”,防止空参绕过 -
$sec - $is_expired > 3600是允许的最大有效期(1 小时),单位是秒;若需更精确到毫秒,得改用 Lua 模块 - 不要在
if中写多个条件(如if ($a && $b)),Nginx 不支持逻辑运算符,要拆成嵌套if或改用map组合
签名字段 sign 怎么参与校验又不暴露密钥
签名不能由 Nginx 计算(没 HMAC 支持),也不能把密钥硬编码在配置里(会被 nginx -T 泄露)。正确做法是让 Nginx 把原始参数原样透传给后端,由后端用密钥重算签名比对。
你需要确保 URL 中的参数顺序固定、无多余空格、无未 decode 字符——否则后端解码后内容和签名原文不一致,校验必失败。
- 客户端生成签名时,应按字典序拼接参数:
t=1717027200&f=file.zip→ 签名 →?t=1717027200&f=file.zip&sign=xxx - Nginx 配置中禁用自动 decode:
merge_slashes off;,并在proxy_pass前加rewrite ^(.*)$ $1? break;防止参数被二次解析 - 后端收到请求后,丢弃
sign字段,对剩余查询参数按相同规则排序拼接,再用共享密钥 HMAC-SHA256 计算比对
为什么用 auth_request 比直接 proxy_pass 更安全
把签名和时效校验全堆在后端一个接口里,容易因业务逻辑变更或异常响应导致误放行。用 auth_request 可以把鉴权逻辑完全隔离:Nginx 先同步请求一个轻量鉴权服务,仅根据其返回状态码(200 放行,401/403 拦截)决定是否继续代理。
location /dl/ {
auth_request /_auth;
proxy_pass https://www.php.cn/link/65b5b8d1f89bf53a5713bc3afdd83e9e;
}
<p>location = /_auth {
proxy_pass <a href="https://www.php.cn/link/29b97ecc5839e61efb7bdc25e979d8c6">https://www.php.cn/link/29b97ecc5839e61efb7bdc25e979d8c6</a>;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}</p>
这样做的实际好处:
- 鉴权服务崩溃或超时,
auth_request默认拒绝,不会 fallback 到放行 - 你可以对
/_auth单独限流、打日志、加 tracing,不影响主下载链路 - 前端 URL 里的
sign和t无需 Nginx 解析,全部由鉴权服务统一处理,逻辑更集中
真正的难点不在 Nginx 配置,而在于鉴权服务怎么高效解析、排序、签名——尤其是当 URL 参数含中文或特殊符号时,URL decode 的先后顺序稍有偏差,签名就对不上。这点很容易被忽略,但一出问题几乎无法 debug。











