关于PHP中$_SERVER['HTTP_ORIGIN']使用curl请求是空的情况

6714次阅读 452人点赞 作者: WuBin 发布时间: 2025-11-14 11:16:10
扫码到手机查看

问题描述

部署到线上后发现这么个问题:

比如项目地址:A:https://ai2hinen9t1.thexin.cn/ai/x1/index.html

B:https://ai2hinen9t1.thexin.cn/ai/x1/chat.php用户鉴权地址:C:https://ai2hinen9t1.thexin.cn/user-check/check.php

C鉴权中,也进行了域名白名单的审核,代码如下:

ini_set('date.timezone', 'Asia/Shanghai');
try {

    // 定义允许的前端域名白名单(数组形式,无斜杠)
    $allowedOrigins = [
        'http://192.168.1.101:8080',
        'http://localhost:8080',
        'https://yourdomain.com' // 可添加更多域名
    ];

    // 动态获取前端请求源(可能为空,如非跨域请求)
    $origin = $_SERVER['HTTP_ORIGIN'] ?? '';

    // 判断当前请求源是否在白名单中
    $isAllowed = in_array($origin, $allowedOrigins);

    // 1. 处理 OPTIONS 预检请求
    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
        if ($isAllowed) {
            header("Access-Control-Allow-Origin: {$origin}"); // 返回当前请求的域名
            header("Access-Control-Allow-Headers: Authorization, Content-Type");
            header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
            header("Access-Control-Allow-Credentials: true");
            header("Access-Control-Max-Age: 86400"); // 缓存预检结果24小时
        }
        exit;
    }

    // 2. 处理实际请求(POST/GET)
    if ($isAllowed) {
        header("Access-Control-Allow-Origin: {$origin}"); // 返回当前请求的域名
        header("Access-Control-Allow-Credentials: true");
    } else {
        throw new Exception('您的域名不在白名单中', 1002);
    }

} catch (Exception $e) {
    http_response_code(401);
    $errcode = $e->getCode();
    $errMsg = $e->getMessage();
    $json = json_encode([
        'code'  => $errcode,
        'msg'   => $errmsg,
        'data'  => []
    ], JSON_UNESCAPED_UNICODE);
    exit($json);
}

我在浏览器中,访问A页面,A在页面加载后会自动向C发送一个请求,来验证是否有权限使用,这时候:

$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$_SERVER['HTTP_ORIGIN'] 可以获取到https://ai2hinen9t1.thexin.cn 

这时候,通过浏览器进行请求,功能正常。

然后当我在页面进行对话的时候,会通过B发送对话请求,具体流程如下:

B 通过发送curl -> C (鉴权)-> B -> 鉴权成功 -> 向第三方发请求
                                鉴权失败 -> 抛错,中止请求

我将B向C发送鉴权封装如下:

<?php
class UserManager {
    const URL_USER_CHECK = '...地址';

    static $userKey;
    static $token;
    static $appId;

    // 执行用户权限检测
    public static function requestCheck($appId, $token, $isSaveCount = 0)
    {

        $url = self::URL_USER_CHECK;

        $params = [
            'app_id'        => $appId,
            'token'         => $token,
            // 是否保存次数
            'save_count'    => $isSaveCount,
        ];

        $ch = curl_init();
        
        $options = array(
            CURLOPT_URL => $url,
            CURLOPT_POST => true, 
            CURLOPT_RETURNTRANSFER => true, 
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HEADER => false,
            // 如果是application/json 那么必须json_encode 使用表单格式
            CURLOPT_POSTFIELDS =>  http_build_query($params),
            CURLOPT_HTTPHEADER => [
                // 'Content-Type: application/json' // 必须使用file_get_contents('php://input');获取
               'Content-Type: application/x-www-form-urlencoded',
                // curl向服务端发送,要想获取origin,必须手动设置 
               // 'Origin: https://ai2hinen9t1.thexin.cn'
            ]
        );
        
        curl_setopt_array($ch, $options);
        
        $response = curl_exec($ch);
        
        if(curl_error($ch)) {
            $errMsg = 'Error: ' . curl_error($ch);
            self::throwError('抱歉,账号验证请求失败,请稍候重试。', 1003);
            curl_close($ch);
            return false;
        }
        
        curl_close($ch);
        
        return json_decode($response, true);
    }

    public static function throwError($message = '', $errCode = 0)
    {
         throw new Exception($message, $errCode);
    }
}

我只需要在需要的地方执行即可:

$commonUserInfo = UserManager::requestCheck(
        $appId = $appid,
        $token = $token,
        $isSaveCount = $isNeedSaveCount ? 1 : 0
);

然后我却发现,A->C通过浏览器发送的请求,可以正常获取$SERVER['HTTP_ORIGIN'],而通过B->C,用curl发送的请求,C中无法获取$SERVER['HTTP_ORIGIN']!

为什么在chat.php中通过 cURL 发送请求到check-token.php时,$_SERVER['HTTP_ORIGIN']会为空

这背后涉及到跨域资源共享(CORS)的工作原理以及服务器端请求和客户端请求的区别。

核心原因

HTTP_ORIGIN是一个由浏览器在发起跨域请求时自动添加的请求头。它用来告诉目标服务器,这个请求来自于哪个源(协议 + 域名 + 端口)。

而你在chat.php中使用 cURL 发起的请求,是一个服务器端到服务器端的请求,并非由浏览器发起。因此,cURL 不会自动添加Origin请求头,check-token.php自然也就无法从$_SERVER中获取到HTTP_ORIGIN的值。

详细解释

让我们来对比一下两种请求流程:

  1. index.html直接发送请求 (成功的情况)

浏览器 (https://ai2hinen9t1.thexin.cn/ai/x1/index.html)
       |
       | 发起 AJAX 请求
       | 请求头中自动包含: Origin: https://ai2hinen9t1.thexin.cn
       v
服务器 (ai2hinen9t1.thexin.cn)
       |
       | 执行 check.php
       | $_SERVER['HTTP_ORIGIN'] 被设置为浏览器传来的 Origin 值
       | 你的代码检查该值是否在白名单中,验证通过
       v
返回数据给浏览器

在这个流程中,浏览器是请求的发起者,它遵循 CORS 协议,主动提供了Origin信息。

chat.php使用 cURL 发送请求 (失败的情况)

浏览器 (https://ai2hinen9t1.thexin.cn/ai/x1/chat.php)
       |
       | 访问 chat.php 页面
       v
服务器 (ai2hinen9t1.thexin.cn)
       |
       | 执行 chat.php 代码
       | 代码到达 cURL 部分,准备发起内部请求
       |
       | cURL 发起请求到 check.php
       | 这个请求是从服务器内部发出的,没有浏览器参与
       | 请求头中没有 Origin 字段
       v
服务器 (ai2hinen9t1.thexin.cn)
       |
       | 执行 check.php
       | $_SERVER['HTTP_ORIGIN'] 不存在,为空字符串 ''
       | 你的代码检查 '' 是否在白名单中,验证失败,抛出异常
       v
返回错误信息给 chat.php 的 cURL 调用

在这个流程中,chat.php是一个中间层。浏览器请求chat.php,然后chat.php作为一个 “客户端” 请求check.php。由于这个内部请求不是由浏览器直接发起的,所以 CORS 的Origin机制不适用。

解决方案

方案一:在 cURL 请求中手动添加Origin

这是最直接的解决方法。既然 cURL 不自动加,你可以手动把它加上。

修改chat.php中的requestCheck方法,在CURLOPT_HTTPHEADER中添加Origin

// ... 在 chat.php 的 requestCheck 方法中 ...

$options = array(
    CURLOPT_URL => $url,
    CURLOPT_POST => true, 
    CURLOPT_RETURNTRANSFER => true, 
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HEADER => false,
    CURLOPT_POSTFIELDS => http_build_query($params),
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/x-www-form-urlencoded',
        // 手动添加 Origin 头,值可以是你允许的任何一个白名单域名
        'Origin: https://ai2hinen9t1.thexin.cn' 
    ]
);

// ...

方案二:使用 API Key 进行服务器间认证 (推荐)

这是更安全、更规范的做法。对于服务器之间的通信,不应该依赖用于浏览器的 CORS 机制,而应该使用专门的认证方式,比如 API Key。

修改check.php

try {
    $allowedOrigins = [
        'https://ai2hinen9t1.thexin.cn',
    ];
    $origin = $_SERVER['HTTP_ORIGIN'] ?? '';

    // 定义一个服务器间通信的 API Key
    $serverApiKey = 'your_strong_secret_api_key_here';
    $requestApiKey = $_REQUEST['api_key'] ?? ''; // 从 GET 或 POST 参数中获取

    // 判断:要么 Origin 在白名单内,要么 API Key 正确
    $isAllowed = in_array($origin, $allowedOrigins) || ($requestApiKey === $serverApiKey);

    if (!$isAllowed) {
        throw new Exception("Unauthorized request.", 1002);
    }

    // ... 后续逻辑 ...

} catch (Exception $e) {
    // ...
}

修改chat.phprequestCheck方法: 在请求参数中带上这个 API Key。

public static function requestCheck($appId, $token, $isSaveCount = 0)
{
    $url = self::URL_USER_CHECK;

    $params = [
        'app_id'        => $appId,
        'token'         => $token,
        'save_count'    => $isSaveCount,
        'api_key'       => 'your_strong_secret_api_key_here' // 带上 API Key
    ];

    // ... cURL 选项 ...
}

这样,服务端与服务端的curl通信,使用api_key作为凭证进行验证即可,就避免了curl为空的问题。

HTTP_ORIGIN为空是正常现象?

HTTP_ORIGIN并不是一个标准的 CGI/HTTP 环境变量,它是由浏览器在跨域请求(如 AJAX、Fetch API)中自动添加的请求头,用于标识请求来源域。

$_SERVER中不存在HTTP_ORIGIN完全正常的现象。

为什么 有的get请求HTTP_ORIGIN不存在?

HTTP_ORIGIN这个请求头只有在特定情况下才会被浏览器发送,它不是一个标准的、每次请求都会携带的头。具体来说:

  1. 跨域请求(CORS):当你的 JavaScript 代码(例如在a.com的页面)尝试向另一个域名(例如b.com)发送 AJAX 请求时,浏览器会自动在请求头中添加Origin: http://a.com。服务器端的$_SERVER['HTTP_ORIGIN']变量就是从这个请求头来的。

  2. 非跨域请求:当你直接在浏览器地址栏输入 URL、或者点击同域名下的链接时,请求是同源的,浏览器不会发送Origin请求头。因此,$_SERVER中自然也就没有HTTP_ORIGIN这个元素。

  3. 简单请求与预检请求(Preflight):对于一些复杂的跨域请求(例如使用POST方法发送application/json数据),浏览器会先发送一个OPTIONS方法的 “预检请求”,这个请求一定会包含Origin头。如果服务器允许该来源的跨域请求,浏览器才会发送真正的请求。

  4. 服务端到服务端的curl请求,默认不会携带origin,因此HTTP_ORIGIN也不存在。

当浏览器发送的是一个简单的、同源的GET请求(或者从浏览器中直接访问地址),所以不会附加Origin请求头。因此,你的 PHP 脚本也就无法在$_SERVER中找到HTTP_ORIGIN

如何正确处理HTTP_ORIGIN

如果你是在编写处理跨域请求(CORS)的 API 接口,那么你需要考虑HTTP_ORIGIN。处理逻辑应该是这样的:

1、检查请求是否包含Origin

// 注意:要用 $_SERVER['HTTP_ORIGIN'] ?? '' 来避免 Notice: Undefined index 错误
$origin = $_SERVER['HTTP_ORIGIN'] ?? ''; 

2、如果$origin不为空,说明这是一个跨域请求。你需要验证这个$origin是否在你允许的域名列表中。

$allowedOrigins = [
    'https://your-frontend.com',
    'https://www.your-frontend.com'
];

if (in_array($origin, $allowedOrigins)) {
    // 允许该来源的跨域请求
    header("Access-Control-Allow-Origin: $origin");
    // 如果你需要支持带凭据的请求 (credentials: 'include'),需要设置为 true
    header("Access-Control-Allow-Credentials: true"); 
}

3、如果$origin为空,说明这是一个同源请求或者不是由浏览器发起的请求(例如 curl、Postman),这种情况下通常不需要做任何 CORS 处理。

不要担心$_SERVER里没有HTTP_ORIGIN。它只在跨域的 AJAX/Fetch 请求时才会出现

当浏览器执行的是同源get请求、请求很简单的时候就会为空。

点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
关键词:curl,HTTP_ORIGIN
推荐阅读
  • python基础-操作列表和迭代器

    python基础笔记-操作列表和迭代器的相关方法

    6708次阅读 164人点赞 发布时间: 2024-06-13 13:26:27 立即查看
  • uniapp实现被浏览器唤起的功能

    当用户打开h5链接时候,点击打开app若用户在已经安装过app的情况下直接打开app,若未安装过跳到应用市场下载安装这个功能在实现上主要分为两种场景,从普通浏览器唤醒以及从微信唤醒。

    12221次阅读 828人点赞 发布时间: 2022-12-14 16:34:53 立即查看
  • PHP

    【正则】一些常用的正则表达式总结

    在日常开发中,正则表达式是非常有用的,正则表达式在每个语言中都是可以使用的,他就跟JSON一样,是通用的。了解一些常用的正则表达式,能大大提高你的工作效率。

    15425次阅读 646人点赞 发布时间: 2021-10-09 15:58:58 立即查看
  • 【中文】免费可商用字体下载与考证

    65款免费、可商用、无任何限制中文字体打包下载,这些字体都是经过长期验证,经得住市场考验的,让您规避被无良厂商起诉的风险。

    16367次阅读 1304人点赞 发布时间: 2021-07-05 15:28:45 立即查看
  • Vue

    Vue3开发一个v-loading的自定义指令

    在vue3中实现一个自定义的指令,有助于我们简化开发,简化复用,通过一个指令的调用即可实现一些可高度复用的交互。

    18720次阅读 1503人点赞 发布时间: 2021-07-02 15:58:35 立即查看
  • JS

    关于手机上滚动穿透问题的解决

    当页面出现浮层的时候,滑动浮层的内容,正常情况下预期应该是浮层下边的内容不会滚动;然而事实并非如此。在PC上使用css即可解决,但是在手机端,情况就变的比较复杂,就需要禁止触摸事件才可以。

    16901次阅读 1366人点赞 发布时间: 2021-05-31 09:25:50 立即查看
  • Vue

    Vue+html2canvas截图空白的问题

    在使用vue做信网单页专题时,有海报生成的功能,这里推荐2个插件:一个是html2canvas,构造好DOM然后转canvas进行截图;另外使用vue-canvas-poster(这个截止到2021年3月...

    32715次阅读 2567人点赞 发布时间: 2021-03-02 09:04:51 立即查看
  • Vue

    vue-router4过度动画无效解决方案

    在初次使用vue3+vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操...

    28566次阅读 2199人点赞 发布时间: 2021-02-23 13:37:20 立即查看
交流 收藏 目录