ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Hindsight Supabase 租户扩展深度解析:本地 JWKS 验证、按用户 Schema 隔离与内置版本迁移

Hindsight Supabase 租户扩展深度解析:本地 JWKS 验证、按用户 Schema 隔离与内置版本迁移 Hindsight Supabase 租户扩展深度解析本地 JWKS 验证、按用户 Schema 隔离与内置版本迁移【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight本篇指南聚焦 Hindsight 仓库中supabase-tenant扩展它如何用 Supabase Auth 的 JWT 完成请求认证并把每个用户的记忆隔离到独立的 PostgreSQL schema 中。读完后你将掌握该扩展的完整配置项HINDSIGHT_API_TENANT_*系列环境变量、Docker 镜像构建与部署方式、JWKS 本地验签与 HS256 回退两条认证路径的底层机制以及从 0.9.2 内置版本迁移到独立打包版本的全部注意事项。扩展是什么独立打包的 TenantExtensionsupabase-tenant是 Hindsight 的TenantExtension实现它用 Supabase Auth本地 JWT 验证使用项目 JWKS 公钥在本地完成验签每个请求无网络调用对使用旧式 HS256 签名的项目回退到/auth/v1/user接口每用户一个 schema用户 ida1b2…7890得到 schemauser_a1b2…7890连字符转为下划线首次访问时执行迁移之后走缓存零用户管理身份来源就是你现有的 Supabase 项目扩展不维护任何用户表。这个扩展位于 hindsight-extensions 注册表中是TENANT槽位的两个实现之一另一个是static-keys-tenant。它不发布到 PyPI、不打 wheelHindsight 的官方镜像不带任何扩展分发的单位是你在官方镜像之上构建出来的派生镜像。这一点决定了它的安装方式——后面会详细展开。历史背景该扩展在 Hindsight0.9.2 及之前内置于服务端路径为hindsight_api.extensions.builtin.supabase_tenant。此后不再随服务器打包但配置项名称、schema 命名规则完全不变详见 迁移章节。认证与隔离模型TenantExtension 接口如何驱动 schema 隔离Hindsight 服务端在 tenant.py 中定义了租户扩展契约。扩展实现两个抽象方法authenticate(context) - TenantContext验证context.api_key即Authorization头去掉Bearer后的值返回TenantContext(schema_name...)。TenantContext的文档注释明确说明后续所有数据库查询都会使用该 schema 做全限定表名例如user_xxx.memory_units隔离因此发生在 SQL 层而非应用层list_tenants() - list[Tenant]返回后台 worker 需要轮询任务的 schema 列表——这是 worker 端租户发现的唯一入口。SupabaseTenantExtension的authenticate()主流程extension.py按以下顺序执行缺失 token →AuthenticationError(Missing Authorization header...)长度低于MIN_TOKEN_LENGTH源码常量20 字符→ 判定为格式非法直接拒绝避免把垃圾值送进验签按模式分发_use_jwks为真走本地 JWKS 验签否则走/auth/v1/user回退路径从subclaim 取出用户 id必须匹配 UUID 正则^[0-9a-f]{8}-...-12}$才允许进入 schema 名——扩展运行在服务进程内、处于认证边界上schema_prefix同样受正则^[a-zA-Z_][a-zA-Z0-9_]*$约束extension.py防止用户输入污染 schema 名拼出f{prefix}_{user_id.replace(-, _)}若该 schema 不在进程内缓存_initialized_schemas中调用self.context.run_migration(schema_name)首次建 schema随后返回TenantContext。缓存的意义authenticate()每个请求都会跑而run_migration只做一次。list_tenants()则直接返回_initialized_schemas中自进程启动以来见过的全部 schema——注意这意味着重启后租户列表从空开始累积直到各租户再次发起请求这正是 README 强调 worker 必须配置相同扩展变量的原因见下文。两条 JWT 验证路径路径一本地 JWKS 验签默认推荐服务端启动时on_startup()会拉取{SUPABASE_URL}/auth/v1/.well-known/jwks.jsonextension.py按kid建立公钥缓存此后每个请求零网络调用。源码中几个关键常量extension.py常量值作用MIN_TOKEN_LENGTH20过短 token 直接拒绝REQUEST_TIMEOUT_SECONDS10.0连接/读取超时按阶段设置非总超时JWKS_CACHE_TTL_SECONDS600缓存过期阈值与 Supabase Edge 侧 10 分钟缓存对齐JWKS_MIN_REFRESH_INTERVAL_SECONDS30防抖两次强制刷新之间的最小间隔SUPPORTED_ALGORITHMSRS256,ES256Supabase Auth 非对称签名支持的两个算法签名字段校验在_verify_token_jwks()extension.py中通过pyjwt.decode()完成同时校验 audienceauthenticated与 issuer{SUPABASE_URL}/auth/v1并逐类映射ExpiredSignatureError、InvalidAudienceError、InvalidIssuerError、DecodeError到AuthenticationError——过期 token 会得到明确的 Token has expired 而非笼统的 500。密钥轮转处理_get_signing_key()解析 token 的kid头后先检查缓存是否超过 600s TTL是则刷新若kid不在缓存中则再触发一次强制刷新受 30s 最小间隔保护以覆盖密钥轮转场景仍找不到才抛AuthenticationError(Unable to find signing key for token)。路径二HS256 遗留项目回退若 JWKS 端点返回空项目仍用 HS256 对称签名扩展回退为每请求调用{SUPABASE_URL}/auth/v1/user携带客户端 tokenAuthorization与service_rolekeyapikey头extension.py401 →Invalid or expired token非 200 → 透传状态码超时asyncio.TimeoutError→Authentication timeout - please retry连接失败aiohttp.ClientError→Connection error: ...。源码注释提示了一个版本细节HTTP 传输层当前是aiohttpClientTimeout(connect10, sock_read10)按每阶段而非总时长计时早期版本用 httpxREADME 中安装PyJWT[crypto]和httpx一句是遗留表述以 Dockerfile 实际安装的PyJWT[crypto]2.12.0与aiohttp3.14.3为准。配置HINDSIGHT_API_TENANT_EXTENSIONhindsight_ext_supabase_tenant:SupabaseTenantExtension HINDSIGHT_API_TENANT_SUPABASE_URLhttps://xxx.supabase.coHindsight 的扩展加载器约定与槽位同前缀的其他环境变量都会去掉HINDSIGHT_API_TENANT_前缀、转小写后成为构造函数的config字典键SUPABASE_URL→config[supabase_url]。完整参数表变量必填默认值说明HINDSIGHT_API_TENANT_SUPABASE_URL是—Supabase 项目 URLHINDSIGHT_API_TENANT_SUPABASE_SERVICE_KEY仅 HS256 项目—service_rolekey。JWKS 不可用时的回退验签必需设置后启动时还会执行一次/auth/v1/health连通性检查HINDSIGHT_API_TENANT_SCHEMA_PREFIX否userschema 名前缀必须是合法 Postgres 标识符片段启动即校验而非首请求才报错。__init__在缺supabase_url、前缀不合法含空、以数字开头时直接抛ValueErrorextension.pyon_startup中若 JWKS 拉不到且没配 service key同样抛ValueError阻止启动——README 原话the server fails at startup rather than accepting unverifiable tokens。另外supabase_url尾部/会被rstrip掉保证 issuer 拼写正确。worker 与 API 必须配置同一组变量。worker 通过list_tenants()决定对哪些 schema 执行整合consolidation与后台维护从 extensions README 的接口文档可见A schema you never return gets no consolidation or maintenance。worker 侧没加载该扩展时返回的租户列表永远为空所有租户的后台处理都会停摆。安装与运行扩展不发布到 PyPI分发的单位是镜像。从仓库根目录构建扩展源码必须在 build context 内docker build -f hindsight-extensions/supabase-tenant/Dockerfile -t hindsight-with-supabase .Dockerfile 的关键步骤值得逐条看ARG HINDSIGHT_IMAGEghcr.io/vectorize-io/hindsight:latest FROM ${HINDSIGHT_IMAGE} # 装进服务端的虚拟环境。该 venv 由 uv sync 创建、自身不带 pip # 裸 pip install 会落到 user site-packages运行中的服务器看不到。 RUN uv pip install --python /app/api/.venv/bin/python --no-cache \ PyJWT[crypto]2.12.0 \ aiohttp3.14.3 # /app/extensions 是派生镜像自有的目录不会遮蔽服务器自带的任何包。 COPY hindsight-extensions/supabase-tenant/hindsight_ext_supabase_tenant \ /app/extensions/hindsight_ext_supabase_tenant ENV PYTHONPATH/app/extensions # 构建期就 import 一次打包错误应该死在 build而不是死在第一个带认证的请求。 RUN /app/api/.venv/bin/python -c import hindsight_ext_supabase_tenant最后一行 import 检查是整套打包方案的保险丝没有它任何 PYTHONPATH 拼写错误都会打包成功、上线即炸。若不需要镜像内置的本地 embedding/reranking 模型可用--build-arg HINDSIGHT_IMAGEghcr.io/vectorize-io/hindsight:latest-slim换 slim 基座。运行示例API 与 worker 使用同一镜像时环境变量自然一致docker run -p 8888:8888 \ -e HINDSIGHT_API_TENANT_EXTENSIONhindsight_ext_supabase_tenant:SupabaseTenantExtension \ -e HINDSIGHT_API_TENANT_SUPABASE_URLhttps://xxx.supabase.co \ hindsight-with-supabasedocker-compose.yml形态构建上下文必须是仓库根目录services: hindsight-api: build: context: . dockerfile: hindsight-extensions/supabase-tenant/Dockerfile environment: HINDSIGHT_API_TENANT_EXTENSION: hindsight_ext_supabase_tenant:SupabaseTenantExtension HINDSIGHT_API_TENANT_SUPABASE_URL: https://xxx.supabase.co独立 worker 部署时给 worker 容器传入完全相同的HINDSIGHT_API_TENANT_*变量。extensions README 还明确警告不要在容器入口脚本里pip install扩展——那等同于每次重启都从网络解析未锁定的代码进运行中的服务器扩展应固化在镜像层。若不用 Docker 直接跑服务端则把hindsight_ext_supabase_tenant/目录加入 Hindsight 运行环境的PYTHONPATH并在该环境安装PyJWT[crypto]JWKS/RS256 验签与aiohttp当前传输层依赖。使用客户端如何调用客户端把 Supabase 签发的 JWT 作为 bearer token 发出扩展验签后把请求路由到对应用户的 schemacurl -H Authorization: Bearer supabase_jwt \ https://your-hindsight-server/v1/default/banks/my-bank/memories/recall认证失败时扩展抛出的AuthenticationError.reason如Missing Authorization header、Token has expired、Invalid token audience会原样出现在响应中便于区分token 过期audience 不匹配issuer 不符等具体失败原因。从内置版本迁移这是纯打包层面的移动不是行为变更。步骤按上文把扩展装进镜像更新扩展路径-HINDSIGHT_API_TENANT_EXTENSIONhindsight_api.extensions.builtin.supabase_tenant:SupabaseTenantExtension HINDSIGHT_API_TENANT_EXTENSIONhindsight_ext_supabase_tenant:SupabaseTenantExtension所有HINDSIGHT_API_TENANT_*配置项名称与含义不变schema 命名规则不变已有租户 schema 会被原样接管list_tenants()基于运行时见过的 schema 累积数据库里已迁移的 schema 无需重建。开发在仓库内跑测试在扩展目录内uv sync uv run pytest tests -vpyproject.toml 存在只为跑测试[tool.uv] package false让 uv 只建 venv、把源码留在 path 上而不构建任何发行物hindsight-api-slim以本地路径 editable 方式引入tool.uv.sources因为它是接口提供方、仅测试期依赖——运行时服务端才是宿主进程反过来由扩展引入服务器依赖会把服务器版本从部署脚下抽走。测试值得细看的地方test_supabase_tenant.pySupabase 被打桩成真实的进程内aiohttp.web服务器而非 mock 客户端——状态码处理、JSON 解析、超时、连接错误都走扩展真实的 aiohttp 传输路径用例覆盖配置校验缺 URL、前缀以数字开头、尾斜杠剥离、启动期 JWKS 拉取与三种回退分支空 keys / 拉取失败 / HTTP 错误、无 JWKS 且无 service key 必须启动失败、签名键缓存刷新与轮转、kid缺失、token 各失败分支过期/audience/issuer/非 UUID 的sub、schema 首次初始化/二次缓存/初始化失败、list_tenants从空到累积等test_package_entrypoint.py 则直接断言README 里写的那个环境变量值能加载到扩展类——文档与代码的契约由测试兜底。小结supabase-tenant展示了 Hindsight 多租户隔离的标准做法认证边界上完成身份解析隔离下沉到 PostgreSQL schemaJWKS 本地验签把每请求网络开销降到零HS256 回退保证旧项目可平滑接入。其镜像即发行物的打包模型、启动期即校验的配置策略、以及构建期 import 检查都是部署第三方扩展时可直接复用的工程模式。扩展由社区BrighterBalance贡献MIT 许可完整接口契约可参考 TenantExtension 基类更多打包约定见 extensions 总 README。【免费下载链接】hindsightHindsight: Agent Memory That Learns项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表