高并发下$_server['http_authorization']常为空是因nginx+php-fpm默认剥离该头,需配置fastcgi_param http_authorization $http_authorization;密钥校验须用hash_equals()防时序攻击,禁用trim/strtolower;密钥应通过环境变量加载,频控需lua脚本或incrbyex保证原子性。

高并发下 $_SERVER['HTTP_AUTHORIZATION'] 为什么经常拿不到密钥
PHP 的 CGI/FPM 模式默认会剥离 Authorization 请求头,尤其在 Nginx + PHP-FPM 架构中,$_SERVER['HTTP_AUTHORIZATION'] 常为空。这不是代码写错了,是 Web 服务器配置问题。
解决方法是显式透传该头:
- Nginx 配置里加:
fastcgi_pass_request_headers on;(默认已开启,但部分旧版本需确认) - 更可靠的做法是在
location ~ \.php$块中加:fastcgi_param HTTP_AUTHORIZATION $http_authorization; - Apache 用户需确保
mod_rewrite或mod_env启用,并在.htaccess或 vhost 中添加:SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
用 hash_equals() 校验密钥,别用 == 或 ===
直接比较密钥字符串(如 $input_key === $valid_key)存在时序攻击风险——攻击者可通过响应时间差异暴力猜解有效密钥。PHP 内置的 hash_equals() 是恒定时间比较函数,必须用它。
注意两点:
立即学习“PHP免费学习笔记(深入)”;
-
hash_equals()要求两个参数都为字符串,且不能为null;若密钥可能未传,先做非空判断再调用 - 不要对原始密钥做任何 trim / strtolower 处理后再比对——这会破坏恒定时间特性,应在存储和发放阶段就统一格式
- 示例正确写法:
if (!isset($_SERVER['HTTP_AUTHORIZATION']) || !is_string($_SERVER['HTTP_AUTHORIZATION'])) { http_response_code(401); exit; } $provided = trim($_SERVER['HTTP_AUTHORIZATION']); if (!hash_equals($valid_api_key, $provided)) { http_response_code(403); exit; }
密钥不硬编码,用 getenv() + 环境隔离防泄露
把密钥写死在代码里,一来违反安全规范,二来多实例部署时无法差异化配置。高并发服务通常横向扩缩,必须支持运行时注入。
推荐方式:
- 通过环境变量加载:
$valid_api_key = getenv('API_KEY') ?: die('API_KEY missing'); - Docker/K8s 场景下,用 Secret 挂载环境变量或文件,避免出现在镜像层
- 本地开发用
.env文件(配合vlucas/phpdotenv),但生产环境禁用该库——它有额外文件 I/O 和解析开销,影响高并发吞吐 - 绝不使用
parse_ini_file()或file_get_contents()动态读取密钥文件——每次请求都触发磁盘 IO,在 QPS 过万时会成为瓶颈
Redis 做密钥频控时,INCR + EXPIRE 必须原子执行
单纯先 INCR 再 EXPIRE 有竞态:若进程在两者之间崩溃,计数器残留且永不过期。高并发下极易积累脏数据。
正确做法只有两种:
- 用 Redis 2.6+ 的 Lua 脚本保证原子性:
eval "local current = redis.call('incr', KEYS[1]); redis.call('expire', KEYS[1], ARGV[1]); return current" 1 api:limit:abc123 60 - 或改用
INCRBYEX(Redis 7.0+),一条命令完成自增+过期:INCRBYEX api:limit:abc123 60 1 - 注意:不要用
SET key val EX 60 NX模拟计数——它无法递增,只适合开关类场景
密钥管理本身不复杂,难点在于所有环节都要经得起压测:头传递是否稳定、比较是否抗侧信道、配置是否零磁盘依赖、频控是否无竞态。漏掉任意一环,高并发时都会暴露成单点故障。











