新的 API 密钥和非对称认证
Configure new API keys and ES256 asymmetric authentication for self-hosted Supabase.
你可以配置自托管的 Supabase,同时使用新的 API 密钥(new API keys)和旧的 API 密钥(ANON_KEY 和 SERVICE_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_SECRET、ANON_KEY和SERVICE_ROLE_KEY。快速开始(Linux) 会自动处理这些;手动操作则运行generate-keys.sh。 - 如果你正在升级现有的自托管 Supabase 环境,记得查看 更新日志 并添加/更新以下文件:
.env.example(将新部分合并到你的.env文件中)docker-compose.ymlutils/add-new-auth-keys.shutils/rotate-new-api-keys.shvolumes/api/kong-entrypoint.shvolumes/api/kong.yml
添加新钥匙 #
🌐 Adding the new keys
在你的项目目录里,你有 docker-compose.yml:
🌐 From your project directory where you have docker-compose.yml:
1sh 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 读取 JWT_SECRET,并把它作为对称密钥包含在 JWT_KEYS 和 JWT_JWKS 中。如果你之后更改了 JWT_SECRET,你也必须重新生成 JWKS。
🌐 The script reads JWT_SECRET from .env and includes it as a symmetric key inside both JWT_KEYS and JWT_JWKS. If you later change JWT_SECRET, you must regenerate the JWKS as well.
为了让新的认证正常工作,.env 文件中以下配置应该取消注释:
🌐 The following configuration should be uncommented in the .env file for the new authentication to work correctly:
1auth:2 environment:3 # JSON array of signing JWKs (EC private + legacy symmetric)4 GOTRUE_JWT_KEYS: ${JWT_KEYS:-[]}56rest:7 environment:8 # PostgREST accepts a plain-text symmetric secret, a single JWK, or a JWKS.9 PGRST_JWT_SECRET: ${JWT_JWKS:-${JWT_SECRET}}1011realtime:12 environment:13 # JWKS for token verification (EC public + legacy symmetric)14 API_JWT_JWKS: ${JWT_JWKS:-{"keys":[]}}1516storage:17 environment:18 # JWKS for token verification (EC public + legacy symmetric)19 JWT_JWKS: ${JWT_JWKS:-{"keys":[]}}嵌套变量插值(${A:-${B}})需要 podman-compose >= 1.6.0。早期版本(一些 Linux 发行版仍然提供)不支持它——如果你使用的是较旧的 podman-compose,要么升级,要么将每个嵌套表达式直接替换成所需的变量,具体替换方式请参见 docker-compose.yml 中的内联注释。
🌐 Nested variable interpolation (${A:-${B}}) requires podman-compose >= 1.6.0. Earlier versions (still shipped by some Linux distributions) do not support it - if you are on an older podman-compose, either upgrade or replace each nested expression with the required variable directly, see the inline comments in docker-compose.yml for the exact substitutions.
重启所有服务:
🌐 Restart all services:
1sh run.sh recreate新的 API 密钥格式 #
🌐 New API keys format
新的 API 密钥使用与 Supabase 平台相同的格式:
🌐 The new API keys use the same format as the Supabase platform:
1sb_publishable_<22-char-random>_<8-char-checksum>2sb_secret_<22-char-random>_<8-char-checksum>正在检查设置 #
🌐 Verifying the setup
使用新的密钥测试:
🌐 Test with the new secret key:
1curl 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:
1curl http://<your-domain>/rest/v1/ \2-H "apikey: your-service-role-key"两者都应该可以,并返回相同的结果。
🌐 Both should work and return the same result.
/rest/v1/ 是开放 API 根,需要管理员级别的密钥(sb_secret_* 或旧版 SERVICE_ROLE_KEY)。公共密钥(sb_publishable_* 或旧版 ANON_KEY)在这个端点会返回 403。
你也可以验证公共 JWKS 端点:
🌐 You can also verify the public JWKS endpoint:
1curl 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_publishable 和 sb_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_KEY | HS256 JWT | 现有: 旧版客户端 API 密钥。嵌入了带 role: "anon" 的 JWT。 |
SERVICE_ROLE_KEY | HS256 JWT | 现有: 旧版服务器端 API 密钥。嵌入的 JWT,带有 role: "service_role"。 |
SUPABASE_PUBLISHABLE_KEY | 不透明 | 新: 带校验和的短随机密钥。用于客户端,替代 ANON_KEY。 |
SUPABASE_SECRET_KEY | 不透明 | 新: 带校验和的短随机密钥。用于服务器端,替代 SERVICE_ROLE_KEY。 |
JWT_KEYS | JSON 数组 | 新: 包含新的非对称密钥对和旧对称密钥的签名 JWK JSON 数组。Auth 用它来签署令牌。 |
JWT_JWKS | JWKS(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 网关和服务配置层运行。
当设置 JWT_KEYS 时,Auth 会开始使用新的非对称 ES256 密钥对签署新的用户会话 JWT。确保所有验证令牌的服务(PostgREST、Realtime、Storage)都配置了 JWT_JWKS,这样它们就可以验证新的 ES256 和旧的 HS256 令牌。
🌐 When JWT_KEYS is set, Auth will start signing new user session JWTs with the new asymmetric ES256 key pair. Make sure all services that verify tokens (PostgREST, Realtime, Storage) are configured with JWT_JWKS so they can verify both the new ES256 and legacy HS256 tokens.
轮换新的 API 密钥 #
🌐 Rotating the new API keys
如果你的新 API 密钥被泄露,或者你想定期更换它们,你可以在不动非对称密钥对的情况下重新生成 sb_publishable 和 sb_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:
1sh utils/rotate-new-api-keys.sh --update-env更换密钥后,重启服务并用新密钥更新你的客户端应用:
🌐 After rotating, restart services and update your client applications with the new keys:
1sh run.sh recreate轮换新的 API 密钥不会使现有的用户会话失效。Auth 发放的用户会话 JWT 不受影响,因为它们是使用非对称密钥对进行验证的,而这个密钥对保持不变。
🌐 Rotating new API keys does not invalidate existing user sessions. User session JWTs issued by Auth are unaffected because they are verified using the asymmetric key pair, which remains unchanged.
正在重新生成非对称密钥对 #
🌐 Regenerating asymmetric key pair
如果 EC 私钥被泄露或你需要重新生成所有东西:
🌐 If the EC private key is compromised or you need to regenerate everything:
1sh 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 中。
重新生成非对称密钥会使所有 ES256 用户会话失效。如果你的用户有活跃会话,请安排一个维护时间。
🌐 Regenerating asymmetric keys invalidates all ES256 user sessions. Plan a maintenance window if your users have active sessions.
它是怎么运作的 #
🌐 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_xxx或Bearer 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:
1consumers:2 - username: anon3 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_role7 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_publishable 和 sb_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/* | PostgREST | 是 | Authorization |
/graphql/v1 | PostgREST | 是 | Authorization |
/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:
- 客户端发送
apikey: sb_publishable_xxx(或旧版apikey: eyJ...)。 - API 网关会检查密钥并识别消费者(
anon或service_role)。 - API 网关会检查
Authorization头。由于它要么不存在,要么以Bearer sb_(一个不透明密钥,而不是会话 JWT)开头,插件会将其替换为:- 新的
sb_键:Authorization头部设置为对应角色的内部预签名 ES256 JWT。 - 旧版 JWT 密钥:
Authorization头部设置为旧版 HS256 JWT(apikey值按原样复制)。
- 新的
- 上游服务在
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:
- 客户端会发送
Authorization: Bearer eyJ...(来自 Auth 的 JWT 会话令牌)以及apikey: sb_publishable_xxx(或老的apikey: eyJ...)。 - API 网关会检查 API 密钥并识别使用者。
- API 网关会检查
Authorization头。因为它存在而且不以Bearer sb_开头(它是真正的 JWT,而不是sb_API 密钥),所以插件会原样通过。无论apikey是新的sb_密钥还是旧的 JWT,这个过程都是一样的——网关只看Authorization头来判断用户会话是否存在。 - 上游服务会验证会话 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:2if authorization exists AND does not start with "Bearer sb_" then3 -- User session JWT: pass through unchanged4 keep authorization5elseif apikey matches secret key then6 -- Replace with pre-signed service_role ES256 JWT7 set authorization = "Bearer <service_role ES256 JWT>"8elseif apikey matches publishable key then9 -- Replace with pre-signed anon ES256 JWT10 set authorization = "Bearer <anon ES256 JWT>"11else12 -- Legacy JWT key: copy apikey as authorization13 set authorization = apikey14end额外资源 #
🌐 Additional resources
- 了解 API 密钥 - Supabase 平台上 API 密钥的工作原理
- 认证架构 - 认证服务如何处理身份验证和令牌签发
- JWT 签名密钥 - 关于管理 Supabase Auth 用于创建和验证 JSON Web 令牌的密钥的最佳实践
- JSON Web Token (JWT) - 如何在 Supabase 中最佳使用 JSON Web Token
- 使用 Docker 自托管 - 初始设置指南,包括旧密钥生成
在 GitHub 上:
🌐 On GitHub: