SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-13

如何自行托管 Open Connector:OAuth 与备份配置

在自己的 VPS 上运行 Open Connector,使用固定镜像、TLS 源站和 OAuth 回调,让 AI agent 不再持有 SaaS 令牌,并通过 SQLite 备份保护连接凭据。

AI agent 使用 Open Connector 的作用

自行托管 Open Connector,可以在 AI agent 与其调用的所有软件即服务(SaaS)API 之间设置一个统一的身份验证网关,因此 agent 无需持有任何提供商令牌。它是 OOMOL Lab 开发的开源网关,采用 Apache 2.0 许可证。它以单个容器运行,将状态保存在一个 SQLite 文件中,并通过 HTTP 和 MCP(模型上下文协议)提供提供商操作。

问题从第二个集成开始。每个提供商都有自己的 OAuth(开放授权)流程、刷新令牌有效期和作用域名称。手动将 5 个提供商接入 agent,意味着需要编写 5 个重定向处理程序、5 个凭据存储,以及 5 个必须在令牌过期前运行的刷新循环。几乎没有人会编写这些代码。他们会为每项服务生成一个长期有效的个人访问令牌,然后将其粘贴到 agent 配置、环境文件,甚至提示词中。此令牌随后可被 agent 运行的每个工具读取,并且会进入记录文本;让机密信息离开 AI agent一文描述的正是这种失效模式。

身份验证网关会将凭据分为两部分。网关存储提供商凭据并运行 OAuth 流程。agent 获取的运行时令牌只能用于访问网关。当 agent 调用某项操作时,网关会加载已存储的凭据,在服务器端将其注入出站请求,然后只返回响应正文。agent 永远不会收到提供商访问令牌,因此即使 agent 记录文本泄露,攻击者获得的也只是一个可撤销的运行时令牌,而不是您的 GitHub 账户。

目录声称提供超过 1,000 个提供商和 10,000 个预构建操作。这是项目自身给出的数据,外部无法验证。可以验证的是其结构:每项操作对应一个 HTTP 端点,每个提供商对应一个已存储连接,每个 agent 对应一个令牌。

为什么选择自行托管 Open Connector,而不是使用托管连接器服务

托管连接器服务执行相同的工作,并保存您连接的每个服务提供商的 refresh token。Google 或 GitHub 的 refresh token 是访问您的邮件和代码仓库的长期有效加密凭据,通常不会因密码更改而失效。它们一旦遭到入侵,您的系统也会随之遭到入侵。自行托管会将这些记录存入您租用并管理的服务器上的 SQLite,并使用始终留在您自己的服务器上的密钥进行保护。

开始前,先明确这项成本。这个 VPS 会成为您运行的最有价值的服务器。它在一个文件中保存十几个服务的有效凭据,因此应按密码管理器主机的标准进行管理:防火墙只开放 443,不使用共享登录凭据,准备一份您确实恢复过一次的备份,并在服务停止响应时发出告警。如果您不愿意将密码库放在这台服务器上,也不要将连接器放在上面。

安装前固定版本

Open Connector 仍处于早期阶段。该仓库首次出现于 29 June 2026。截至 1 August 2026,最新的带标签版本是 v1.3.3,于 30 July 2026 发布,并且同样带有 latest 标签。registry 还发布了 tip 标签,该标签基于 main 上的最新提交构建。

对于这样一个刚起步的项目,浮动标签经常变化。一个跳过两个版本的 docker compose pull 可能会更改您的 agent 所依赖的 endpoint,导致您花费整个晚上将其误判为 agent 问题并进行排查。请将镜像固定到发布版本标签,并在阅读发布说明后,于您决定的时间升级。

在自有 VPS 上通过 TLS 部署 Open Connector

容器启动前,您需要:

  • Ubuntu 24.04 或相近版本上的 Docker 和 Compose 插件
  • 一个 A 记录指向此 VPS 的主机名,例如 connect.example.com
  • 一个已为该主机名终止 TLS(传输层安全)的反向代理
  • 下方生成的两个随机密钥

适用于多个 Docker Compose 应用的 Traefik 反向代理介绍了代理端配置。针对单个应用,从头到尾配置证书的过程请参阅在 VPS 上使用 Docker 和 HTTPS 部署 n8n

首先生成密钥。加密密钥用于保护存储的凭据。管理员令牌用于保护 Web 控制台和整个 /api 接口。这两个值都没有默认值,但运行时即使缺少它们也会正常启动。

mkdir -p ~/open-connector && cd ~/open-connector
umask 077
printf 'OOMOL_CONNECT_ENCRYPTION_KEY=%s\n' "$(openssl rand -base64 32)" > .env
printf 'OOMOL_CONNECT_ADMIN_TOKEN=%s\n' "$(openssl rand -base64 32)" >> .env
chmod 600 .env

现在将两个值复制到密码管理器中,再进行首次启动。加密密钥无法恢复,原因见下方的故障列表。

现在创建 compose.yaml。它与上游示例有两处不同,而且两处都很重要。

services:
  connector:
    image: ghcr.io/oomol-lab/open-connector:v1.3.3
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    volumes:
      - connector-data:/app/data
    environment:
      OOMOL_CONNECT_DATA_DIR: /app/data
      OOMOL_CONNECT_ORIGIN: "https://connect.example.com"
      OOMOL_CONNECT_ENCRYPTION_KEY: "${OOMOL_CONNECT_ENCRYPTION_KEY:?set this in .env}"
      OOMOL_CONNECT_ADMIN_TOKEN: "${OOMOL_CONNECT_ADMIN_TOKEN:?set this in .env}"

volumes:
  connector-data:

第一处修改是使用固定的标签,而不是 latest。第二处是端口。上游文件发布 3000:3000,这会绑定主机上的所有网络接口。Docker 会在 ufw 过滤链处理数据包之前,将已发布端口写入 NAT(网络地址转换)表,因此 ufw deny 3000 无法关闭该端口;这就是Docker 端口绕过 ufw 的原因中介绍的陷阱。写入 127.0.0.1:3000:3000 后,端口只会发布到 loopback 接口,反向代理则从同一台主机连接。

:? 会将每个变量标记为必需变量。因此,缺少 .env 时,堆栈会拒绝启动,而不是以未加密的凭据启动。将值保存在 .env 中,而不是 compose 文件中,这是Docker Compose 环境文件和密钥采用的模式。

docker compose up -d
docker compose logs -n 30 connector
curl -s http://127.0.0.1:3000/health
sudo ss -tlnp | grep 3000

运行时启动后,/health 会回答 { "ok": true }ss 必须输出 127.0.0.1:3000。如果某行显示 0.0.0.0:3000,说明端口映射仍是上游配置,网关正在直接响应整个互联网。健康检查返回 connection refused,表示容器尚未监听端口;请先查看日志,再处理代理配置。

同一服务的 Traefik 标签
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.connector.rule=Host(`connect.example.com`)"
      - "traefik.http.routers.connector.entrypoints=websecure"
      - "traefik.http.routers.connector.tls.certresolver=le"
      - "traefik.http.services.connector.loadbalancer.server.port=3000"

如果 Traefik 在同一台主机上的 Docker 中运行,请将此服务连接到 Traefik 网络,并删除 ports: 块。Traefik 会通过内部网络访问容器,因此完全不需要向主机发布端口。certresolver=le 必须与 Traefik 静态配置中的解析器名称一致,否则路由器启动时不会获得证书。

为什么 OAuth 要求使用真实主机名

OOMOL_CONNECT_ORIGIN 是人们经常跳过的设置,而跳过它会导致 OAuth 失败,表现得像是提供商出现了问题。运行时会根据该来源生成重定向 URI,格式为 <origin>/oauth/callback。如果未设置该值,来源默认为 http://localhost:3000,因此运行时会向提供商发送 http://localhost:3000/oauth/callback 作为重定向 URI,而您的 OAuth 应用注册的是 https://connect.example.com/oauth/callback。这两个字符串不一致,因此 GitHub 会返回:

The redirect_uri MUST match the registered callback URL for this application.

OAuth 提供商会将浏览器重定向回该 URI,因此该地址必须能从公网访问;除 localhost 外,提供商会拒绝普通的 http://。这就是此部署需要主机名和证书的全部原因。请在首次启动前设置来源,因为该值会在启动时读取:编辑 .envcompose.yaml 后,再次运行 docker compose up -d 以使设置生效。

通过 OAuth 连接第一个提供商

先在提供商处创建 OAuth 应用。在 GitHub 中,依次进入 Settings、Developer settings、OAuth Apps,然后选择 New OAuth App。将授权回调 URL 设置为 https://connect.example.com/oauth/callback。保存 client ID 和 client secret。

每次 /api 调用都会携带 admin token,因此请在当前 shell 会话中导出一次。

export ADMIN_TOKEN='paste-the-admin-token'
curl -s https://connect.example.com/api/oauth/configs \
  -H "authorization: Bearer $ADMIN_TOKEN"

该列表会显示运行时为每个提供商预期使用的重定向 URI,因此可最快确认 origin 配置是否已生效。如果仍显示 localhost,说明容器仍在使用旧值,OAuth 流程会在最后一步失败。

保存 client credentials,然后发起授权。

curl -s -X PUT https://connect.example.com/api/oauth/configs/github \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"clientId":"...","clientSecret":"..."}'

curl -s -X POST https://connect.example.com/api/oauth/authorizations \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"service":"github"}'

第二次调用会返回 authorizationUrl。在浏览器中打开它并批准请求的 scopes,随后提供商会将浏览器重定向回 /oauth/callback,运行时会在此处交换 code 并保存 credential。通过 origin 访问的 Web 控制台也会在相同的 admin token 保护下,通过表单引导您完成相同步骤。使用普通 API key 的提供商不需要这些步骤:使用 {"authType":"api_key","values":{"apiKey":"..."}} 调用 PUT /api/connections/<service> 会直接保存该 key。

为每个代理分配运行时令牌,不要使用凭据

代理使用由管理 API 签发的运行时令牌向网关进行身份验证。

curl -s -X POST https://connect.example.com/api/runtime-tokens \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"name":"research-agent"}'

响应会携带一个以 oct_ 开头的令牌。为每个代理签发一个令牌,并以该代理命名。否则,撤销无法识别的令牌时,必须撤销所有令牌。随后,代理通过普通 HTTP 调用操作。

curl -s -X POST https://connect.example.com/v1/actions/github.get_current_user \
  -H "authorization: Bearer oct_..." \
  -H 'content-type: application/json' \
  -d '{"input":{}}'

正常响应是一个信封,其中 success 字段为 true,提供商负载位于 data 下。该响应中不应包含 GitHub 令牌。对于 MCP 客户端,将其指向 https://connect.example.com/mcp,并使用相同的 bearer 请求头。网关会提供 search_actionsexecute_action 等发现工具,而不是为每个 API 提供一个工具,从而减少代理的工具列表。在 VPS 上运行 MCP 服务器介绍了这部分连接的客户端配置。

在确认配置完成前,再执行一次检查。删除 authorization 请求头,然后重复调用操作。项目自身的快速入门会在完全不提供 bearer 的情况下调用 /v1。因此,如果未配置运行时身份验证,任何能够访问该端口的人都可以执行操作。如果未认证调用成功,有两种处理方式:配置运行时令牌,并确认匿名调用现在会失败;或者在反向代理中,将 /api/v1/mcp 限制为代理可访问的地址。只有 /oauth/callback 必须对公网开放,因为提供商的浏览器重定向仅需要这一条路径。

将操作列表缩减到代理实际需要的范围

一个网关后面如果接入了上千个提供商,就不应将这么大的操作面直接交给语言模型。模型一旦开始读取并非由它生成的文本,操作面还会进一步扩大,因为您自己的 SearXNG 实例响应代理的 Web 搜索所返回的页面,可能包含针对代理可执行操作的指令。让编码代理采用能够生效的最小修改的同一原则,也适用于权限配置:只授予任务实际需要的少数操作,不要授予其他权限。两个控制项可以缩小操作面。

OOMOL_CONNECT_ALLOWED_ACTIONS接受以逗号分隔的允许列表,并支持 service.**OOMOL_CONNECT_BLOCKED_ACTIONS是拒绝列表,且拒绝列表优先。将允许列表设为 github.get_current_user,github.list_issues 后,其他所有操作都会被拒绝,无论代理请求执行什么操作;这正是普通错误与安全事件之间的区别。运行时令牌还会在全局规则之上附加自己的操作规则,并且其 allowedProxies 列表默认为空,因此在授予 POST /v1/proxy/:service 之前,该操作会被拒绝。该代理端点会在附加您的凭据后,将原始请求转发给提供商,因此除非某个特定代理确实需要,否则应将其留空。

OOMOL_CONNECT_ALLOW_PRIVATE_NETWORK默认为 false,可阻止自托管提供商连接指向私有地址,例如 169.254.169.254 上的云元数据服务,或同一网络中的数据库。保持关闭状态。只有对于您自行托管的提供商,才应将其开启。

备份存放所有令牌的服务器

有两项内容很重要,缺一不可。connector-data卷中的 /app/data/connect.sqlite 数据库保存着已密封的凭据。.env 中的加密密钥用于解封这些凭据。没有密钥,卷备份无法恢复任何内容;没有卷,密钥也无法恢复任何内容。因此,密钥应保存在密码管理器中,卷应纳入常规备份轮换。

复制 SQLite 文件时,请先停止容器。写入期间创建的副本可能会在恢复后成为损坏的数据库。

docker volume ls | grep connector-data
docker compose stop connector
docker run --rm -v open-connector_connector-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/connector-data.tgz -C /data .
docker compose start connector

卷名由项目目录加上 _connector-data 组成,因此需要执行第一个命令,然后将实际名称填入第三个命令。使用 从 VPS 创建 restic 备份 将归档发送到 VPS 外部。restic 会在归档离开 VPS 前对其加密,因为该归档就是凭据存储。

运行时会将最近的操作运行记录保存为审计记录,默认保存 5,000 条。因此,控制台可以显示哪个代理在何时执行了什么操作。代理行为异常时,应首先查看这份日志。同时,将 Uptime Kuma 状态页 指向 https://connect.example.com/health。网关停止响应时,代理会以难以判断原因的方式失败;确认网关已停止运行,可以节省一小时的代理输出排查时间。

会出现什么问题,以及您将看到的消息

提供商处的 redirect_uri_mismatch 源站地址与已注册的回调 URL 不一致。将 /api/oauth/configs 中的精确字符串与提供商的应用设置进行比较,包括将 httpshttp 进行比较,以及检查是否存在尾部斜杠。

每次 /api 调用都返回 401。 管理员令牌请求头缺失或拼写错误。请求头为 Authorization: Bearer <token>,Web 控制台也要求使用同一个令牌。

容器正在运行,但凭据以明文保存。 这是因为 OOMOL_CONNECT_ENCRYPTION_KEY 未到达容器。运行时会以未加密的方式存储凭据记录,而不是拒绝启动。您可以在自己的安装中验证这一点:使用一个便于识别的 API 密钥连接提供商,然后在数据库中搜索该密钥。

docker compose cp connector:/app/data/connect.sqlite /tmp/connect.sqlite
grep -c 'github_pat_' /tmp/connect.sqlite
shred -u /tmp/connect.sqlite

计数大于 0 表示密钥未生效,因此请检查 .env 是否与 compose.yaml 位于同一目录,并确认 docker compose config 显示了该值。设置密钥后,同样的搜索会返回 0,因为记录已使用 AES-256-GCM 加密(高级加密标准、256 位密钥、Galois/计数器模式)。

恢复后无法解密任何内容。 加密密钥已更改或丢失。按照设计,密钥不会写在数据旁边,因此没有恢复路径,提交支持工单也无法解决问题。请重新连接每个提供商。运行时支持通过单独的密钥变量和数据命令进行密钥轮换,因此在轮换前请先阅读当前版本说明。

代理报告错误,指出某个操作名称,而该操作明明能在目录中看到。 发现和执行是两个独立阶段。某个操作可以出现在 search_actions 中,但仍可能被 OOMOL_CONNECT_ALLOWED_ACTIONS、拒绝列表或该运行时令牌自身的规则拒绝。

升级。 备份卷,将镜像标签修改为新版本,然后 docker compose pull && docker compose up -d。监控 docker compose logs -n 50 connector,查找迁移日志行;之后重新运行健康检查和一个实际操作,确认一切正常后再恢复使用。回滚就是改回旧标签;之所以可行,是因为您固定了镜像标签。

FAQ

自托管 Open Connector 需要公网域名吗?

对于使用 API key 的提供商,不需要:127.0.0.1 上的网关就足够了。对于 OAuth,实际上需要。提供商会将浏览器重定向到您的回调 URL,因此该 URL 必须能从公网解析;除 localhost 外,提供商通常会拒绝纯 http://。首次启动前,将 OOMOL_CONNECT_ORIGIN 设置为您的 https:// 主机名,并在提供商的 OAuth 应用中注册 <origin>/oauth/callback

如果丢失 Open Connector 加密密钥,会发生什么?

存储的凭据将无法解密,并且无法恢复。该密钥有意不与数据存放在一起,因此任何持有数据库的人都无法读取凭据,包括您自己。您只能设置新密钥,然后重新连接每个提供商。请将密钥保存在密码管理器中,并将数据库纳入备份轮换,因为恢复数据时两者缺一不可。

我的 AI agent 能看到提供商访问令牌吗?

通过网关调用时不能。agent 使用以 oct_ 开头的运行时令牌进行身份验证,网关在服务器上将提供商凭据注入出站请求,并且只返回响应。有两种情况会破坏这一隔离:/v1/proxy/:service 端点会转发附带您凭据的原始请求,其授权范围默认为空是有原因的;您亲自将 API key 粘贴到 agent 中也会绕过网关。

网关应当能从公网访问吗?

只有 /oauth/callback 需要能从公网访问。在 127.0.0.1 上发布容器端口,使 Docker 的 NAT 规则无法将其暴露到防火墙之外,并在前面部署反向代理。然后在不带 authorization header 的情况下测试一次操作调用。如果调用成功,就在代理中将 /api/v1/mcp 限制为 agent 使用的地址,直到只有经过身份验证的调用可以正常工作。

Open Connector 已经适合用于生产环境了吗?

它采用 Apache 2.0 许可证,并且迭代很快:代码仓库于 29 June 2026 发布,v1.3.3 于 30 July 2026 发布。因此,请将本指南中的每个版本号都视为 1 August 2026 的快照。请固定使用某个 release tag 运行,不要使用 latesttip;每次升级前阅读 release notes,并保留一份已实际恢复过的 volume 备份。对于您拥有的服务器,这种设计是可靠的;风险来自版本频繁变化,而不是架构本身。