【问题标题】:Google Drive API Push Notifications - getting Invalid Credentials error when trying to use JWT tokenGoogle Drive API Push Notifications - 尝试使用 JWT 令牌时出现 Invalid Credentials 错误
【发布时间】:2021-12-20 05:47:28
【问题描述】:

我正在尝试为我的 Google Drive 帐户设置推送通知到 PHP webhook 端点。

首先,我按照这里的说明进行操作:

https://developers.google.com/drive/api/v3/push

这导致我验证了我的域并在 Search Console 中注册了它。一切似乎都很好。在这个阶段,我收到了一个包含以下数据的 JSON 文件:

{
  "type": "service_account",
  "project_id": "mywebapp",
  "private_key_id": "myprivatekey",
  "private_key": "-----BEGIN PRIVATE KEY-----somelongkey-----END PRIVATE KEY-----\n",
  "client_email": "somemeail@appspot.gserviceaccount.com",
  "client_id": "someclientid",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/mywebapp%40appspot.gserviceaccount.com"
}

然后为了设置watch channel,我有一个PHP脚本

$service_account_email = "myserviceaccount@mywebapp.iam.gserviceaccount.com";
$private_key = "-----BEGIN PRIVATE KEY-----somelongkey-----\n";
$uid = 'someid';
$is_premium_account = false;

$now_seconds = time();
$payload = array(
    "iss" => $service_account_email,
    "sub" => $service_account_email,
    "aud" => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
    "iat" => $now_seconds,
    "exp" => $now_seconds+(60*60),  // Maximum expiration time is one hour
    "uid" => $uid,
    "claims" => array(
        "premium_account" => $is_premium_account
    )
);

$token = JWT::encode($payload, $private_key, "RS256");

// now set up watch channel
$body =  [
    'id' => uniqid(),
    'type' => 'web_hook',
    'address' => 'https://mywebhookendpoint'
];
$client = new \GuzzleHttp\Client();
$result = $client->post('https://www.googleapis.com/drive/v3/changes/watch', [
    'headers' => [ 'Content-Type' => 'application/json',  "Authorization: Bearer ".$token ],
    'base_uri' => "https://www.googleapis.com/drive/v3/changes/watch",
    'body' => json_encode($body)
]);

当我这样做时,我得到

Fatal error: Uncaught GuzzleHttp\Exception\ClientException: Client error: `POST https://www.googleapis.com/drive/v3/changes/watch` resulted in a `401 Unauthorized` response:
{
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "authError",
    "message": "Invalid Credentials" (truncated...)

当我在 PHPStorm 中调试调用时,我看到创建了一个令牌,并且它从当前时间开始过期一小时。总的来说,一切都“看起来”很好。

我怀疑我从根本上采取了错误的方法,这不会让我感到惊讶,因为我以前从未尝试过,而且 Google 文档似乎对身份验证难题的所有不同部分如何组合在一起非常神秘,尤其是当/where 需要 Oauth,服务帐户何时/何地就足够了,JWT 令牌在何处/何时起作用。

如何克服上述错误?

【问题讨论】:

  • 为什么不用google api php客户端库?
  • 最终还是这样做了(我目前的方法在下面的答案中)

标签: php google-drive-api


【解决方案1】:

好的,到此为止了……

首先,我不得不说,Google API 文档有时就像在零重力环境中穿过镜子大厅的迷宫......

在这里注意到:

https://developers.google.com/drive/api/v3/about-auth

“您的应用程序必须使用 OAuth 2.0 来授权请求​​”

所以我放弃了上述方法,并通过服务帐户更具体地阅读了 Google OAuth,特别是使用 PHP,通过

https://github.com/googleapis/google-api-php-client

这是我成功获得授权令牌的方式。下面假设使用composer.json,条目如"require": { "google/apiclient": "^2.0"}

function getClient()
{
    require __DIR__ . '/vendor/autoload.php';
    $client = new Google_Client();
    $client->setApplicationName('Some Name');
    $client->setAuthConfig( __DIR__  . '/serviceaccount.json');
    $client->setScopes(Google_Service_Drive::DRIVE);
    $client->fetchAccessTokenWithAssertion();
    $token = $client->getAccessToken();
    return ['client'=>$client, 'token'=> $token['access_token']];
}

显然,Then, from here I can createservice` 对象通过

$client_and_token = getClient();
$service = new Google\Service\Drive($client_and_token['client']);

然后,设置Google Drive Push Notifications,如果我按照此处的说明进行操作:

https://developers.google.com/drive/api/v3/push

我被引导相信,正确的做法是

$httpclient = new \GuzzleHttp\Client();
$body = [
    'id' => uniqid(),
    'kind' => "drive#changes",
    'type' => 'web_hook',
    'address' => 'https://myendpoint'
];

$token = $client_and_token['token'];
$apiendpoint = 'https://www.googleapis.com/drive/v3/changes/watch';
$result = $httpclient->post($apiendpoint, [
                                'headers' => ['Content-Type' => 'application/json', 'Authorization' => "Bearer {$token}"],
                                'body' => json_encode($body),
                                "http_errors" => false]);

显然,感谢在这里阅读:

Get Google Analytics API token / Google_Client getAccessToken returns null / empty

$client->fetchAccessTokenWithAssertion(); 部分是使其与服务帐户一起使用的关键,而不是此处显示的方法 -https://developers.google.com/drive/api/v3/quickstart/php - 关于 PHP 快速入门,它假设我可以告诉用户交互,作为接收令牌的手段。无论如何,根据文档,收到令牌后,您必须添加到标题

Authorization: Bearer auth_token_for_current_user

上面最终返回有关缺少参数pageToken 的错误。顺便说一句,任何使用new \GuzzleHttp\Client() 的人都可能会注意到被截断的错误,因此很难弄清楚到底发生了什么。起初我只看到missing parameter: p,因为休息被截断了!因此在上面我添加了"http_errors" => false,然后在$contents = $result->getBody()->getContents(); 中查看全部内容

在阅读手表推送文档时

https://developers.google.com/drive/api/v3/reference/changes/watch

我没有看到任何关于 pageToken 是必需参数的信息。

不过貌似需要,所以看完这里

https://developers.google.com/drive/api/v3/manage-changes

我使用上面的 $service 对象通过

获得了初始 pageToken
$response = $service->changes->getStartPageToken();
$startpagetoken = $response->startPageToken;

然后我执行 Push POST 将频道通知设置为

$apiendpoint = 'https://www.googleapis.com/drive/v3/changes/watch?pageToken='.$startpagetoken;
$result = $httpclient->post($apiendpoint, [
                                'headers' => ['Content-Type' => 'application/json', 'Authorization' => "Bearer {$token}"],
                                'body' => json_encode($body),
                                "http_errors" => false]);

现在一切正常。当然,我可能需要考虑何时刷新令牌等,并弄清楚如何处理后续的 pageToken,但很高兴我终于克服了所有这些障碍。

我怀疑很多人都经历过这种痛苦,尤其是那些使用 PHP 进行这种服务器到服务器没有登录,没有用户交互身份验证方法的人。

总之,要让所有这些工作:

  1. 按照步骤启用 Google Drive API、验证域、在搜索控制台中注册域。这些步骤在这里:https://developers.google.com/drive/api/v3/push。请注意,您甚至可以在本地计算机上的 localhost 上运行它。我在 Mac 上使用ngrok,因此在上述步骤中,我使用我提供的ngrok 网址,但当然您必须确保您的网站不对公众开放。我仍然不确定最好的方法。我现在在<head> 的元标记中使用no-follow,但我确信我需要在本地 Apache 配置中做其他事情(也许有人有想法?)。

  2. 如下所示设置服务帐户: https://developers.google.com/identity/protocols/oauth2/service-account。许多关于服务帐户的文章很快就会失控。您不必对帐户做各种废话。只需为与服务帐户绑定的电子邮件地址添加角色。因此,如果您有类似name@app.iam.gserviceaccount.com 之类的服务帐户电子邮件,请确保该用户在帐户中具有足够高的角色(如所有者)。并且不确定是否需要它,但是对于我试图监视更改的文件夹,我在具有共享访问权限的用户列表中添加了name@app.iam.gserviceaccount.com。作为此过程的一部分,您最终会得到一个带有服务帐户凭据的 json 文件。这就是我上面在$client->setAuthConfig( __DIR__ . '/serviceaccount.json');中使用的内容@

  3. 发出必要的 Push HTTP 请求,这取决于您需要做什么,可以是 https://www.googleapis.com/drive/v3/changes/watch(加上烦人的 pageToken!),让您的端点设置为接收通知。 p>

  4. 在您的通知端点中,做任何您需要做的事情,例如,我正在使用我的 Google Drive Push Notifications,这样当我将 Zoom 视频保存到我的 Google Drive 时,我会通知用户我的 WordPress 网站有权访问该视频。这是我不向 Zoom 支付 Zoom Record to Cloud 功能的额外费用的方式。我只是在 Zoom 中点击录制按钮,然后在完成后将视频保存到我的 Google Drive。所以在 WordPress 中,这是这样做的

add_action( 'rest_api_init', function () {
    register_rest_route( 'mytheme/v1', '/googledrivesave', array(
        'methods'  => 'POST',
        'callback' => 'googledrivesave',
        'permission_callback' => '__return_true'
    ) );
}

function googledrivesave (WP_REST_Request $request) {
    $headers= getallheaders();
    // do stuff
    return http_response_code(200);
}

在 Laravel 等中,将是类似的方法。

【讨论】:

    猜你喜欢
    • 2017-10-12
    • 2014-05-24
    • 1970-01-01
    • 2021-11-04
    • 1970-01-01
    • 2016-11-08
    • 2023-03-30
    • 2020-06-08
    • 2020-02-20
    相关资源
    最近更新 更多