☰
OpenConnector 凭证管理与运行时存储实战:从 SQLite/PostgreSQL 存储、AES-256-GCM 加密到连接选择与 Action 策略
2026/10/10 5:59:29 网站建设 项目流程
  • 后端
  • API网关
  • LLM 网关

【免费下载链接】open-connector

Open-source auth gateway connecting 1500+ SaaS providers to AI agents through SDK, CLI, MCP, HTTP, and OpenAPI.

项目地址:https://gitcode.com/gh_mirrors/op/open-connector
点击查看免费下载

本篇技术指南以 OpenConnector 的凭证体系为主线,系统讲解运行时数据库(SQLite/PostgreSQL/Cloudflare D1)中连接、OAuth 客户端配置、运行时令牌、运行日志与幂等 Action 响应的存储与加密方式,并给出api_key、custom_credential、oauth2三类本地连接从创建、执行到密钥轮换的完整 HTTP 调用链,同时结合仓库源码解释凭证字段校验、连接身份(Connection Identity)与 Action/Proxy 双层策略的底层实现。读完本文,你将能够为本地或自托管部署安全地配置凭证存储、启用加密、管理多连接命名空间,并借助运行时令牌与控制面策略精细限定 AI Agent 可执行的权限面。

运行时存储概览:凭证存在哪里?

OpenConnector 的 Node 运行时把以下数据统一存放在 SQLite 或 PostgreSQL 中:

  • 各 provider 的连接(connection)与本地凭证;
  • OAuth 客户端配置(client id / secret);
  • 进行中的 OAuth 状态(pending state);
  • 运行时令牌(runtime token)及其哈希;
  • 近期运行日志;
  • HTTP Action 的幂等(idempotency)claim 与响应缓存。

Cloudflare Workers 运行时则把同样的运行时记录存放在 D1 中,把临时 transit 文件存放在 R2 中。

默认情况下,数据库文件位于:

./data/connect.sqlite

涉及存储位置的核心环境变量如下(完整表格见 configuration.md):

环境变量默认值作用
OOMOL_CONNECT_DATA_DIR./dataSQLite 数据库、本地 transit 文件与 Node 上传暂存目录;Docker 镜像默认指向/app/data,应挂载为 volume
OOMOL_CONNECT_DATABASE_URL未设置PostgreSQL 连接串;设置后 Node 运行时改用 PostgreSQL,否则使用 data 目录下的 SQLite

对应三种凭证形态,运行时存储策略并不相同:

  • no_authprovider 以**虚拟连接(virtual connection)**形式存在,不存储任何机密。从源码看,这类连接在src/connection-service.ts的createNoAuthConnectionSummary中生成,ID 形如${service}:${connectionName}(如hackernews:default),因此开源用户可以零配置运行公开 Action;
  • api_key与custom_credentialprovider 的本地机密保存在所选运行时数据库中;
  • oauth2provider 使用用户自带的 OAuth 客户端配置与运行时回调 URL,运行时不替你保管 provider 侧的应用密钥。

注意:PostgreSQL 场景下需要先通过npm run runtime:migrate初始化 schema,相关说明见 configuration.md。

SaaS OAuth 凭证:远端引用而非本地令牌

SaaS OAuth 连接保存的是一个远端账户引用(remote account reference),而不是本地 OAuth 凭证。Provider 令牌与应用机密保留在 SaaS 侧,Connect 只加密项目 API Key 与敏感的授权请求数据。从 connection-service.ts 的StoredSaasConnection定义可以看到,它携带managedProjectId、providerConfigId、externalUserId、connectedAccountId等引用字段,并且本地getCredential遇到 SaaS 连接会直接抛出unsupported_auth_type("SaaS credentials are not available locally")。

配置 SaaS 项目前必须满足两个前提:启用加密,且显式配置公开 origin(public origin)。配置细节、执行边界与故障处理参见 SaaS OAuth,备份恢复与独立 clone 重置的区别参见 maintenance。

加密:AES-256-GCM 与“不落盘原始幂等键”

设置OOMOL_CONNECT_ENCRYPTION_KEY即可对以下四类数据加密:已存储的凭证、OAuth 客户端配置、进行中的 OAuth state,以及已完成的幂等 Action 响应 payload:

OOMOL_CONNECT_ENCRYPTION_KEY="replace-with-a-long-random-secret" npm run dev

加密实现采用AES-256-GCM,覆盖:

  1. provider 凭证记录;
  2. OAuth 客户端配置;
  3. 进行中的 OAuth state;
  4. 为幂等 HTTP Action 重试而保留的已完成响应。

安全性上还有两点关键设计:

  • 原始Idempotency-Key永不落盘——数据库中只保存它的哈希与请求指纹;claim 标识符、state、时间戳与过期时间作为未加密元数据存储;
  • 加密密钥不由 OpenConnector 存储,一旦丢失,加密记录无法恢复。

未设置OOMOL_CONNECT_ENCRYPTION_KEY时,运行时仍可正常运行(仅打印启动警告),适合本地开发;但此时凭证、OAuth 客户端配置、pending OAuth state 与已完成的幂等响应都以明文存储。由于 Action 响应可能包含敏感的 provider 数据,即使响应已不再具备重放资格,也应把connect.sqlite、PostgreSQL 或 D1 视为敏感数据存储。

关于幂等响应的生命周期:已完成幂等 Action 响应的重放窗口为24 小时。过期的幂等记录会在后续幂等 Action 请求 claim 同一 key 时被机会式删除——注意这并不意味着 24 小时到期后物理删除是必然保证。

Credential Fields:以 provider catalog 为契约的字段校验

凭证字段由每个 provider 的 catalogauth元数据声明,运行时把这份元数据视为本地 API 请求的契约:

  • api_key连接必须提供values.apiKey;
  • api_key连接可额外声明extraFields;
  • custom_credential连接必须恰好提供 provider 声明的fields;
  • oauth2客户端配置可声明额外的clientConfigFields。

提交的所有字符串值会被trim,空字符串视为缺失;未知字段会被拒绝而非静默存储——这是为了让凭证表单、脚本与 provider 定义在漂移时快速失败(fail fast)。

源码实现位于 src/core/credential-fields.ts 的normalizeCredentialValues:

  • 先用stringRecord规整输入;
  • 遍历提交键,凡不在 provider 声明字段集合中的一律抛出Unexpected credential field: <key>;
  • 再遍历声明的必需字段,缺失时抛出<key> is required.。

写 setup 脚本前,建议先检查目标 provider 的声明结构:

curl -s http://localhost:3000/api/providers/github

Connection Identity:让 Agent 知道“以谁的身份”执行

当 provider 能以较低成本对凭证调用 current-user / current-account 类端点做校验时,其 validator 会存下一个稳定的连接档案(profile):

  • accountId:provider 侧的用户、工作区、机器人、账户或令牌标识;
  • displayName:人类可读的账户标签;
  • grantedScopes:已知的 provider 原生授权 scope。

该档案会出现在/api/connections、MCP Action 发现、Action agent 指南与近期运行日志中。Agent 应利用它判断某个 Action 将以哪个账户执行——原始 provider 令牌永不暴露。

此外,OAuth 连接在/api/connections中携带oauthAuthorizationId:即完成其 consent 的回调state。它只在新 consent 完成时变化,令牌刷新不会改变它,因此调用方可以区分“全新授权”与“被替换的旧凭证”。该字段出现之前建立的连接会省略它。源码层面,src/connection-service.ts 在构建 summary 时明确只暴露这个运行时持有的字符串,其余 credential metadata(含客户端密钥)一律不输出。

查看当前连接:

curl -s http://localhost:3000/api/connections

API Key 连接:创建、命名与执行

创建或替换默认 API Key 连接:

curl -s -X PUT http://localhost:3000/api/connections/github \ -H 'content-type: application/json' \ -d '{"authType":"api_key","values":{"apiKey":"github_pat_..."}}'

创建或替换命名连接(connectionName即连接别名):

curl -s -X PUT http://localhost:3000/api/connections/github \ -H 'content-type: application/json' \ -d '{"authType":"api_key","connectionName":"work","values":{"apiKey":"github_pat_..."}}'

接受的键为apiKey加上 providerauth[].extraFields声明的内容(由normalizeCredentialValues强制执行,见上文)。连接名有格式约束,源码 src/connection-service.ts 中connectionNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/,默认连接名常量为"default"。

用默认连接执行一个 Action:

curl -s -X POST http://localhost:3000/v1/actions/github.get_current_user \ -H 'content-type: application/json' \ -d '{"input":{}}'

Custom Credential 连接

创建或替换默认 custom credential 连接:

curl -s -X PUT http://localhost:3000/api/connections/example \ -H 'content-type: application/json' \ -d '{"authType":"custom_credential","values":{"host":"localhost","password":"..."}}'

接受的键来自 providerauth[].fields的声明。

OAuth2 连接:自带 OAuth App 的完整流程

OAuth2 provider 需要你自建 provider 侧 OAuth App。先列出支持 OAuth 的 provider,并复制对应服务的expectedRedirectUri:

curl -s http://localhost:3000/api/oauth/configs

把该回调 URL 原样粘贴到 provider OAuth App 中。默认端口下 GitHub 使用:

http://localhost:3000/oauth/callback

如果浏览器是通过其他 origin 到达运行时(例如隧道),须在启动前设置OOMOL_CONNECT_ORIGIN:

OOMOL_CONNECT_ORIGIN="https://your-tunnel.example" npm run dev

随后使用/api/oauth/configs返回的新expectedRedirectUri。

保存本地客户端配置:

curl -s -X PUT http://localhost:3000/api/oauth/configs/github \ -H 'content-type: application/json' \ -d '{"clientId":"...","clientSecret":"..."}'

收敛授权面:requestedScopes 与 effectiveScopes

默认情况下,授权请求会包含 provider 声明的全部 scope。需要更小权限面的部署,可在 OAuth 客户端配置中保存requestedScopes:

curl -s -X PUT http://localhost:3000/api/oauth/configs/github \ -H 'content-type: application/json' \ -d '{"clientId":"...","clientSecret":"...","requestedScopes":["read:user"]}'

约束条件:每个请求的 scope 必须来自 provider 声明的auth[].scopes,运行时拒绝未知 scope(拒绝静默扩张授权);省略requestedScopes则保持 provider 默认;存在时数组至少包含一个 scope。配置摘要会同时暴露requestedScopes与计算结果effectiveScopes。

自定义回调 URI:redirectUri

若 provider OAuth App 注册的是不同的回调 URI(例如原生 App 的自定义 scheme),可将其保存为redirectUri:

curl -s -X PUT http://localhost:3000/api/oauth/configs/example \ -H 'content-type: application/json' \ -d '{"clientId":"...","clientSecret":"...","redirectUri":"myapp://oauth/callback"}'

关键约束:

  • 必须是不含 user info 或 fragment 的绝对 URI,按保存值原样发送;
  • 接受自定义 scheme,但javascript:、vbscript:、data:、file:、blob:、about:被拒绝;
  • 授权请求与 code 交换都会携带该 URI,expectedRedirectUri也会报告它,便于注册到 provider;
  • 省略或传空字符串则回到运行时回调;每次PUT会整体替换配置,因此保存时必须带上redirectUri;
  • 运行时的/oauth/callback路由不变:拥有该回调的 App 把 provider 的回调查询参数(code、state或error)转发给它即可,其余 provider 继续使用运行时回调。

部分 provider 在auth[].clientConfigFields中声明额外 OAuth 客户端字段,需以extra提交。

启动授权与命名连接

启动授权:

curl -s -X POST http://localhost:3000/api/oauth/authorizations \ -H 'content-type: application/json' \ -d '{"service":"github"}'

在浏览器中打开返回的authorizationUrl;provider 重定向到本地回调后,运行时把 OAuth 凭证存为默认连接。

如需存为命名连接,在启动授权时携带connectionName:

curl -s -X POST http://localhost:3000/api/oauth/authorizations \ -H 'content-type: application/json' \ -d '{"service":"github","connectionName":"work"}'

连接级(connection-scoped)自定义 OAuth App

控制台可以在不改动全局配置的前提下,用自定义 App 启动一次连接级 OAuth 流程。前提:设置OOMOL_CONNECT_ALLOWED_CUSTOM_OAUTH为*或逗号分隔的 provider 列表,并设置OOMOL_CONNECT_ENCRYPTION_KEY。然后在授权请求中直接带上clientId(以及 provider 要求时的clientSecret,外加 provider 声明的extra/secretExtra字段):

curl -s -X POST http://localhost:3000/api/oauth/authorizations \ -H 'content-type: application/json' \ -d '{"service":"github","connectionName":"work","clientId":"...","clientSecret":"..."}'

连接级 OAuth 客户端请求同样可以携带校验过的requestedScopes子集。回调 URL 仍是部署的/oauth/callback(例如https://connect.example.com/oauth/callback),该连接会保留所提交的 App 值用于后续令牌刷新;省略全部客户端字段则继续使用全局配置。

最后,请像对待任何存有 API Key 或 OAuth 令牌的存储一样,保护好所选运行时数据库。

执行时的连接选择:默认、别名与令牌级限制

未提供别名时使用默认连接:

curl -s -X POST http://localhost:3000/v1/actions/github.get_current_user \ -H 'content-type: application/json' \ -d '{"input":{}}'

已存在命名连接时,用请求头x-oo-connector-alias选择:

curl -s -X POST http://localhost:3000/v1/actions/github.get_current_user \ -H 'x-oo-connector-alias: work' \ -H 'content-type: application/json' \ -d '{"input":{}}'

也接受alias查询参数:

curl -s -X POST "http://localhost:3000/v1/actions/github.get_current_user?alias=work" \ -H 'content-type: application/json' \ -d '{"input":{}}'

持久运行时令牌的 allowedConnections

持久运行时令牌可用allowedConnections进一步限制该选择:

  • 省略或传空列表 = 所有已存连接都可用;
  • 列表条目是创建/列出连接时返回的稳定不透明 ID;
  • 上面的alias=work选择work连接,其id必须在授权列表内;未命名请求选择默认连接,需要其 ID 在列;
  • 其他连接在凭证加载前即返回403 connection_not_allowed;
  • 虚拟no_auth连接无需授权。

从源码看,这一判定由 src/core/action-policy.ts 的evaluateConnection完成:allowedConnections为空则直接放行,否则按 ID 精确匹配,未命中返回connection_not_allowed。

完整示例:保留一个共享默认 GitHub 连接 + 一个work连接,然后签发一个无限制令牌与一个仅限 work 的令牌:

curl -s -X PUT http://localhost:3000/api/connections/github \ -H 'content-type: application/json' \ -d '{"authType":"api_key","values":{"apiKey":"github_pat_default"}}' curl -s -X PUT http://localhost:3000/api/connections/github \ -H 'content-type: application/json' \ -d '{"authType":"api_key","connectionName":"work","values":{"apiKey":"github_pat_work"}}' curl -s -X POST http://localhost:3000/api/runtime-tokens \ -H 'content-type: application/json' \ -d '{"name":"shared-client","allowedActions":[],"blockedActions":[],"allowedProxies":[]}' curl -s -X POST http://localhost:3000/api/runtime-tokens \ -H 'content-type: application/json' \ -d '{"name":"work-client","allowedActions":[],"blockedActions":[],"allowedProxies":[],"allowedConnections":["<work-connection-id>"]}'

数据重置与加密密钥轮换

重置 Node 运行时在所选 SQLite/PostgreSQL 数据库中的数据:

npm run runtime:data -- reset --yes

轮换 Node 运行时的数据加密密钥:

OOMOL_CONNECT_ENCRYPTION_KEY="old-secret" \ OOMOL_CONNECT_NEW_ENCRYPTION_KEY="new-secret" \ npm run runtime:data -- rotate-key

仅在确实想要明文存储时才移除加密(--plain会把记录改写为明文):

OOMOL_CONNECT_ENCRYPTION_KEY="old-secret" \ npm run runtime:data -- rotate-key --plain

PostgreSQL 场景下,在 reset 或 rotate 命令上设置OOMOL_CONNECT_DATABASE_URL;schema 必须已通过npm run runtime:migrate初始化。密钥轮换会重新编码:已存凭证、OAuth 客户端配置、pending OAuth state、已完成的幂等 Action 响应 payload;幂等键哈希、请求指纹、claim 状态与时间戳保持未加密元数据不变。

重要操作前提:在重置数据或轮换 PostgreSQL 加密密钥之前,先暂停所有 Node 运行时实例。

runtime:data支持 Node 的 SQLite 与 PostgreSQL 后端;Cloudflare 场景请直接用 Cloudflare 工具备份/恢复 D1 与 R2。

OAuth 令牌自动刷新

OAuth 访问令牌在满足两个条件时自动刷新:令牌已过期,且 provider 签发了 refresh token。刷新后的凭证写回所选 Node 运行时数据库,配置了OOMOL_CONNECT_ENCRYPTION_KEY时加密写入。

实现上,src/connection-service.ts 的resolveOAuthCredential先判断isOAuthCredentialExpired;已过期且有 refresh token 时走refreshOAuthCredential,通过 per-connection 的 promise 缓存(key 为connection.id:revision)避免并发重复刷新,刷新后写回失败(连接已被替换)则抛出connection_not_found提示重试。

如果令牌过期且没有 refresh token,需在本地运行时重新连接该 provider。注意部分 provider(如 Google)可能要求携带请求 offline access 的授权参数——provider 定义在预期需要 refresh token 时应包含这些参数。

本地 API 访问与令牌体系

服务器默认绑定127.0.0.1。仅当运行时需要从本机/容器外部可达时才设置HOST=0.0.0.0。

当 admin API 或 Web 控制台需要暴露到自身 shell 之外时,设置 admin bearer 令牌:

OOMOL_CONNECT_ADMIN_TOKEN="replace-with-an-admin-token" npm run dev

Admin 客户端访问/api、/docs或 Web 控制台时携带:

Authorization: Bearer replace-with-an-admin-token

为/v1与/mcp调用方签发运行时令牌,可以从 Web 控制台的 Access 页或POST /api/runtime-tokens完成。令牌在创建时仅显示一次,数据库只保存其哈希。运行时客户端发送:

Authorization: Bearer oct_...

持久令牌可独立配置 Action 规则、provider proxy 授权与可选的连接授权:

  • allowedProxies默认为空,即拒绝一切/v1/proxy/:service;仅在客户端确实需要 proxy 访问时添加某个 provider service 或*;
  • 省略allowedConnections或传[]= 连接访问不受限;更新时必须发送该字段,否则一次PUT可能把已有限制清掉;
  • 非空列表是连接 API 返回的稳定不透明 ID 的精确白名单;未命名请求选择默认连接,除非其 ID 在列,否则被拒;
  • 虚拟no_auth连接无需授权;
  • HTTP、MCP 与 proxy 调用方在凭证查找之前即收到connection_not_allowed;
  • 运行时发现(discovery)会被过滤,但GET /api/connections与 Actionagent.md指南不受过滤。

OOMOL_CONNECT_RUNTIME_TOKEN仍被接受,用于引导脚本与向后兼容。内置 Web 控制台会从运行时接收一个同站本地 cookie,以便在启用 API 令牌认证时继续工作。

Action Policy 与 Proxy Policy:双层权限控制面

用OOMOL_CONNECT_ALLOWED_ACTIONS仅向 HTTP 与 MCP 执行暴露选定 Action:

OOMOL_CONNECT_ALLOWED_ACTIONS="hackernews.*,github.get_current_user" npm run dev

用OOMOL_CONNECT_BLOCKED_ACTIONS在更宽 allowlist 之内精确拒绝某些 Action:

OOMOL_CONNECT_ALLOWED_ACTIONS="github.*" \ OOMOL_CONNECT_BLOCKED_ACTIONS="github.delete_repository" \ npm run dev

Provider proxy 请求使用独立的 service 级策略变量,因为/v1/proxy/:service可以触达 curated Action catalog 之外的 provider API 端点。Action 策略与 proxy 策略相互独立:Action 变量永不限制 proxy,proxy 变量也永不限制 Action。在部署层与运行时层,每个 provider proxy 默认允许,直到你加以限制:

OOMOL_CONNECT_ALLOWED_PROXIES="github" npm run dev

持久运行时令牌必须通过其独立的allowedProxies列表额外授权对应 provider;令牌授权与部署/运行时 proxy 策略取交集,令牌无法拓宽部署策略,空令牌 proxy 授权 = 拒绝所有 provider proxy。

OOMOL_CONNECT_BLOCKED_PROXIES="*"可整体禁用/v1/proxy/:service。需要同时收紧两个面时:

OOMOL_CONNECT_ALLOWED_ACTIONS="github.get_current_user" \ OOMOL_CONNECT_ALLOWED_PROXIES="github" \ npm run dev

OOMOL_CONNECT_BLOCKED_PROXIES也可在OOMOL_CONNECT_ALLOWED_PROXIES为*时精确剔除某个 provider:

OOMOL_CONNECT_ALLOWED_PROXIES="*" \ OOMOL_CONNECT_BLOCKED_PROXIES="github" \ npm run dev

规则语法:Action 策略条目是逗号分隔的 Action id,gmail.*匹配该 provider 的全部 Action,裸*匹配所有 Action;proxy 策略条目是逗号分隔的 provider service 名,或*表示所有 provider proxy。

底层匹配逻辑见 src/core/action-policy.ts 的compileActionRule与compileProxyRule:Action 规则对*恒真、对xxx.*前缀匹配、否则精确匹配;proxy 规则仅支持*与精确 service 名。策略按deployment → runtime → token三层编译为不可变快照(ActionPolicySnapshot),同一请求内的所有策略消费者共享同一视图,block 规则优先于 allow 规则,各层 allowlist 取交集、只要任一非空层未命中即拒绝——这正是“令牌不能拓宽部署策略”的实现根源。

小结:把“存哪、怎么加密、谁能用哪个连接”管清楚

从存储选址(OOMOL_CONNECT_DATA_DIR/OOMOL_CONNECT_DATABASE_URL)、加密开关(OOMOL_CONNECT_ENCRYPTION_KEY)到三类凭证连接(api_key / custom_credential / oauth2)的创建与执行、连接别名与令牌级allowedConnections选择、密钥轮换,以及 Action/Proxy 双层策略,OpenConnector 把“凭证”抽象成了 Agent 可以安全消费的一等公民:Agent 看到的只是连接身份与授权结果,原始令牌始终留在受保护的运行时存储中。部署时请记住三条底线:把connect.sqlite/PostgreSQL/D1 当敏感数据对待;加密密钥一旦丢失即不可恢复;任何暴露到外部的 admin 接口都必须用令牌保护。

  • 后端
  • API网关
  • LLM 网关

【免费下载链接】open-connector

Open-source auth gateway connecting 1500+ SaaS providers to AI agents through SDK, CLI, MCP, HTTP, and OpenAPI.

项目地址:https://gitcode.com/gh_mirrors/op/open-connector
点击查看免费下载

相关推荐

上一篇:Cloud Hypervisor 的 Windows 客户机支持完全指南:镜像制备、启动配置、网络与内核调试
下一篇:Decap CMS GitLab 后端深度解析:从 REST API 封装到 Editorial Workflow 的 Merge Request 标签机制

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询