单页 React 应用程序(含 OAuth)
此沙箱提供了构建和开发使用 Envoy 的单页应用程序的示例。
沙箱涵盖了 Envoy 的许多功能,包括
动态 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,供示例使用。
提供了设置,以 构建和更新应用程序以供生产使用,以及 开发环境,并提供 自动代码重新加载。
生产和开发环境分别在端口 10000
和 10001
上公开。
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 上可用,并提供一个登录按钮。
注意
虚拟 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 进行查询。
警告
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 传递回用户。
此 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
。
步骤 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 或完整应用程序。后者提供了更多控制,通常更可取。
这可以在 用户 或组织级别完成。
注意
设置 Github OAuth 时,您需要提供重定向 URI。
这必须与 Envoy 中配置的 URI 匹配。
在本例中,将其设置为 https://127.0.0.1:10000。
您需要一个单独的 OAuth 应用程序用于开发。
根据您的用例,您可能还需要设置应用程序所需的任何权限。
设置好后,您将需要 提供的客户端 ID 和密钥。
步骤 11:更新 Envoy 的配置以使用 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
最后,添加(或用 github
和 github-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。
另请参见
- 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 的引用。