Skip to content
Self-Hosting

新的 API 密钥和非对称认证

Configure new API keys and ES256 asymmetric authentication for self-hosted Supabase.

你可以配置自托管的 Supabase,同时使用新的 API 密钥(new API keys)和旧的 API 密钥(ANON_KEYSERVICE_ROLE_KEY HS256 签名的 JWTs)。

🌐 You can configure self-hosted Supabase to use the new API keys alongside the legacy API keys (ANON_KEY and SERVICE_ROLE_KEY HS256-signed JWTs).

在你开始之前 #

🌐 Before you begin

  • 完成 Docker 设置指南,确保在你的 .env 文件中设置了 JWT_SECRETANON_KEYSERVICE_ROLE_KEY快速开始(Linux) 会自动处理这些;手动操作则运行 generate-keys.sh
  • 如果你正在升级现有的自托管 Supabase 环境,记得查看 更新日志 并添加/更新以下文件:
    • .env.example(将新部分合并到你的 .env 文件中)
    • docker-compose.yml
    • utils/add-new-auth-keys.sh
    • utils/rotate-new-api-keys.sh
    • volumes/api/kong-entrypoint.sh
    • volumes/api/kong.yml

添加新钥匙 #

🌐 Adding the new keys

在你的项目目录里,你有 docker-compose.yml

🌐 From your project directory where you have docker-compose.yml:

1
sh utils/add-new-auth-keys.sh --update-env

这会生成新的配置环境变量并将它们写入 .env。如果没有 --update-env,脚本会打印这些值并以交互方式提示你。

🌐 This generates new configuration environment variables and writes them to .env. Without --update-env, the script prints the values and prompts you interactively.

为了让新的认证正常工作,.env 文件中以下配置应该取消注释:

🌐 The following configuration should be uncommented in the .env file for the new authentication to work correctly:

docker-compose.yml
1
auth:
2
environment:
3
# JSON array of signing JWKs (EC private + legacy symmetric)
4
GOTRUE_JWT_KEYS: ${JWT_KEYS:-[]}
5
6
rest:
7
environment:
8
# PostgREST accepts a plain-text symmetric secret, a single JWK, or a JWKS.
9
PGRST_JWT_SECRET: ${JWT_JWKS:-${JWT_SECRET}}
10
11
realtime:
12
environment:
13
# JWKS for token verification (EC public + legacy symmetric)
14
API_JWT_JWKS: ${JWT_JWKS:-{"keys":[]}}
15
16
storage:
17
environment:
18
# JWKS for token verification (EC public + legacy symmetric)
19
JWT_JWKS: ${JWT_JWKS:-{"keys":[]}}

重启所有服务:

🌐 Restart all services:

1
sh run.sh recreate

新的 API 密钥格式 #

🌐 New API keys format

新的 API 密钥使用与 Supabase 平台相同的格式:

🌐 The new API keys use the same format as the Supabase platform:

1
sb_publishable_<22-char-random>_<8-char-checksum>
2
sb_secret_<22-char-random>_<8-char-checksum>

正在检查设置 #

🌐 Verifying the setup

使用新的密钥测试:

🌐 Test with the new secret key:

1
curl http://<your-domain>/rest/v1/ \
2
-H "apikey: your-supabase-secret-key"

你应该会收到来自 PostgREST 的有效响应。然后确认旧版服务角色密钥仍然可用:

🌐 You should receive a valid response from PostgREST. Then verify that the legacy service role key still works:

1
curl http://<your-domain>/rest/v1/ \
2
-H "apikey: your-service-role-key"

两者都应该可以,并返回相同的结果。

🌐 Both should work and return the same result.

你也可以验证公共 JWKS 端点:

🌐 You can also verify the public JWKS endpoint:

1
curl http://<your-domain>/auth/v1/.well-known/jwks.json

这应该返回 EC 公钥(对称密钥被排除在外)。第三方服务可以使用这个接口获取公钥,并在不需要私钥的情况下验证非对称用户会话的 JWT。

🌐 This should return the EC public key (the symmetric key is excluded). Third-party services can use this endpoint to obtain the public key and verify asymmetric user session JWTs without needing the private key.

环境变量配置 #

🌐 Environment variables configuration

.env.example 中,新变量默认是空值。当为空时,API 网关和所有服务会以仅传统模式运行:sb_publishablesb_secret 的 API 密钥未配置。

🌐 New variables default to empty values in .env.example. When empty, the API gateway and all services operate in legacy-only mode: sb_publishable and sb_secret API keys are not configured.

环境变量(已有和新建)类型描述
JWT_SECRET对称密钥现有: 用于签署和验证 HS256 JWT 的共享密钥。被多个服务使用。
ANON_KEYHS256 JWT现有: 旧版客户端 API 密钥。嵌入了带 role: "anon" 的 JWT。
SERVICE_ROLE_KEYHS256 JWT现有: 旧版服务器端 API 密钥。嵌入的 JWT,带有 role: "service_role"
SUPABASE_PUBLISHABLE_KEY不透明新: 带校验和的短随机密钥。用于客户端,替代 ANON_KEY
SUPABASE_SECRET_KEY不透明新: 带校验和的短随机密钥。用于服务器端,替代 SERVICE_ROLE_KEY
JWT_KEYSJSON 数组新: 包含新的非对称密钥对和旧对称密钥的签名 JWK JSON 数组。Auth 用它来签署令牌。
JWT_JWKSJWKS(JSON)新: 包含新的公钥和旧的对称密钥。PostgREST、Realtime 和 Storage 用它来验证令牌。

与 Supabase 平台的不同 #

🌐 Differences from the Supabase platform

  • 每个角色一个密钥。 自托管的 Supabase 支持单个 sb_publishable 和单个 sb_secret。该平台允许每个项目创建多个 sb_ 密钥。
  • 不进行校验和验证。 不透明密钥使用与平台相同的格式(sb_publishable_<random>_<checksum>),但是 API 网关不会验证校验和。API 网关会将密钥作为不透明字符串进行匹配。

向后兼容 #

🌐 Backward compatibility

新的认证配置完全向后兼容:

🌐 The new authentication configuration is fully backward compatible:

  • **API网关同时接受两种密钥类型。**你可以逐步迁移客户端——有些使用老的API密钥,有些使用新的。
  • JWKS 包含对称密钥。 JWT_JWKS 同时包含 EC 公钥(用于验证新的 ES256 令牌)以及旧的 JWT_SECRET 作为对称 JWK(用于验证旧的 HS256 令牌)。接收到 JWT_JWKS 的服务可以验证这两种类型的令牌。
  • 不需要更改数据库。 非对称密钥系统完全在 API 网关和服务配置层运行。

轮换新的 API 密钥 #

🌐 Rotating the new API keys

如果你的新 API 密钥被泄露,或者你想定期更换它们,你可以在不动非对称密钥对的情况下重新生成 sb_publishablesb_secret

🌐 If your new API keys are compromised or you want to rotate them periodically, you can regenerate sb_publishable and sb_secret without touching the asymmetric key pair:

1
sh utils/rotate-new-api-keys.sh --update-env

更换密钥后,重启服务并用新密钥更新你的客户端应用:

🌐 After rotating, restart services and update your client applications with the new keys:

1
sh run.sh recreate

正在重新生成非对称密钥对 #

🌐 Regenerating asymmetric key pair

如果 EC 私钥被泄露或你需要重新生成所有东西:

🌐 If the EC private key is compromised or you need to regenerate everything:

1
sh utils/add-new-auth-keys.sh --update-env

这会生成一个新的 EC P-256 密钥对、新的 JWKS、新的非对称 JWT,以及新的 sb_ API 密钥。更新 .env 并重启服务后:

🌐 This generates a new EC P-256 key pair, new JWKS, new asymmetric JWTs, and new sb_ API keys. After updating .env and restarting services:

  • 新用户会话令牌将使用新的EC密钥进行签名。
  • 使用旧 EC 密钥签署的现有用户会话令牌将验证失败。用户需要重新登录。
  • 用旧的对称密钥(JWT_SECRET)签署的现有用户会话令牌仍然可以使用,因为 JWT_SECRET 没有改变,并且仍然包括在新的 JWKS 中。

它是怎么运作的 #

🌐 How it works

下面是关于新认证架构的一些详细说明。

🌐 Below are a few notes on the details of the new authentication architecture.

客户端 SDK 发送的内容 #

🌐 What client SDK sends

通过 supabase-js 的每个请求都包含两个头信息:

🌐 Every request via supabase-js includes two headers:

  • apikey - API 密钥(sb_ 或旧版 JWT)
  • Authorization - 未认证时,客户端 SDK 会把 API 密钥复制到这里(Bearer sb_publishable_xxxBearer eyJ...)。认证后,这里会包含由 Auth 生成的用户会话 JWT。

对于 实时 WebSocket 连接,API 密钥作为 ?apikey= 查询参数发送在升级 URL 中,而不是使用 apikey 头。

🌐 For Realtime WebSocket connections, the API key is sent as a ?apikey= query parameter in the upgrade URL instead of an apikey header.

存储边缘函数可以在没有 API 密钥的情况下接受请求。这些服务自行处理认证。

Kong API 网关路由 #

🌐 Kong API gateway routing

Kong 配置了两个消费者,每个消费者都接受旧的和新的 API 密钥:

🌐 Kong is configured with two consumers that each accept both the legacy and new API keys:

volumes/api/kong.yml
1
consumers:
2
- username: anon
3
keyauth_credentials:
4
- key: $SUPABASE_ANON_KEY # legacy HS256 JWT (ANON_KEY)
5
- key: $SUPABASE_PUBLISHABLE_KEY # new opaque key (omitted when not configured)
6
- username: service_role
7
keyauth_credentials:
8
- key: $SUPABASE_SERVICE_KEY # legacy HS256 JWT (SERVICE_ROLE_KEY)
9
- key: $SUPABASE_SECRET_KEY # new opaque key (omitted when not configured)

当还没有添加新的 API 密钥时,kong-entrypoint.sh 脚本会在 Kong 加载配置之前删除空的凭证条目。

🌐 When new API keys have not been added yet, the kong-entrypoint.sh script removes the empty credential entries before Kong loads the config.

为了帮助进行授权流程,kong.yml 中的一个专门配置会用仅限网关内部的预签名 JWT 替代 sb_publishablesb_secret 的 API 密钥。这些预签名 JWT 也会在 .env 中自动配置,但不应在任何应用代码中使用。

🌐 To assist with the authorization flows a specialized configuration in kong.yml substitutes internal, gateway-level-only pre-signed JWTs for sb_publishable and sb_secret API keys. These pre-signed JWTs are also auto-configured in .env but should not be used in any application code.

路由服务是否需要 API 密钥头信息替换
/auth/v1/*认证Authorization
/rest/v1/*PostgRESTAuthorization
/graphql/v1PostgRESTAuthorization
/realtime/v1/api/tenants实时 (REST)拒绝(被阻止)-
/realtime/v1/api/openapi实时 (REST)拒绝(被阻止)-
/realtime/v1/api/*实时 (REST)Authorization
/realtime/v1/*实时 (WebSocket)x-api-key
/storage/v1/*存储Authorization
/functions/v1/*边缘函数-

请求流程 #

🌐 Request flows

API 网关(Kong)的配置有逻辑来决定上游服务(例如 Auth)接收哪个 Authorization 头。这个逻辑处理两种情况:只携带 API 密钥(没有用户会话)的请求,以及携带用户会话 JWT 的请求。

🌐 The API gateway (Kong) configuration has the logic to decide what Authorization header the upstream service, such as Auth, receives. The logic handles two cases: requests that only carry an API key (no user session), and requests that carry a user session JWT.

未认证的请求(仅 API 密钥,无用户会话 JWT) #

🌐 Unauthenticated requests (API key only, no user session JWT)

当客户端只发送带有 API 密钥的 apikey 头(没有 Authorization 头),或者还在 Authorization 中通过 supabase-js 复制了 API 密钥时:

🌐 When the client sends only an apikey header with the API key (no Authorization header), or also the API key duplicated in Authorization by supabase-js:

  1. 客户端发送 apikey: sb_publishable_xxx(或旧版 apikey: eyJ...)。
  2. API 网关会检查密钥并识别消费者(anonservice_role)。
  3. API 网关会检查 Authorization 头。由于它要么不存在,要么以 Bearer sb_(一个不透明密钥,而不是会话 JWT)开头,插件会将其替换为:
    • 新的 sb_ 键: Authorization 头部设置为对应角色的内部预签名 ES256 JWT。
    • 旧版 JWT 密钥: Authorization 头部设置为旧版 HS256 JWT(apikey 值按原样复制)。
  4. 上游服务在 Authorization 接收到一个有效的 JWT,并使用 JWT_JWKS(或 JWT_SECRET)进行验证。

已认证的请求(用户会话 JWT) #

🌐 Authenticated requests (user session JWT)

当客户端之前通过 Auth 登录并且有有效的用户会话 JWT 令牌时:

🌐 When the client has previously signed in through Auth and has a valid user session JWT token:

  1. 客户端会发送 Authorization: Bearer eyJ...(来自 Auth 的 JWT 会话令牌)以及 apikey: sb_publishable_xxx(或老的 apikey: eyJ...)。
  2. API 网关会检查 API 密钥并识别使用者。
  3. API 网关会检查 Authorization 头。因为它存在而且Bearer sb_ 开头(它是真正的 JWT,而不是 sb_ API 密钥),所以插件会原样通过。无论 apikey 是新的 sb_ 密钥还是旧的 JWT,这个过程都是一样的——网关只看 Authorization 头来判断用户会话是否存在。
  4. 上游服务会验证会话 JWT。如果 Auth 用 ES256 签名(当配置了 JWT_KEYS 时),验证会使用 EC 公钥。如果 Auth 用 HS256 签名(旧版本),验证会使用对称密钥。两个密钥都可以在 JWT_JWKS 中找到。

kong.yml 中的 request-transformer 表达式将其实现为一个单独的 Lua 条件语句:

🌐 The request-transformer expression in kong.yml implements this as a single Lua conditional:

1
-- Pseudocode for the Authorization header logic:
2
if authorization exists AND does not start with "Bearer sb_" then
3
-- User session JWT: pass through unchanged
4
keep authorization
5
elseif apikey matches secret key then
6
-- Replace with pre-signed service_role ES256 JWT
7
set authorization = "Bearer <service_role ES256 JWT>"
8
elseif apikey matches publishable key then
9
-- Replace with pre-signed anon ES256 JWT
10
set authorization = "Bearer <anon ES256 JWT>"
11
else
12
-- Legacy JWT key: copy apikey as authorization
13
set authorization = apikey
14
end

额外资源 #

🌐 Additional resources

在 GitHub 上:

🌐 On GitHub: