资讯动态

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

发布时间:2026/9/13 3:23:04 来源:尧图企业网站定制
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 TokenJWT构建身份与授权体系。你将掌握身份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 的subsubject主题与ississuer签发者两个声明中计算得出。这一点在 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 }这意味着只要给出相同的iss与sub无论令牌由谁签发派生出的身份都是确定且一致的。这是 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::ResultSelf { // 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 验证器校验令牌签名后按isssub计算身份。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) - ResultOptionSelf, headers::Error { let header parts .headers .typed_try_get::headers::Authorizationauthorization::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头之外开发者还可以通过?tokentoken查询参数携带凭证前者优先级更高。这也是 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 instanceJWT 格式非法或请求头解析失败400 Bad RequestAuthorization is invalid: malformed token需要认证但未提供Authorization头401 UnauthorizedAuthorization required其他自定义校验失败401 Unauthorized具体错误消息匿名访问默认被允许但存在权限边界授权文档明确写道所有/v1/database端点都支持匿名访问。如果请求未携带Authorization头SpacetimeDB 会为请求分配一个新的匿名身份。在源码层面这一行为由 crates/client-api/src/auth.rs 的get_or_create实现当请求中没有 JWT 时直接调用SpacetimeAuth::alloc创建一个全新的身份与令牌并在响应头中通过spacetime-identity与spacetime-identity-token两个自定义响应头回传给客户端见SpacetimeIdentity与SpacetimeIdentityToken的类型定义crates/client-api/src/auth.rs。匿名请求可以做什么访问公共信息数据库信息database info、schema、名称names调用 reducer 或运行 SQL 查询访问公共表public tables中的数据。例如 GET /v1/database/:name_or_identity/schema 端点不需要任何授权即可获取 schemaPOST /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 FullTokenValidatorT: TokenValidator Send Sync { pub local_key: DecodingKey, pub local_issuer: Boxstr, pub oidc_validator: T, }FullTokenValidator默认验证器的验证顺序是先用本地公钥验证如果令牌能通过本集群公钥的签名校验此时不强制校验 issuer因为 SpacetimeDB 会用自己的密钥为短生命周期令牌重签名则直接接受本地验证失败后尝试 OIDC 验证从令牌中提取原始iss声明该提取过程刻意不做签名校验仅用于密钥发现如果iss就是本集群的local_issuer则返回第一步的错误否则交给 OIDC 验证器处理。OIDC 验证器与 JWKS 缓存OIDC 验证器通过标准的OpenID Connect Discovery流程验证第三方令牌crates/core/src/auth/token_validation.rs访问{issuer}/.well-known/openid-configuration获取jwks_uri访问jwks_uri拉取 JWKSJSON Web Key Set根据 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包括iss与sub均不得超过 128 字节且不得为空若令牌中携带了hex_identity声明则其值必须与Identity::from_claims(iss, sub)计算出的身份完全一致否则拒绝过期校验exp若存在则过期超过 60 秒宽限leeway的令牌会被拒绝exp为null或缺失时按永不过期处理兼容旧版令牌。SpacetimeIdentityClaims的完整字段定义含hex_identity、sub、iss、aud、iat、exp及自定义extra声明见 crates/auth/src/identity.rs。令牌统一使用ES256ECDSA P-256算法签名见 crates/client-api/src/auth.rs。短生命周期令牌的重签名POST /v1/identity/websocket-token 端点接收一个有效令牌并返回一个过期时间为 60 秒的短生命周期令牌适合嵌入 URL 等不可信场景。其实现create_websocket_tokencrates/client-api/src/routes/identity.rs调用re_sign_with_expiry用本集群密钥重新签名——即使原令牌的 issuer 不同重签后也会带上 60 秒的exp且由于FullTokenValidator先验证本集群签名因此这些重签令牌仍可通过验证。顶层路由与健康检查GET /v1/ping授权文档列出的顶层路由只有一个路由说明GET /v1/ping无操作No-op用于判断客户端能否连接到 SpacetimeDBGET /v1/ping不执行任何逻辑、也不返回任何数据客户端可以发送请求到该端点以探测与 SpacetimeDB 主机的连通性。它是能否连上的最小可用性探针不涉及任何身份验证。配套身份端点全景把授权能力串起来授权文档中引用了 POST /v1/identity 作为令牌来源。为便于把授权链路串成一个完整闭环这里列出/v1/identity下的全部端点路由注册见 crates/client-api/src/routes/identity.rs路由说明认证要求POST /v1/identity生成新的身份与令牌无POST /v1/identity/websocket-token生成 60 秒短生命周期令牌需要AuthorizationBearerGET /v1/identity/public-key获取本集群用于验证令牌的公钥Content-Type 为application/pem-certificate-chain无GET /v1/identity/:identity/databases列出某身份拥有的数据库返回identities数组无GET /v1/identity/:identity/verify验证身份/令牌对需要AuthorizationBearer其中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上一步返回的 token3. 带令牌调用数据库端点# 携带令牌查询数据库信息 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-identity与spacetime-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/verify204令牌有效且身份匹配400令牌有效但身份不匹配401令牌无效或缺失。安全实践与注意事项结合官方文档与源码实现使用 SpacetimeDB HTTP API 授权时建议注意以下几点匿名发布数据库需谨慎POST /v1/database不带Authorization头时新数据库归临时匿名身份所有令牌一旦丢失将无法找回所有权。务必先通过/v1/identity申请身份再用其发布数据库。令牌具备集群边界自签令牌由本集群私钥签发不能跨集群使用更换主机或密钥轮换后旧令牌会以401 Authorization failed: token not signed by this instance被拒绝。善用短生命周期令牌需要把凭证暴露给不可信环境如 URL、日志、第三方时优先使用POST /v1/identity/websocket-token生成的 60 秒令牌避免泄露长期令牌。模块内仍需自行鉴权HTTP 层的匿名访问是允许访问公共资源但模块内的 reducer 需要基于ReducerContext中的调用者身份自行实现业务级授权并善用表的公共/私有table_access声明与行级安全策略。了解验证器的 OIDC 依赖使用第三方 OIDC 令牌时主机需要能够访问{issuer}/.well-known/openid-configuration与jwks_uriJWKS 默认缓存 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.rsClaim 结构与校验规则crates/auth/src/identity.rs【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价