不安全,直接拼接$_server[‘http_origin’]存在跨域配置漏洞;必须白名单校验、硬编码或可信配置加载、严格字符串匹配、禁止*与credentials共用。

php中用header()动态设置access-Control-Allow-Origin是否安全?
不安全,直接拼接$_SERVER['HTTP_ORIGIN']或$_SERVER['HTTP_REFERER']返回是典型跨域配置漏洞。攻击者可伪造请求头绕过前端限制,后端若无校验,等于完全放行任意域名。
必须白名单校验,且仅在明确需要跨域的接口中设置,非必要不开启。
- 只对预检请求(
OPTIONS)和实际业务请求(如GET/POST)分别处理 - 白名单应硬编码或从可信配置加载,避免从请求中直接取值参与判断
- 不要用
*配合credentials: true,二者互斥
如何用白名单数组动态匹配并设置Access-Control-Allow-Origin
核心逻辑:检查$_SERVER['HTTP_ORIGIN']是否在预设白名单内,匹配成功才返回该域名,否则不设头或返回403。
$allowed_origins = [ 'https://a.example.com', 'https://b.example.net', 'https://shop.company.org' ]; <p>$origin = $_SERVER['HTTP_ORIGIN'] ?? ''; if (in_array($origin, $allowed_origins, true)) { header('Access-Control-Allow-Origin: ' . $origin); header('Access-Control-Allow-Credentials: true'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization'); }</p><p>// 预检请求直接结束,不执行后续逻辑 if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { exit(0); }
注意:in_array()必须加true做严格比较,防止https://evil.com.evil.com被https://evil.com误匹配。
立即学习“PHP免费学习笔记(深入)”;
为什么不能用parse_url()提取域名再比对?
因为parse_url($origin, PHP_URL_HOST)会丢失协议和端口信息,而跨域判定是“协议+域名+端口”三者全等。比如http://api.example.com和https://api.example.com是不同源,但parse_url提取的host一样。
- 白名单应写完整源(含
https://和端口,如https://admin.example.com:8080) - 直接比对
$origin字符串,不解析、不截断 - 若需支持子域名泛化(如
*.example.com),需手动实现模式匹配,不依赖parse_url
Apache/nginx中能否替代PHP动态设置?
可以,但有局限:Web服务器层无法做运行时逻辑判断(如查数据库、读用户配置),只能做静态规则或简单正则匹配。
例如Nginx中用map指令映射白名单:
map $http_origin $cors_origin { default ""; "~^https?://(a.example.com|b.example.net)$" "$http_origin"; } <p>server { location /api/ { add_header 'Access-Control-Allow-Origin' $cors_origin; add_header 'Access-Control-Allow-Credentials' 'true';</p><h1>其他头...</h1><pre class="brush:php;toolbar:false;"><code>}
}
这种写法省去PHP解析开销,但维护成本高、不支持动态增删域名,且正则易出错。生产环境建议PHP控制为主,Web服务器仅作兜底或简单场景使用。
真正难的是多租户场景下每个客户对应不同域名,此时必须由PHP读取租户配置并实时判断——这点Web服务器做不到。