Cloud 运行 + Cloud Endpoints + Service Account Authentication – 在 curl 中有效,但在 JS 中使用 fetch API 时无效

Cloud Run + Cloud Endpoints + Service Account Authentication – works in curl but doesn't when using fetch API in JS

我已经配置了一个执行 GCF 的云端点。当云 运行 服务允许 allUsers 调用 API.

时一切正常

删除 allUsers 并使用服务帐户进行身份验证后,云 运行 控制台中显示 403 错误:

The request was not authenticated. Either allow unauthenticated invocations or set the proper Authorization header. Read more at https://cloud.google.com/run/docs/securing/authenticating

Chrome JS控制台显示如下错误信息:

Access to fetch at 'https://.run.app/do-this&key=' from origin 'http://0.0.0.0:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

这是我在浏览器中运行的JS代码:

        let options: RequestInit = {
            headers: {
                'Authorization': `Bearer ${token}`,
            },
        }
        
        const result = await fetch(fetchURL, options);

当运行使用相同的标记进行 curl 时,我得到了预期的响应

curl -H "Authorization: Bearer ${token}" 'https://<my-api>.run.app/do-this&key=<key>'

为了完整起见,这里还有端点 yaml

swagger: '2.0'
info:
  title: My first widget
  description: This is a great widget
  version: 1.0.0
host: <my-api>.run.app
schemes:
  - https
produces: 
  - application/json
paths:
  /do-this:
    get:
      summary: Do-this
      operationId: doit
      x-google-backend:
        address: https://<project-id>.cloudfunctions.net/do-that
      responses:
        '200':
          description: A successful response.
          schema:
            type: string
        '403':
          description: An error occurred
          schema:
            type: string
      security:
        - api_key: []

securityDefinitions:
  # This section configures basic authentication with an API key.
  api_key:
    type: "apiKey"
    name: "key"
    in: "query"

更新esp的命令:

gcloud run services update <my-api> --set-env-vars="^|^ENDPOINTS_SERVICE_NAME=<my-api>.run.app|ESP_ARGS=--rollout_strategy=managed,--cors_preset=basic" --project=<project-id> --platform=managed --region=europe-west1

更新

启用 cors 浏览器端没有帮助。

Google 文档提到应该可以 call from outside GCP

If you're invoking a service from a compute instance that doesn't have access to compute metadata (e.g. your own server), you'll have to manually generate the proper token:
Self-sign a service account JWT with the target_audience claim set to the URL of the receiving service. Exchange the self-signed JWT for a Google-signed ID token, which should have the aud claim set to the above URL. Include the ID token in an Authorization: Bearer ID_TOKEN header in the request to the service. Although Identity-Aware Proxy is not yet supported for Cloud Run (fully managed), you can examine the Identity-Aware Proxy sample code for code examples of the steps above.

The end-users section:虽然提到了 CORS

When you build a web app, you have to account for Cross-Origin Resource Sharing (CORS) issues. For example, CORS preflight requests are sent without an Authorization header, so they are rejected on a non-public service. Because the preflight requests fail, the main request will also fail.
To work around this, you can host your web app and service(s) on the same domain to avoid CORS preflight requests. You can achieve that by using Firebase Hosting.

我尝试在 Firebase 托管上托管 JS 脚本和 HTML,但问题仍然存在。

想到的另一个问题是:我是否需要在开放 api 规范中将 OAuth 与 API 密钥身份验证一起设置?

更新 2

This discussion 表明无法将 Cloud 运行 与支持 CORS 的身份验证一起使用。我还想知道为什么它可以卷曲。我正在使用服务帐户令牌进行身份验证,而不是最终用户。

没有为 Cloud Endpoint 激活 cors。像这样更新您的 openAPI 规范

swagger: '2.0'
info:
  title: My first widget
  description: This is a great widget
  version: 1.0.0
host: <my-api>.run.app
x-google-endpoints:
- name: <my-api>.run.app
  allowCors: True
...
...
...

或设置no-cors按照错误消息所述检查您的通话。

我按以下方式让它工作:

  1. oauth 作为安全定义添加到 OpenAPI 规范中,并将其与每个 API 路径的 api 密钥一起使用
  2. 使用 --set-env-vars="^|^ENDPOINTS_SERVICE_NAME=<my-api>.run.app|ESP_ARGS=--cors_preset=basic,--rollout_strategy=managed"
  3. 部署端点
  4. 在 Cloud Function 中,将 Access-Control-Allow-Origin 设置为空字符串 ''res.setHeader("Access-Control-Allow-Origin", '')
  5. 允许 allUsers
  6. 访问 Cloud 运行 容器

虽然每个人都可以访问云 运行 容器,但端点负责身份验证。

让我感到惊讶的一件事是 CF 会自动在 Access-Control-Allow-Origin header 中添加调用主机(例如 mydomain.com)和 *。此 header 中不允许使用多个项目,因此我将删除 mydomain.com 并保留 *.

我将尝试不同的选项,完成后将提供一个 how-to,其中包含所有涉及的步骤。非常感谢任何 comments/suggestions!

更新

深​​入挖掘后,我现在明白 Access-Control-Allow-Origin 是由 const cors = require('cors')({origin: true});

自动添加的

对于我的用例,我不需要 CF 中的 cors,因为它们只能从 Cloud 运行 ESP 访问。

因此重要的步骤是:

  • 将 oauth 添加到 OpenAPI 规范
  • 通过ESP_ARGS
  • 启用cors
  • 允许 allUsers
  • 访问云 运行 容器