You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel Guzzle POST请求返回403 Forbidden问题排查与代码修正

问题

使用Laravel Livewire控制器代码通过Guzzle发送POST请求时返回403 Forbidden,但Postman发送相同参数的请求可正常响应。签名已通过签名板生成并正确保存到存储目录,但无法通过file_variables参数将文件传递给API。
环境:PHP 8.2.0 / Laravel 10.6.2

原控制器代码

public function next()
{
    $this->header = [
        'Accept'     => 'application/json',
        'X-API-KEY' => $this->user->api_key,
        'Content-Type' => 'multipart/form-data',
    ];

    $body = [
        [
            'name' => 'u',
            'contents' => $this->task->action->systask,
        ],
        [
            'name' => 'session',
            'contents' => $this->task->session,
        ],
        [
            'name' => 'name',
            'contents' => $this->currentQ['qName'],
        ],
    ];

    if (isset($this->currentQ['qType'])) {
        if ($this->currentQ['qType'] == 'signature') {
            //upload signature file
            Storage::disk("docs")->put('signatures/signature' . $this->user->da_id . '.png', base64_decode(Str::of($this->currentVariables[html_entity_decode($this->currentQFields[0]['variable_name'])])->after(',')));

            $sign = fopen(storage_path('docs/signatures/signature' . $this->user->da_id . '.png'), 'r');
            array_push(
                $body,
                [
                    'name' => 'file_variables',
                    'contents' => json_encode([$this->currentQFields[0]['variable_name'] => $this->currentQFields[0]['variable_name']])
                ],
                [
                    'name' => 'signature1',
                    'contents' => $sign,
                ],
            );
            if (isset($this->currentVariables[html_entity_decode($this->currentQFields[0]['variable_name'])])) {
                unset($this->currentVariables[html_entity_decode($this->currentQFields[0]['variable_name'])]);
            }
        } else {
            array_push(
                $body,
                [
                    'name' => 'variables',
                    'contents' => json_encode($this->currentVariables),
                ],
            );
        }
    }
    $api_url = config('hrda.base_uri') . config('hrda.resume_session');
    $client = new Client();
    try {
        $response = $client->request('POST', $api_url, [
                ['headers' => $this->header],
                'multipart' => $body,
            ]);
    } catch (RequestException $e) {
        return back()->with('error', $e->getMessage());
    }

    // ...
}

已尝试操作

  • 使用file_get_contents、fopen、Base64编码/解码方式发送文件,均无效
  • API已允许当前服务器IP(Postman可正常访问)

Postman请求信息

请求头
x-api-key: oNV9KwVRCSVG5asdWEXwlEG2nf2
Accept: application/json

表单数据(form-data)
Session: b2QWUlCEOWJLr7dnLLlgnGzpwaIiZ9Ax
u: frontend.yml
signature: C:\users/xyz/storage/userDocs/signatures/signature7.png
name: Question_2


排查与修正方案

1. 移除手动设置的Content-Type头

Guzzle处理multipart请求时会自动生成包含boundary的正确Content-Type,手动设置会覆盖该值,导致请求格式错误,触发API的403校验。
修正:删除$this->header中的'Content-Type' => 'multipart/form-data',

2. 修复Guzzle请求参数格式

原代码中request方法的第三个参数错误地将headers嵌套在额外数组中,导致请求头未正确传递。
修正:将

$response = $client->request('POST', $api_url, [
        ['headers' => $this->header],
        'multipart' => $body,
    ]);

改为

$response = $client->request('POST', $api_url, [
        'headers' => $this->header,
        'multipart' => $body,
    ]);

3. 对齐表单字段名

Postman中文件字段名为signature,但代码中使用signature1,字段名不匹配会导致API无法识别文件,引发校验失败。
修正:将'name' => 'signature1',改为'name' => 'signature',

4. 优化文件读取逻辑

使用Laravel Storage的path()方法获取文件路径更可靠,同时需手动关闭文件句柄避免资源泄漏。
修正:

// 替换原文件读取代码
$filePath = Storage::disk('docs')->path('signatures/signature' . $this->user->da_id . '.png');
$sign = fopen($filePath, 'r');

// 请求完成后添加关闭语句
fclose($sign);

5. 修正file_variables的JSON格式

原代码中file_variables的JSON内容为[变量名=>变量名],不符合API预期,应映射为[变量名=>文件名]。
修正:

[
    'name' => 'file_variables',
    'contents' => json_encode([
        $this->currentQFields[0]['variable_name'] => 'signature' . $this->user->da_id . '.png'
    ])
]

6. 添加文件名参数到文件字段

Guzzle上传文件时指定filename参数,确保API能正确识别文件名,避免解析错误。
修正:

[
    'name' => 'signature',
    'contents' => $sign,
    'filename' => 'signature' . $this->user->da_id . '.png'
]

修正后的完整代码

public function next()
{
    $this->header = [
        'Accept'     => 'application/json',
        'X-API-KEY' => $this->user->api_key,
    ];

    $body = [
        [
            'name' => 'u',
            'contents' => $this->task->action->systask,
        ],
        [
            'name' => 'session',
            'contents' => $this->task->session,
        ],
        [
            'name' => 'name',
            'contents' => $this->currentQ['qName'],
        ],
    ];

    if (isset($this->currentQ['qType'])) {
        if ($this->currentQ['qType'] == 'signature') {
            // 保存签名文件
            $signatureVarName = html_entity_decode($this->currentQFields[0]['variable_name']);
            $signatureFileName = 'signature' . $this->user->da_id . '.png';
            Storage::disk("docs")->put(
                'signatures/' . $signatureFileName,
                base64_decode(Str::of($this->currentVariables[$signatureVarName])->after(','))
            );

            // 读取文件
            $filePath = Storage::disk('docs')->path('signatures/' . $signatureFileName);
            $sign = fopen($filePath, 'r');

            // 添加表单字段
            array_push(
                $body,
                [
                    'name' => 'file_variables',
                    'contents' => json_encode([$signatureVarName => $signatureFileName])
                ],
                [
                    'name' => 'signature',
                    'contents' => $sign,
                    'filename' => $signatureFileName
                ],
            );

            // 移除变量
            if (isset($this->currentVariables[$signatureVarName])) {
                unset($this->currentVariables[$signatureVarName]);
            }

            // 关闭文件句柄
            fclose($sign);
        } else {
            array_push(
                $body,
                [
                    'name' => 'variables',
                    'contents' => json_encode($this->currentVariables),
                ],
            );
        }
    }

    $api_url = config('hrda.base_uri') . config('hrda.resume_session');
    $client = new Client();

    try {
        $response = $client->request('POST', $api_url, [
            'headers' => $this->header,
            'multipart' => $body,
        ]);
    } catch (RequestException $e) {
        return back()->with('error', $e->getMessage());
    }

    // ...
}

内容的提问来源于stack exchange,提问作者Salman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 17:37:51