单页 React 应用程序(含 OAuth)

此沙箱提供了构建和开发使用 Envoy 的单页应用程序的示例。

沙箱涵盖了 Envoy 的许多功能,包括

  • direct_response

  • OAuth

  • 动态 xDS 文件系统更新

  • WebSocket 代理

  • Gzip 压缩

  • TLS/SNI 上/下游连接/终止

  • 路径/主机重写

该应用程序使用 React,并使用 Vite 构建,并演示了使用 Envoy 的 OAuth 过滤器 进行 OAuth 身份验证。

这涵盖了一种场景,我们希望 OAuth 同时对用户进行身份验证并提供用于进一步 API 交互的凭据。

这可以通过将 OAuth 配置 forward_bearer_token 设置为 true 来实现。

36                redirect_uri: "%REQ(x-forwarded-proto)%://%REQ(:authority)%/authorize"
37                forward_bearer_token: true
38                pass_through_matcher:
39                  name: ":path"
40                  string_match:

警告

设置 forward_bearer_token 意味着提供的访问令牌将被转发到 Envoy 为此 HTTP 过滤器链代理的任何集群/上游。

如果存在不可信的上游,则需要采取措施来删除任何敏感的 cookie,例如 BearerToken

这可以通过为受影响的路由设置 request_headers_to_remove 来实现。

一个虚拟的“Myhub”后端提供了最小的 OAuth 提供程序和 API,供示例使用。

提供了设置,以 构建和更新应用程序以供生产使用,以及 开发环境,并提供 自动代码重新加载

生产和开发环境分别在端口 1000010001 上公开。

Myhub 后端可以轻松地替换为 Github 或其他基于 OAuth 的上游服务,并提供了一些 有关如何执行此操作的指南

步骤 1:创建 .local 目录用于沙箱自定义

更改到 examples/single-page-app 目录,并创建一个目录来存储沙箱自定义。

您可以使用 .local,它将被 Git 忽略。

$ mkdir .local

ui/ 目录复制到 .local 并设置 UI_PATH。这将允许自定义,而无需更改提交的文件。

$ cp -a ui .local
$ export UI_PATH=./.local/ui

步骤 2:生成 HMAC 密钥

Envoy 的 OAuth 过滤器 要求使用 HMAC 密钥来对凭据进行编码。

将默认的沙箱密钥复制到自定义目录,并创建所需的 HMAC 密钥。

用您选择的短语替换 MY_HMAC_SECRET_SEED

$ cp -a secrets .local
$ HMAC_SECRET=$(echo "MY_HMAC_SECRET_SEED" | mkpasswd -s)
$ export HMAC_SECRET
$ envsubst < hmac-secret.tmpl.yml > .local/secrets/hmac-secret.yml

导出密钥文件夹的路径以供 Docker 使用

$ export SECRETS_PATH=./.local/secrets

步骤 3:启动容器

首先导出 UID 以确保容器创建的文件使用您的用户 ID 创建。

然后启动 Docker 组成

$ pwd
envoy/examples/single-page-app
$ export UID
$ docker compose pull
$ docker compose up --build -d
$ docker compose ps
NAME                          IMAGE                       COMMAND                                                    SERVICE   CREATED         STATUS         PORTS
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
single-page-app-envoy-1       single-page-app-envoy       "/docker-entrypoint.sh envoy -c /etc/envoy/envoy.yaml ..." envoy     2 minutes ago   Up 2 minutes           0.0.0.0:10000-10001->10000-10001/tcp, :::10000-10001->10000-10001/tcp
single-page-app-myhub-1       single-page-app-myhub       "/opt/myhub/app.py"                                        myhub     2 minutes ago   Up 2 minutes (healthy) 0.0.0.0:7000->7000/tcp, :::7000->7000/tcp
single-page-app-myhub-api-1   single-page-app-myhub-api   "/opt/myhub/app.py"                                        myhub-api 2 minutes ago   Up 2 minutes (healthy)
single-page-app-ui-1          single-page-app-ui          "/entrypoint.sh dev.sh"                                    ui        2 minutes ago   Up 2 minutes (healthy)

步骤 4:浏览至开发应用程序并登录

开发应用程序现在应该在 https://127.0.0.1:10001 上可用,并提供一个登录按钮。

../../_images/spa-login.png

注意

虚拟 OAuth 提供程序会自动将所有人作为硬编码的 envoydemo 用户进行信任,并重定向回应用程序。

在现实世界中,提供程序会在继续之前对用户进行身份验证和授权。

沙箱配置了对 pass_through_matcher 的反向匹配。

这会忽略所有用于 OAuth 的路径,除了

  • /authorize.*

  • /hub.*

  • /login

  • /logout.

37                forward_bearer_token: true
38                pass_through_matcher:
39                  name: ":path"
40                  string_match:
41                    safe_regex:
42                      regex: >-
43                        ^\/(authorize.*|login|logout)$
44                  invert_match: true
45                redirect_path_matcher:
46                  path:

当用户点击 login 时,应用程序会通过调用 Envoy 中的 /login 路径来启动 OAuth 流程。

这会将用户重定向到 OAuth 提供程序以进行授权/身份验证,并提供一个包含重定向链接的进一步重定向链接。

34                default_expires_in: 600s
35                authorization_endpoint: https://127.0.0.1:7000/authorize
36                redirect_uri: "%REQ(x-forwarded-proto)%://%REQ(:authority)%/authorize"
37                forward_bearer_token: true
38                pass_through_matcher:
39                  name: ":path"

在成功授权/身份验证后,用户会通过此链接重定向回应用程序,并附带必要的 OAuth 授权码 以继续操作。

44                  invert_match: true
45                redirect_path_matcher:
46                  path:
47                    prefix: /authorize
48                signout_path:
49                  path:
50                    exact: /logout

然后,Envoy 使用此授权码及其客户端密钥来确认授权并为用户获取访问令牌。

50                    exact: /logout
51                credentials:
52                  client_id: "0123456789"
53                  token_secret:
54                    name: token
55                    sds_config:
56                      path_config_source:
57                        path: /etc/envoy/secrets/myhub-token-secret.yml
58                  hmac_secret:
59                    name: hmac
60                    sds_config:
61                      path_config_source:
1resources:
2- "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret
3  name: token
4  generic_secret:
5    secret:
6      inline_string: VERY_SECRET_KEY

登录后,您应该能够使用 OAuth 凭据对 API 进行查询。

../../_images/spa-resources.png

警告

Envoy 的 OAuth 实现默认情况下会为端点上的所有路径触发 OAuth 流程。

这很容易在请求资产时触发 OAuth 泛滥,并在 OAuth 流程失败时导致死循环。

这可以通过限制 OAuth 流程使用的路径来避免。

沙箱示例通过反转 pass_through_matcher 来实现,以仅匹配所需的 OAuth 路径。

提示

Myhub OAuth 提供程序不会为发出的凭据提供有效期。同样,Github 也可能提供或不提供,具体取决于配置。这在 OAuth2 规范方面是有效的。

如果授权提供程序未包含有效期,Envoy 将默认情况下会失败身份验证。

这可以通过设置 default_expires_in 来解决。

33                  timeout: 3s
34                default_expires_in: 600s
35                authorization_endpoint: https://127.0.0.1:7000/authorize
36                redirect_uri: "%REQ(x-forwarded-proto)%://%REQ(:authority)%/authorize"
37                forward_bearer_token: true

步骤 5:执行 API 查询

对于沙箱应用程序,forward_bearer_token 已设置,因此 Envoy 还会将获取的访问令牌作为 cookie 传递回用户。

../../_images/spa-cookies.png

此 cookie 随后会在所有对代理的 Myhub API 的后续请求中通过 Envoy 传递。

76                  prefix: "/hub/"
77                route:
78                  host_rewrite_literal: api.myhub
79                  regex_rewrite:
80                    pattern:
81                      regex: '^/hub/(.*)'
82                    substitution: '/\1'
83                  cluster: hub-api
84              - match:
85                  prefix: "/"
86                route:
87                  cluster: ui

步骤 6:实时重新加载代码更改

在浏览器中打开 https://127.0.0.1:10001 并对 UI 进行一些更改。

例如,您可以更改页面标题。

$ sed -i s/Envoy\ single\ page\ app\ example/DEV\ APP/g .local/ui/index.html

页面应该会自动刷新。

同样,对 .local/ui/src/... 中的 Typescript 应用程序组件的任何更改都应该自动在浏览器中重新加载。

Envoy 通过允许将代理连接到 Vite 开发后端“升级”以使用 Websockets 来实现这一点。

22          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
23          upgrade_configs:
24          - upgrade_type: websocket
25          http_filters:
26          - name: envoy.filters.http.oauth2
27            typed_config:

您可以使用以下命令查看开发服务器的日志:

$ docker compose logs ui
single-page-app-ui-1  | Starting (dev.sh) with user: 1000 worker /home/worker
single-page-app-ui-1  | yarn run v1.22.19
single-page-app-ui-1  | $ vite --host 0.0.0.0 --port 3000
single-page-app-ui-1  |
single-page-app-ui-1  |   VITE v5.0.10  ready in 119 ms
single-page-app-ui-1  |
single-page-app-ui-1  |   ➜  Local:   https://127.0.0.1:3000/
single-page-app-ui-1  |   ➜  Network: http://172.30.0.5:3000/

如果您想与进程交互,也可以使用 docker attach

提示

您可以使用 Yarn 管理 Typescript 包。

$ docker compose run --rm ui yarn

步骤 7:从应用程序中注销

注销时,应用程序会向 Envoy 配置的 signout_path 发送请求。

47                    prefix: /authorize
48                signout_path:
49                  path:
50                    exact: /logout
51                credentials:
52                  client_id: "0123456789"
53                  token_secret:

这将清除 Envoy 存储的 cookie 和凭据,然后将用户返回到应用程序主页。

应用程序还会清除与用户会话关联的任何存储数据。

步骤 8:构建生产资产

首先,创建一个自定义的 xds/ 目录并设置它。

您需要重新构建 Envoy 以确保它看到正确的目录。

$ mkdir .local/production
$ cp -a xds .local/production/
$ export XDS_PATH=./.local/production/xds
$ docker compose up --build -d envoy

您可以使用以下命令构建应用程序的生产资产。

$ docker compose run --rm ui build.sh

构建 React 应用程序后,沙盒脚本会自动使用为服务应用程序所需的静态路由更新 Envoy 的配置。

您可以查看生成的路由。

$ jq '.resources[0].filter_chains[0].filters[0].typed_config.route_config.virtual_hosts[0].routes' < .local/production/xds/lds.yml
[
  {
    "match": {
      "path": "/assets/index-dKz4clFg.js"
    },
    "direct_response": {
      "status": 200,
      "body": {
        "filename": "/var/www/html/assets/index-dKz4clFg.js"
      }
    },
    "response_headers_to_add": [
      {
        "header": {
          "key": "Content-Type",
          "value": "text/javascript"
        }
      }
    ]
  },
  {
    "match": {
      "path": "/myhub.svg"
    },
    "direct_response": {
      "status": 200,
      "body": {
        "filename": "/var/www/html/myhub.svg"
      }
    },
    "response_headers_to_add": [
      {
        "header": {
          "key": "Content-Type",
          "value": "image/svg+xml"
        }
      }
    ]
  },
  {
    "match": {
      "prefix": "/"
    },
    "direct_response": {
      "status": 200,
      "body": {
        "filename": "/var/www/html/index.html"
      }
    },
    "response_headers_to_add": [
      {
        "header": {
          "key": "Content-Type",
          "value": "text/html"
        }
      }
    ]
  }
]

注意

此设置将 Envoy 配置为将必要的文件存储在内存中。

这可能非常适合单页应用程序用例,但对于许多或大型文件来说,扩展性不好。

提示

当您对 javascript/typescript 文件进行更改时,重新构建应用程序会为编译后的资产创建新的路由。

在这种情况下,Envoy 将通过 xDS 更新并使用新路由的资产。

如果您只对没有新路由的资产进行了更改——例如 index.html——您应该同时重新构建应用程序并在更改后重新启动 Envoy。

$ docker compose run --rm ui build.sh
$ docker compose restart envoy

步骤 9:浏览到生产服务器

您可以在 https://127.0.0.1:10000 上浏览此服务器。

与开发端点不同,生产端点配置了以下内容:

  • TLS(自签名)

  • Gzip 压缩

  • 静态服务资产

步骤 10:设置 Github OAuth/API 访问权限

提示

本沙盒中解释了 Github 的设置,但这些说明应该很容易适应其他提供商。

您需要设置 Github OAuth 或完整应用程序。后者提供了更多控制,通常更可取。

这可以在 用户 或组织级别完成。

../../_images/spa-github-oauth.png

注意

设置 Github OAuth 时,您需要提供重定向 URI。

这必须与 Envoy 中配置的 URI 匹配。

在本例中,将其设置为 https://127.0.0.1:10000

您需要一个单独的 OAuth 应用程序用于开发。

根据您的用例,您可能还需要设置应用程序所需的任何权限。

设置好后,您将需要 提供的客户端 ID 和密钥

步骤 11:更新 Envoy 的配置以使用 Github

添加 Github 提供的客户端密钥

$ TOKEN_SECRET="GITHUB PROVIDED CLIENT SECRET"
$ export TOKEN_SECRET
$ envsubst < secrets/token-secret.tmpl.yml > .local/secrets/github-token-secret.yml

创建的文件将在容器的 /etc/envoy/secrets 下可用。

提示

以下说明使用 sed,但您可能希望使用编辑器进行必要的替换。

对于每个配置,需要更新 2 个地方,一个用于开发侦听器,另一个用于生产。

创建 Envoy 配置的副本,并告诉 Docker 使用它。

$ cp -a envoy.yml .local/envoy.yml
$ export ENVOY_CONFIG=.local/envoy.yml

对于 .local/envoy.yml 中的 OAuth 配置,设置 Github 提供的客户端密钥

$ sed -i s@client_id:\ \"0123456789\"@client_id:\ \"$GITHUB_PROVIDED_CLIENT_ID\"@g .local/envoy.yml

authorization_endpoint 替换为 https://github.com/login/oauth/authorize

$ sed -i s@authorization_endpoint:\ https://127.0.0.1:7000/authorize@authorization_endpoint:\ https://github.com/login/oauth/authorize@g .local/envoy.yml

token_endpoint > uri 替换为 https://github.com/login/oauth/access_token

$ sed -i s@uri:\ http://myhub:7000/authenticate@uri:\ https://github.com/login/oauth/access_token@g .local/envoy.yml

token_secret > path 指向上面创建的 github-token-secret.yml

$ sed -i s@path:\ /etc/envoy/secrets/myhub-token-secret.yml@path:\ /etc/envoy/secrets/github-token-secret.yml@g .local/envoy.yml

替换 主机重写

$ sed -i s@host_rewrite_literal:\ api.myhub@host_rewrite_literal:\ api.github.com@g .local/envoy.yml

最后,添加(或用 githubgithub-api 集群替换 myhub* 集群)Github configured clusters

$ cat _github-clusters.yml >> .local/envoy.yml

步骤 12:更新应用程序配置以使用 Github

我们需要告诉应用程序提供商的名称。

目前,已经实现了 Myhub 和 Github 的提供商。

 7export const AuthProviders: IAuthProviders = {
 8  "myhub": {
 9    "name": "Myhub",
10    "icon": MyhubIcon},
11  "github": {
12    "name": "Github",
13    "icon": GithubIcon}}

如果您按照上述步骤操作,则 Vite 应用程序环境设置将从 .local/ui/.env* 中读取。

$ echo "VITE_APP_AUTH_PROVIDER=github" > .local/ui/.env.local

步骤 13:重新构建应用程序并重新启动 Envoy

$ docker compose run --rm ui build.sh
$ docker compose up --build -d envoy

提示

请注意使用的是 up --build -d 而不是 restart

这是必要的,因为我们已经更改了 envoy.yml,它在构建时加载到容器中。

浏览到生产服务器 https://127.0.0.1:10000

现在您可以登录并使用 Github API

../../_images/spa-login-github.png

另请参见

Envoy OAuth 过滤器

Envoy OAuth 过滤器的配置参考。

Envoy OAuth 过滤器 API

Envoy OAuth 过滤器的 API 参考。

OAuth2 规范

OAuth 2.0 是行业标准的授权协议。

React

用于 Web 和原生用户界面的库。

Vite

下一代前端工具。

Envoy Gzip 压缩 API

Envoy Gzip 压缩的 API 和配置参考。

保护 Envoy 快速入门指南

保护 Envoy 的关键概念概述。

Github OAuth 应用程序

有关设置 Github OAuth 应用程序的信息。

Github API

Github API 的引用。