news 2026/9/13 3:22:47

SpacetimeDB HTTP API 授权机制完全指南:JWT 身份认证、Bearer 令牌与匿名访问详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpacetimeDB HTTP API 授权机制完全指南:JWT 身份认证、Bearer 令牌与匿名访问详解

SpacetimeDB HTTP API 授权机制完全指南:JWT 身份认证、Bearer 令牌与匿名访问详解

【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB

本篇指南以 SpacetimeDB 官方参考文档 HTTP API 授权(Authorization) 为核心脉络,深入讲解 SpacetimeDB 如何通过 OpenID Connect 兼容的 JSON Web Token(JWT)构建身份与授权体系。你将掌握:身份(Identity)如何从 JWT 的sub/iss声明派生、Authorization: Bearer请求头的正确用法、匿名访问的权限边界,以及GET /v1/ping等顶层路由的用途。同时结合仓库源码,我们还会追溯令牌签发与验证的底层实现,帮助你从"会调接口"进阶到"理解机制"。

SpacetimeDB 的授权模型:Identity 与 Token 的二元结构

SpacetimeDB 的授权体系建立在两个核心概念之上:身份(Identity)令牌(Token)

  • 身份是用户在 SpacetimeDB 中的全局唯一标识,也是数据库所有权(owner)、行级安全策略(row-level security)等权限判定的依据;
  • 令牌是身份的"凭证",客户端通过 HTTP 请求头携带令牌,服务端校验令牌后即可确定调用者的身份。

关键的设计约束在于:SpacetimeDB 可以从任何 OpenID Connect 兼容的 JWT 中派生身份,具体做法是从 JWT 的sub(subject,主题)与iss(issuer,签发者)两个声明中计算得出。这一点在 crates/lib/src/identity.rs 中有着明确的实现——Identity::from_claims"{issuer}|{subject}"作为输入,经过blake3哈希并裁剪、附加校验字节后得到最终的 32 字节身份值:

/// Derives an identity from a [JWT] `issuer` and a `subject`. pub fn from_claims(issuer: &str, subject: &str) -> Self { let input = format!("{issuer}|{subject}"); let first_hash = blake3::hash(input.as_bytes()); let id_hash = &first_hash.as_bytes()[..26]; let mut checksum_input = [0u8; 28]; checksum_input[2..].copy_from_slice(id_hash); checksum_input[0] = 0xc2; checksum_input[1] = 0x00; let checksum_hash = &blake3::hash(&checksum_input); // ...组装最终 Identity }

这意味着:只要给出相同的isssub,无论令牌由谁签发,派生出的身份都是确定且一致的。这是 SpacetimeDB 能同时支持"自签令牌"与"第三方 OIDC 令牌"两种接入方式的根本原因。

身份与令牌的三种生成途径

根据授权文档,SpacetimeDB 客户端可以通过以下三种方式获得身份与令牌:

1. 通过POST /v1/identity端点申请

客户端可以向 SpacetimeDB 主机的 POST /v1/identity 端点发起请求,申请一个由 SpacetimeDB 主机私钥签名的全新身份与令牌。响应的 JSON 形式为:

{ "identity": string, "token": string }

需要特别强调的是,这种令牌不可移植到其他 SpacetimeDB 集群——它由当前集群的私钥签名,其他集群无法验证其签名(这一点我们会在"验证链路"一节详细展开)。

在源码 crates/client-api/src/routes/identity.rs 中,create_identity处理函数会调用SpacetimeAuth::alloc完成签发,最终返回CreateIdentityResponse { identity, token }。而SpacetimeAuth::alloc的实现位于 crates/client-api/src/auth.rs:

/// Allocate a new identity, and mint a new token for it. pub async fn alloc(ctx: &(impl NodeDelegate + ControlStateDelegate + ?Sized)) -> axum::response::Result<Self> { // Generate claims with a random subject. let subject = Uuid::new_v4().to_string().into(); let claims = TokenClaims { issuer: ctx.jwt_auth_provider().local_issuer().into(), subject, // Placeholder audience. audience: ["spacetimedb".into()].into(), extra: None, }; let (claims, token) = claims.encode_and_sign(ctx.jwt_auth_provider()).map_err(log_and_500)?; // ... }

从源码可以看到,新身份的sub声明是一个随机生成的 UUID v4 字符串iss则是当前集群的local_issueraud是占位用的"spacetimedb"。之后用 ES256 算法签名生成 JWT。

2. 通过 WebSocket API 匿名连接自动生成

客户端通过 WebSocket API 发起匿名连接时,SpacetimeDB 同样会自动生成一个新的身份与令牌,并通过IdentityToken消息传递给客户端。这意味着即使客户端不主动申请身份,也能在建立连接后获得一套可复用的凭证——适合快速上手或无需持久化身份的交互式场景。IdentityToken消息类型定义在 crates/client-api-messages/src/websocket/v1.rs 中。

3. 携带第三方 OIDC 令牌

由于身份是从 JWT 的sub/iss声明确定性派生的,客户端也可以直接使用任意 OpenID Connect 兼容身份提供商(IdP)签发的 JWT作为访问凭证,无需事先在 SpacetimeDB 中注册。服务端会通过 OIDC 发现机制(见下文"OIDC 验证器")校验令牌签名后,按iss+sub计算身份。

Authorization请求头规范

SpacetimeDB 的众多 HTTP 端点要么要求、要么可选地接受Authorization头中的令牌。其格式统一为:

Authorization: Bearer ${token}

其中token是一个 OpenID Connect 兼容的 JWT,例如 POST /v1/identity 端点返回的令牌。

源码中的凭证提取逻辑

在 crates/client-api/src/auth.rs 中,SpacetimeCreds::from_request_parts展示了凭证的完整提取逻辑——优先从Authorization头读取 Bearer 令牌,其次回退到 URL 查询参数?token=...

/// Extract credentials from the headers or else query string of a request. fn from_request_parts(parts: &request::Parts) -> Result<Option<Self>, headers::Error> { let header = parts .headers .typed_try_get::<headers::Authorization<authorization::Bearer>>()?; if let Some(headers::Authorization(bearer)) = header { let token = bearer.token().to_owned(); return Ok(Some(SpacetimeCreds { token })); } if let Ok(Query(creds)) = Query::<Self>::try_from_uri(&parts.uri) { return Ok(Some(creds)); } Ok(None) }

也就是说,除了标准的Authorization: Bearer <token>头之外,开发者还可以通过?token=<token>查询参数携带凭证(前者优先级更高)。这也是 crates/client-api/src/routes/database.rs 中诸多/v1/database路由所依赖的统一鉴权入口。

请求失败时的标准错误响应

当令牌无效或缺失时,服务端通过AuthorizationRejection类型返回明确的 HTTP 状态码与错误消息(见 crates/client-api/src/auth.rs):

场景状态码响应体
令牌有效,但签名不是本集群签发(如密钥已轮换)401 UnauthorizedAuthorization failed: token not signed by this instance
JWT 格式非法或请求头解析失败400 Bad RequestAuthorization is invalid: malformed token
需要认证但未提供Authorization401 UnauthorizedAuthorization required
其他自定义校验失败401 Unauthorized具体错误消息

匿名访问:默认被允许,但存在权限边界

授权文档明确写道:所有/v1/database端点都支持匿名访问。如果请求未携带Authorization头,SpacetimeDB 会为请求分配一个新的匿名身份。

在源码层面,这一行为由 crates/client-api/src/auth.rs 的get_or_create实现:当请求中没有 JWT 时,直接调用SpacetimeAuth::alloc创建一个全新的身份与令牌,并在响应头中通过spacetime-identityspacetime-identity-token两个自定义响应头回传给客户端(见SpacetimeIdentitySpacetimeIdentityToken的类型定义,crates/client-api/src/auth.rs)。

匿名请求可以做什么

  • 访问公共信息:数据库信息(database info)、schema、名称(names);
  • 调用 reducer 或运行 SQL 查询;
  • 访问公共表(public tables)中的数据。

例如 GET /v1/database/:name_or_identity/schema 端点不需要任何授权即可获取 schema;POST /v1/database/:name_or_identity/sql 允许匿名运行 SQL,但只能访问公共表,且调用者身份会被用于强制执行行级安全策略(row-level security)。

匿名请求会被拒绝的操作

  • 删除数据库DELETE /v1/database/:name_or_identity):删除要求所有权,匿名请求会被拒绝;
  • 查看日志GET /v1/database/:name_or_identity/logs):查看日志要求数据库所有权,匿名请求会被拒绝;
  • 更新数据库PUT /v1/database/:name_or_identity):更新现有数据库时,令牌必须对应数据库的所有者,否则请求被拒绝并返回401 UNAUTHORIZED{ "PermissionDenied": { "name": string } }形式的 JSON;
  • 设置名称PUT /v1/database/:name_or_identity/names):设置名称列表要求数据库所有权;
  • 发布新数据库POST /v1/database):虽然匿名也可以发布,但新数据库会被这个自动分配的匿名身份所拥有——这"通常不是你想要的"(官方文档原话),因为一旦丢失令牌你将失去对该数据库的管理权。

匿名调用 reducer 的身份传递

通过 POST /v1/database/:name_or_identity/call/:reducer 匿名调用 reducer 时,调用者的身份会通过ReducerContext传递给模块代码,模块可以基于该身份决定接受或拒绝调用。这是模块侧实现自定义授权逻辑的基础——匿名并不等于无身份,只是身份是"每次请求临时分配的"。

令牌验证链路:两级验证与 OIDC 发现

理解"令牌不可移植到其他集群"的关键,在于 SpacetimeDB 的令牌验证实现。核心代码位于 crates/core/src/auth/token_validation.rs:

// This validator accepts any tokens signed with the local key (regardless of issuer). // If it is not signed with the local key, we will try to validate it with the OIDC validator. pub struct FullTokenValidator<T: TokenValidator + Send + Sync> { pub local_key: DecodingKey, pub local_issuer: Box<str>, pub oidc_validator: T, }

FullTokenValidator(默认验证器)的验证顺序是:

  1. 先用本地公钥验证:如果令牌能通过本集群公钥的签名校验(此时不强制校验 issuer,因为 SpacetimeDB 会用自己的密钥为短生命周期令牌重签名),则直接接受;
  2. 本地验证失败后,尝试 OIDC 验证:从令牌中提取原始iss声明(该提取过程刻意不做签名校验,仅用于密钥发现),如果iss就是本集群的local_issuer,则返回第一步的错误;否则交给 OIDC 验证器处理。

OIDC 验证器与 JWKS 缓存

OIDC 验证器通过标准的OpenID Connect Discovery流程验证第三方令牌(crates/core/src/auth/token_validation.rs):

  1. 访问{issuer}/.well-known/openid-configuration获取jwks_uri
  2. 访问jwks_uri拉取 JWKS(JSON Web Key Set);
  3. 根据 JWT 头部的kid(或遍历全部密钥)找到对应公钥并校验签名与 issuer。

其中CachingOidcTokenValidator会对 JWKS 做缓存:默认每 300 秒刷新一次、缓存有效期 7200 秒(crates/core/src/auth/token_validation.rs),避免每个请求都触发外部 HTTP 调用。同时,validate_url_scheme会强制要求 OIDC URL 仅支持http://https://协议。

Claim 级校验规则

无论走哪条验证路径,令牌最终都要被转换为SpacetimeIdentityClaims。转换前的严格校验逻辑位于 crates/auth/src/identity.rs,包括:

  • isssub均不得超过 128 字节,且不得为空;
  • 若令牌中携带了hex_identity声明,则其值必须与Identity::from_claims(iss, sub)计算出的身份完全一致,否则拒绝;
  • 过期校验:exp若存在,则过期超过 60 秒宽限(leeway)的令牌会被拒绝;expnull或缺失时按"永不过期"处理(兼容旧版令牌)。

SpacetimeIdentityClaims的完整字段定义(含hex_identitysubissaudiatexp及自定义extra声明)见 crates/auth/src/identity.rs。令牌统一使用ES256(ECDSA P-256)算法签名,见 crates/client-api/src/auth.rs。

短生命周期令牌的重签名

POST /v1/identity/websocket-token 端点接收一个有效令牌,并返回一个过期时间为 60 秒的短生命周期令牌,适合嵌入 URL 等不可信场景。其实现create_websocket_token(crates/client-api/src/routes/identity.rs)调用re_sign_with_expiry用本集群密钥重新签名——即使原令牌的 issuer 不同,重签后也会带上 60 秒的exp,且由于FullTokenValidator先验证本集群签名,因此这些重签令牌仍可通过验证。

顶层路由与健康检查:GET /v1/ping

授权文档列出的顶层路由只有一个:

路由说明
GET /v1/ping无操作(No-op),用于判断客户端能否连接到 SpacetimeDB

GET /v1/ping不执行任何逻辑、也不返回任何数据,客户端可以发送请求到该端点以探测与 SpacetimeDB 主机的连通性。它是"能否连上"的最小可用性探针,不涉及任何身份验证。

配套身份端点全景:把授权能力串起来

授权文档中引用了 POST /v1/identity 作为令牌来源。为便于把授权链路串成一个完整闭环,这里列出/v1/identity下的全部端点(路由注册见 crates/client-api/src/routes/identity.rs):

路由说明认证要求
POST /v1/identity生成新的身份与令牌
POST /v1/identity/websocket-token生成 60 秒短生命周期令牌需要Authorization(Bearer)
GET /v1/identity/public-key获取本集群用于验证令牌的公钥,Content-Type 为application/pem-certificate-chain
GET /v1/identity/:identity/databases列出某身份拥有的数据库(返回identities数组)
GET /v1/identity/:identity/verify验证身份/令牌对需要Authorization(Bearer)

其中GET /v1/identity/:identity/verify的三种响应语义(crates/client-api/src/routes/identity.rs)非常清晰:

  • 令牌有效且与路径中的:identity匹配 →204 No Content
  • 令牌有效但与:identity不匹配 →400 Bad Request
  • 令牌无效或缺失Authorization头 →401 Unauthorized

该端点可配合公钥端点(public-key)在不依赖任何 IdP 的情况下自行实现客户端侧的令牌自校验。

实战演练:用 curl 跑通完整授权流程

以下操作基于本地运行的 SpacetimeDB 主机(localhost:3000),演示从申请身份到访问受控资源的完整流程。

1. 探测连通性

curl -i http://localhost:3000/v1/ping

预期返回200状态码且无响应体。若无法连接,请先检查主机是否已启动。

2. 申请身份与令牌

curl -s http://localhost:3000/v1/identity

返回示例:

{ "identity": "0x5f3c...(64 位十六进制身份)", "token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9..." }

token值保存到环境变量供后续使用:

export ST_TOKEN="<上一步返回的 token>"

3. 带令牌调用数据库端点

# 携带令牌查询数据库信息 curl -s -H "Authorization: Bearer $ST_TOKEN" \ http://localhost:3000/v1/database/your_db_name # 匿名调用(不带 Authorization 头)——会被分配临时匿名身份 curl -s http://localhost:3000/v1/database/your_db_name

对于 schema 类端点,携带令牌时响应头还会回显spacetime-identityspacetime-identity-token两个响应头。

4. 生成短生命周期令牌(需先持有有效令牌)

curl -s -X POST -H "Authorization: Bearer $ST_TOKEN" \ http://localhost:3000/v1/identity/websocket-token

返回:

{ "token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9..." }

该令牌 60 秒后过期,可用于 URL 内嵌等不可信上下文。

5. 验证身份与令牌对

curl -i -H "Authorization: Bearer $ST_TOKEN" \ http://localhost:3000/v1/identity/<你的 identity>/verify
  • 204:令牌有效且身份匹配;
  • 400:令牌有效但身份不匹配;
  • 401:令牌无效或缺失。

安全实践与注意事项

结合官方文档与源码实现,使用 SpacetimeDB HTTP API 授权时建议注意以下几点:

  1. 匿名发布数据库需谨慎POST /v1/database不带Authorization头时,新数据库归临时匿名身份所有,令牌一旦丢失将无法找回所有权。务必先通过/v1/identity申请身份再用其发布数据库。
  2. 令牌具备集群边界:自签令牌由本集群私钥签发,不能跨集群使用;更换主机或密钥轮换后,旧令牌会以401 Authorization failed: token not signed by this instance被拒绝。
  3. 善用短生命周期令牌:需要把凭证暴露给不可信环境(如 URL、日志、第三方)时,优先使用POST /v1/identity/websocket-token生成的 60 秒令牌,避免泄露长期令牌。
  4. 模块内仍需自行鉴权:HTTP 层的匿名访问是"允许访问公共资源",但模块内的 reducer 需要基于ReducerContext中的调用者身份自行实现业务级授权,并善用表的公共/私有(table_access)声明与行级安全策略。
  5. 了解验证器的 OIDC 依赖:使用第三方 OIDC 令牌时,主机需要能够访问{issuer}/.well-known/openid-configurationjwks_uri;JWKS 默认缓存 5 分钟、最多 2 小时,IdP 密钥轮换后存在短暂窗口期。

延伸阅读

  • 授权文档原文:HTTP API 授权
  • 身份端点全解:HTTP API 身份端点
  • 数据库端点与各端点授权要求:HTTP API 数据库端点
  • 身份派生实现:crates/lib/src/identity.rs
  • 令牌签发与匿名中间件:crates/client-api/src/auth.rs
  • 身份端点路由实现:crates/client-api/src/routes/identity.rs
  • 令牌验证链路:crates/core/src/auth/token_validation.rs
  • Claim 结构与校验规则:crates/auth/src/identity.rs

【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 3:22:11

fhevm Gateway 协议暂停机制(Pausing Mechanism)实战指南

fhevm Gateway 协议暂停机制&#xff08;Pausing Mechanism&#xff09;实战指南 【免费下载链接】fhevm FHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications 项目地址: https://gitcode.com/GitHub_Trending/…

作者头像 李华
网站建设 2026/9/13 3:22:05

Excel动态图表实战:构建数据驱动的交互式看板

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 3:17:40

YOLOv8裂缝检测实战:路面桥梁墙体小目标识别与边缘部署

简介&#xff1a;本资源是一套基于YOLOv8实现的路面、桥梁及墙体裂缝智能识别完整项目&#xff0c;面向计算机、人工智能、土木工程等相关专业本科生与研究生&#xff0c;专为毕业设计、课程设计及项目实战训练打造。项目代码经导师审核并获96.5分高分答辩评价&#xff0c;功能…

作者头像 李华
网站建设 2026/9/13 3:17:31

Comsol Chatbot:面向工程仿真的垂直领域AI协作者

1. 这不是另一个“AI客服”&#xff0c;而是Comsol工程师的实时协作者你有没有过这样的经历&#xff1a;在Comsol Multiphysics里建模到一半&#xff0c;突然卡在边界条件设置上——明明物理意义清晰&#xff0c;但软件里找不到对应的操作入口&#xff1b;或者导出的S参数矩阵想…

作者头像 李华
网站建设 2026/9/13 3:17:06

10个提示词工程实战技巧,让大模型输出质量立竿见影

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华