ClickHouse 可以使用 JSON Web Token (JWT) 对用户进行身份验证。与其他外部身份验证器 (如 LDAP 或 Kerberos) 不同,JWT 身份验证不会验证预先存在用户的身份。相反,它会根据嵌入在每个标记中的声明动态创建临时用户。这些用户仅存在于内存中,其访问权限由标记声明派生,并会在标记过期后自动移除。
这使得 JWT 身份验证与基于密码或证书的方法有本质区别:不存在 CREATE USER ... IDENTIFIED WITH jwt 语句,尝试这样做会引发异常。JWT 用户完全由标记的生命周期管理。
概览
身份验证流程如下:
- 客户端通过支持的传输机制之一提交已签名的 JWT (HTTP
Authorization: Bearer请求头、TCP 原生协议,或 gRPCjwt字段) 。 - ClickHouse 验证令牌签名。
- 验证必需的声明 (
exp、iat、iss、sub、aud) 。 - 在内存中创建一个临时用户,其访问权限由令牌声明
clickhouse:grants和clickhouse:roles推导得出,并与权限上限取交集。 - 当令牌过期时,后台垃圾回收任务会移除该用户。
令牌声明
必需声明
提交给 ClickHouse 的每个 JWT 都必须包含以下声明:
| 声明 | 说明 |
|---|---|
alg |
签名算法 (请求头声明) 。支持的值:RS256。从版本 26.8 起,还支持 EC 算法 ES256、ES384 和 ES512。 |
exp |
过期时间。用于设置临时用户的 valid_until。 |
iat |
签发时间。用于防止同一身份的旧标记被重放。 |
iss |
签发方。会与提供商预期的签发方进行匹配。 |
sub |
主体。会成为生成用户名的一部分。 |
aud |
受众。会与提供商预期的受众进行匹配。 |
使用基于 JWKS 的密钥解析时,还必须提供 kid (密钥 ID) 请求头声明。
其他已识别的声明
| 声明 | 描述 |
|---|---|
nbf |
生效时间。在此声明不是必需的;但如果提供了该声明,则在该时间之前,令牌会被拒绝。 |
jti |
保留字段。令牌中可以包含该声明,但当前不会对其进行验证或使用。 |
可选 claims
| Claim | 默认名称 | 描述 |
|---|---|---|
| 授权 | clickhouse:grants |
由 SQL GRANT 片段组成的 JSON 数组,例如 ["SELECT ON db.*", "INSERT ON db.table1"]。每个元素都会被解析为 GRANT 语句的主体。 |
| 角色 | clickhouse:roles |
要分配的角色名称 JSON 数组,例如 ["analyst", "reader"]。 |
| 如果身份提供商使用不同的命名约定,可以将默认的 claim 名称重映射为自定义 claim 名称。 |
令牌标头和载荷示例
{
"alg": "RS256",
"kid": "my-key-id"
}{
"iss": "https://idp.example.com",
"sub": "jane.doe",
"aud": "my-clickhouse-cluster",
"exp": 1719504000,
"iat": 1719500400,
"clickhouse:grants": ["SELECT ON analytics.*", "INSERT ON analytics.events"],
"clickhouse:roles": ["analyst"]
}临时用户的行为
JWT 用户与常规 ClickHouse 用户在几个重要方面有所不同。
身份与命名
每个 JWT 用户都会获得一个根据 iss、sub 和 aud 声明 计算出的确定性 UUID。该 UUID 在不同登录之间是稳定的。同一用户即使用不同的令牌多次登录 (只要签发方、主体和受众相同) ,获得的 UUID 也始终相同。
不过,用户名是易变的。其构造方式如下:
JWT::<subject>::<claims_hash><claims_hash> 是一个根据 iss、sub 和 aud 声明,以及 clickhouse:roles 和 clickhouse:grants 声明 共同计算得出的哈希值。iss 和 aud 声明 仅通过这个哈希参与;它们不再内联到可见的用户名前缀中。当 iss/aud 是较长且不透明的 IdP URL (例如 Microsoft Entra 或 Okta) 时,这样既能让名称保持紧凑、易读,又仍可确保不同的 (iss, sub, aud) Tuple,以及不同的 clickhouse:roles / clickhouse:grants 集合,会映射到不同且稳定的名称。不参与该哈希计算的 声明 (例如 iat、exp、nbf 或无关的自定义 声明) 不会改变用户名。
<claims_hash> 部分会在 clickhouse:roles 或 clickhouse:grants 声明发生变化时相应变化。这意味着,即使是同一身份,具有不同角色或授权集合的令牌也会生成不同的用户名。
访问权限
有效访问权限按如下方式计算:
effective_rights = permission_limit ∩ (token_grants ∪ token_roles)其中,permission_limit 是作为上限参考而配置的角色或用户所拥有的访问权限集合。令牌请求的任何超出该上限的权限都会被静默丢弃。
令牌新鲜度
ClickHouse 会跟踪每个稳定身份最近一次通过身份验证的令牌的 iat (签发时间) 声明。如果提供的令牌其 iat 等于或早于已存储的值,服务器会复用现有的临时用户,而不会重新评估声明。这样可以防止旧令牌降低用户权限。
生命周期与垃圾回收
临时用户会在令牌首次通过身份验证时创建,并在 valid_until (由 exp 推导得出) 到期后,由后台垃圾回收任务删除。GC 间隔由 gc_interval 参数控制 (默认值:5 分钟) 。
在两次 GC 运行之间,已过期的用户可能仍会显示在 system.users 中,但已无法再进行身份验证。
持久化访问分配
由于 UUID 是固定的,你可以使用 SQL 语句将 profile、配额、行策略和列脱敏策略分配给 JWT 用户。这些分配会持久保存在 access control 存储中 (磁盘上或 ZooKeeper 中) ,并且在令牌过期和重新身份验证后依然有效。
通过用户当前的用户名引用该用户:
ALTER SETTINGS PROFILE my_profile ADD TO 'JWT::jane.doe::<claims_hash>';请注意,ALTER USER 不能直接用于 JWT 用户,因为它们是只读的。要分配 settings profile、配额或策略,请使用上文所示的 ALTER SETTINGS PROFILE、ALTER QUOTA 或 ALTER ROW POLICY 语句。
与普通用户的区别
| Feature | JWT 用户 | 普通用户 |
|---|---|---|
| 创建 | 根据标记声明自动创建 | CREATE USER 语句 |
| 存储 | 仅驻留于内存中 (临时) | 磁盘、ZooKeeper 或配置文件 |
CREATE USER ... IDENTIFIED WITH jwt |
不支持 (会抛出异常) | 支持所有其他认证类型 |
ALTER USER / DROP USER |
不支持 | 支持 |
| 备份和恢复 | 不包含 | 包含 |
| 用户名 | 自动生成、可变 | 由管理员指定、固定 |
| UUID | 根据 iss+sub+aud 确定性生成 |
创建时随机生成 |
| 生命周期 | 受标记 exp 限定 |
直到被显式删除 |
| 访问权限 | 从标记声明派生,并受权限上限约束 | 通过 GRANT 显式授予 |
| 主机限制 | 按提供商网络配置 | 按用户 HOST 子句设置 |
| 设置 profile | 可按 UUID 分配 (持久) | 可直接配置 |
| 配额和行策略 | 可按 UUID 分配 (持久) | 可直接配置 |
| 默认角色 | 不可配置 | 可配置 |
SQL SECURITY DEFINER 视图
当临时 JWT 用户使用 SQL SECURITY DEFINER 创建视图时,服务器会自动为该用户创建一个持久的影子副本,作为该视图的定义者。该影子用户:
- 名称为
<original_jwt_username>:definer - 具有
NO_AUTHENTICATION(不能用于登录) - 保留创建视图时原始 JWT 用户拥有的相同访问权限
这样可以确保在临时用户的令牌过期、原始用户被垃圾回收机制清理后,视图仍能继续正常工作。
客户端使用
直接传入令牌
使用 clickhouse-client 的 --jwt 选项,通过预先获取的令牌进行身份验证:
clickhouse-client --host your-instance.clickhouse.cloud --secure --jwt '<your_jwt_token>'HTTP 接口
在 Authorization 请求头中以 Bearer 令牌的形式发送该令牌:
curl -H 'Authorization: Bearer <your_jwt_token>' \
'https://your-instance.clickhouse.cloud:8443/?query=SELECT+currentUser()'OAuth2 设备代码登录
clickhouse-client 支持通过 --login 标志使用交互式 OAuth2 设备代码流程。对于 ClickHouse Cloud 端点,客户端会自动执行令牌交换,以获取 ClickHouse 专用 JWT。令牌会在会话期间自动刷新,整个过程对用户透明。获取到新令牌后,客户端会自动重新连接。
clickhouse-client --host your-instance.clickhouse.cloud --loginClickHouse Cloud 内置 JWT 身份验证器
每个 ClickHouse Cloud 服务都带有一个预定义的 JWT 身份验证器,供 SQL 控制台和 clickhouse-client 的 --login 流程使用。该身份验证器配置如下:
| 参数 | 值 |
|---|---|
iss (签发方) |
ClickHouse |
aud (受众) |
服务 UUID (可在 Cloud 控制台 URL 中看到) |
sub (主体) |
你的 ClickHouse Cloud 账户邮箱地址 |
此内置身份验证器的权限上限设置为 default_role 角色和 default 用户。这意味着,任何 JWT 用户的实际权限都会与这两个实体所拥有的授权取交集,因此令牌的权限绝不会提升到超出 default_role 和 default 允许范围的级别。
使用此身份验证器无需进行任何配置。服务创建时会自动为其预配。
为服务启用 JWT 身份验证
通过自定义身份提供商进行 JWT 身份验证 可在 Enterprise 计划中使用。如需升级,请前往 Cloud Console 的套餐页面。
除内置身份验证器外,您还可以将 ClickHouse Cloud 服务配置为接受由自己的身份提供商 (例如 Microsoft Entra 或 Okta) 签发的 JWT。这是一项 Beta 功能,适用于运行 ClickHouse 26.4 或更高版本的 Enterprise 方案服务。您可以在 Cloud 控制台的 设置 → 安全 → JWT 身份验证 中自行配置;有关分步说明,请参阅 JWT 身份验证设置。每个提供商由以下内容定义:
| 参数 | 说明 |
|---|---|
| 名称 | 服务中该提供商的唯一名称。 |
| 签发方 | 身份提供商签发的标记中 iss 声明的值,通常为提供商的 URL (例如 https://your-tenant.okta.com) 。ClickHouse 会拒绝签发方与此值不匹配的标记。 |
| 受众 | 身份提供商在为此服务签发的标记中设置的 aud 声明值。ClickHouse 会拒绝面向其他受众签发的标记。 |
| JWKS URL | 身份提供商发布 JSON Web Key Set 的 HTTPS 端点 (例如 https://idp.example.com/.well-known/jwks.json) 。ClickHouse 会从此端点拉取公钥以验证标记签名。如必需声明所述,JWKS 验证支持 RSA 密钥 (RS256),并且从 26.8 版本起支持 P-256、P-384 和 P-521 曲线上的 EC 密钥 (ES256、ES384、ES512) 。 |
| 角色声明 (可选) | 包含临时用户 ClickHouse 角色的标记声明名称。留空以使用默认声明名称 clickhouse:roles。该声明必须包含角色名称的 JSON 数组,例如 ["analyst", "reader"]。 |
服务器间通信
当查询被转发到另一分片或副本时,JWT 令牌会包含在服务器间协议中。远程节点会独立重新验证该令牌,并创建自己的临时用户。
故障排除
- 未授予访问权限: 引用的角色或用户可能缺少所需的授权。请确保
clickhouse:roles中引用的角色存在,并且已授予相应权限。 - 令牌被拒绝: 请验证令牌中的
iss、aud以及签名算法是否与 JWT 提供商的要求一致。如果使用 JWKS,请确保令牌的kid与提供商密钥集中的某个密钥匹配。 - 用户在两次查询之间消失: 临时用户会在令牌过期后被移除。对于长时间运行的会话,请使用支持令牌刷新的客户端 (例如
--login模式) 。 CREATE USER ... IDENTIFIED WITH jwt失败: 这是预期行为。JWT 用户无法通过 DDL 创建。它们完全由令牌生命周期管理。