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

调用API启停Google Compute Engine实例遇401认证错误求助

解决Google Compute API 401 Invalid Credentials错误的正确姿势

看起来你在认证方式上踩了个常见的坑——API Key不能用来做Bearer令牌,而且Compute Engine的实例启停这类操作,API Key本身就没有权限,因为它只适用于不需要用户身份的公开只读API。咱们一步步把问题解决:

为什么之前的方法行不通?

  • Authorization: Bearer <api_key>是完全错误的用法:Bearer令牌必须是OAuth 2.0的访问令牌,不是API Key。API Key只是用来标识你的项目,没有身份授权能力。
  • 即使在URL里加key=<api_key>,Compute Engine的写操作(启停实例)也不支持这种认证方式,因为这类操作需要验证请求者是否有对应的资源权限。

正确的解决方案:使用服务账号认证(后台服务最佳实践)

对于PHP后台这类无人值守的服务,最稳妥的方式是用服务账号来完成认证,步骤如下:

1. 创建服务账号并分配权限

  • 到Google Cloud控制台的「IAM与管理」→「服务账号」页面,创建一个新的服务账号。
  • 给这个服务账号分配合适的权限:比如Compute Instance Admin (v1)(如果需要全实例管理权限),或者更精细的Compute Instances Start和Compute Instances Stop权限(遵循最小权限原则)。
  • 下载服务账号的JSON密钥文件,保存到你的PHP项目目录(注意不要提交到代码仓库)。

2. 使用Google官方PHP客户端库(推荐)

直接用Google提供的google/cloud-compute库,它会自动处理认证流程,比自己写curl省心太多:

首先安装依赖:

composer require google/cloud-compute

然后写启停实例的代码:

require __DIR__ . '/vendor/autoload.php';

use Google\Cloud\Compute\V1\InstancesClient;
use Google\Cloud\Compute\V1\StartInstanceRequest;
use Google\Cloud\Compute\V1\StopInstanceRequest;

// 初始化客户端,指定服务账号密钥路径
$config = [
    'keyFilePath' => '/path/to/your/service-account-key.json'
];
$instancesClient = new InstancesClient($config);

// 启动实例的示例
try {
    $startRequest = (new StartInstanceRequest())
        ->setProject('your-project-id')
        ->setZone('your-instance-zone') // 比如us-central1-a
        ->setInstance('your-instance-name');
    
    $operation = $instancesClient->start($startRequest);
    // 等待操作完成(可选)
    $operation->pollUntilComplete();
    echo "实例启动成功!";
} catch (Exception $e) {
    echo "启动失败:" . $e->getMessage();
} finally {
    $instancesClient->close();
}

// 停止实例的示例
try {
    $stopRequest = (new StopInstanceRequest())
        ->setProject('your-project-id')
        ->setZone('your-instance-zone')
        ->setInstance('your-instance-name');
    
    $operation = $instancesClient->stop($stopRequest);
    $operation->pollUntilComplete();
    echo "实例停止成功!";
} catch (Exception $e) {
    echo "停止失败:" . $e->getMessage();
} finally {
    $instancesClient->close();
}

3. 如果一定要手动用Curl(不推荐)

如果你不想用客户端库,需要先通过服务账号密钥生成OAuth 2.0访问令牌,再用这个令牌作为Bearer令牌请求API:

// 第一步:生成JWT并获取访问令牌
$keyFilePath = '/path/to/your/service-account-key.json';
$keyData = json_decode(file_get_contents($keyFilePath), true);

// 构造JWT payload
$iat = time();
$exp = $iat + 3600; // 令牌有效期1小时
$payload = [
    'iss' => $keyData['client_email'],
    'sub' => $keyData['client_email'],
    'aud' => 'https://www.googleapis.com/oauth2/v4/token',
    'iat' => $iat,
    'exp' => $exp,
    'scope' => 'https://www.googleapis.com/auth/compute'
];

// 生成JWT签名
$privateKey = openssl_pkey_get_private($keyData['private_key']);
openssl_sign(json_encode($payload), $signature, $privateKey, 'SHA256');
$jwt = base64url_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT'])) . '.' .
       base64url_encode(json_encode($payload)) . '.' .
       base64url_encode($signature);

// 请求访问令牌
$tokenCurl = curl_init();
curl_setopt_array($tokenCurl, [
    CURLOPT_URL => 'https://www.googleapis.com/oauth2/v4/token',
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
    CURLOPT_POSTFIELDS => http_build_query([
        'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'assertion' => $jwt
    ]),
    CURLOPT_RETURNTRANSFER => true
]);
$tokenResponse = curl_exec($tokenCurl);
curl_close($tokenCurl);
$tokenData = json_decode($tokenResponse, true);
$accessToken = $tokenData['access_token'];

// 第二步:用访问令牌调用启停实例API
// 启动实例
$startCurl = curl_init();
curl_setopt_array($startCurl, [
    CURLOPT_URL => 'https://compute.googleapis.com/compute/v1/projects/your-project-id/zones/your-zone/instances/your-instance-name/start',
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $accessToken,
        'Content-Type: application/json'
    ],
    CURLOPT_RETURNTRANSFER => true
]);
$startResponse = curl_exec($startCurl);
curl_close($startCurl);

// 停止实例类似,把URL换成对应的/stop路径

注意:base64url_encode需要自己实现,因为PHP的base64_encode是标准base64,而JWT需要URL安全的版本:

function base64url_encode($data) {
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

最后检查几个关键点

  • 确保服务账号的权限已经正确分配,并且权限已经生效(有时候会有几分钟的延迟)。
  • 确认你的PHP环境能访问Google的API,没有被防火墙或代理拦截。
  • 密钥文件的路径要正确,并且文件权限设置合理(不要让其他用户可读)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:41:18