JWT 令牌身份验证
本文档描述了 Apache Airflow 如何为公共 REST API(核心 API)以及供 Worker 使用的内部执行 API(Execution API)进行 JWT(JSON Web Token)身份验证。
概述
Airflow 使用 JWT 令牌作为其 API 的主要身份验证机制。存在两种不同的 JWT 身份验证流程:
REST API(核心 API) — 由 UI 用户、CLI 工具和外部客户端使用,以与 Airflow 公共 API 交互。
执行 API (Execution API) — 由 Worker、DAG 文件处理器和触发器 (Triggerer) 内部使用,用于通信任务状态并检索运行时数据(连接、变量、XCom)。
两种流程共享相同的基础 JWT 基础设施(位于 airflow.api_fastapi.auth.tokens 中的 JWTGenerator 和 JWTValidator 类),但在受众、令牌生命周期、主体声明和作用域语义上有所不同。
签名与加密
Airflow 支持两种互斥的签名模式:
- 对称加密(共享密钥)
使用预共享的密钥 (
[api_auth] jwt_secret) 和 HS512 算法。所有生成或验证令牌的组件必须共享同一个密钥。如果未配置密钥,Airflow 将在启动时自动生成一个随机的 16 字节密钥,但该密钥是临时的,且在不同进程中不同,这将导致多组件部署中的身份验证失败。部署管理员必须显式配置此值。- 非对称加密(公钥/私钥对)
使用 PEM 编码的私钥 (
[api_auth] jwt_private_key_path) 进行签名,并使用相应的公钥进行验证。支持的算法:RS256 (RSA) 和 EdDSA (Ed25519)。当[api_auth] jwt_algorithm设置为GUESS(默认值)时,算法会根据密钥类型自动检测。验证可以使用以下任一方式:
通过
[api_auth] trusted_jwks_url配置的 JWKS (JSON Web Key Set) 端点(本地文件或远程 HTTP/HTTPS URL,会定期轮询更新)。从配置的私钥中派生出的公钥(当未设置
trusted_jwks_url时的自动回退方案)。
REST API 身份验证流程
令牌获取
客户端发送
POST请求至/auth/token,并在 JSON 请求体中携带凭据(例如用户名和密码)。身份验证管理器验证凭据并创建一个用户对象。
身份验证管理器将用户序列化为 JWT 声明,并调用
JWTGenerator.generate()。生成的令牌以
access_token的形式返回在响应中。
对于基于 UI 的身份验证,令牌存储在安全且仅限 HTTP 访问的 cookie (_token) 中,并设置 SameSite=Lax。
CLI 使用单独的端点 (/auth/token/cli),其过期时间较短。
令牌结构 (REST API)
声明 |
描述 |
|---|---|
|
唯一令牌标识符(UUID4 十六进制)。用于令牌撤销。 |
|
颁发者(来自 |
|
受众(来自 |
|
用户标识符(由身份验证管理器序列化)。 |
|
颁发时间戳(Unix 时间戳,秒)。 |
|
生效前时间戳(与 |
|
过期时间戳( |
令牌验证 (REST API)
在每次 API 请求中,令牌会按以下优先级顺序提取:
Authorization: Bearer <token>请求头。OAuth2 查询参数。
_tokencookie。
JWTValidator 会验证签名、有效期 (exp)、生效前时间 (nbf)、颁发时间 (iat)、受众和颁发者声明。可配置的余量([api_auth] jwt_leeway,默认 10 秒)用于抵消时钟偏移。
令牌撤销 (仅限 REST API)
令牌撤销仅适用于 REST API 和 UI 令牌 — 它不适用于颁发给 Worker 的执行 API 令牌。
已撤销的令牌通过其 jti 声明在 revoked_token 数据库表中进行跟踪。在登出或显式撤销时,令牌的 jti 和 exp 会被插入到此表中。过期的条目会自动以 2× jwt_expiration_time 的频率进行清理。
令牌刷新 (REST API)
JWTRefreshMiddleware 在 UI 请求上运行。当中间件检测到当前令牌的 _token cookie 即将过期时,它会调用 auth_manager.refresh_user() 生成新令牌,并将其设置为更新后的 cookie。
默认计时设置 (REST API)
设置 |
默认值 |
|---|---|
|
86400 秒(24 小时) |
|
3600 秒(1 小时) |
|
10 秒 |
执行 API 身份验证流程
执行 API 是 Airflow 自身使用的一种 API(非第三方调用者),用于报告和设置任务状态转换、发送心跳,并在任务运行时检索连接、变量和 XCom,以及触发执行和 DAG 解析。
令牌生成 (执行 API)
调度器 (Scheduler) 在将任务实例分发给 Worker 之前(通过执行器)会为其生成 JWT。执行器的
jwt_generator属性会创建一个配置了[execution_api]设置的JWTGenerator。令牌的
sub(主体) 声明被设置为任务实例 UUID。令牌被嵌入在发送给 Worker 进程的工作负载 JSON 有效负载(
BaseWorkloadSchema.token字段)中。
令牌结构 (执行 API)
声明 |
描述 |
|---|---|
|
唯一令牌标识符(UUID4 十六进制)。 |
|
颁发者(来自 |
|
受众(来自 |
|
任务实例 UUID — 工作负载的身份。 |
|
令牌作用域: |
|
颁发时间戳。 |
|
生效前时间戳。 |
|
过期时间戳( |
令牌作用域 (执行 API)
执行 API 定义了两种令牌作用域:
- workload
一种受限作用域,仅在通过
Security(require_auth, scopes=["token:workload"])显式启用的端点上被接受。用于管理任务状态转换的端点。- execution
所有执行 API 端点均接受此作用域。这是用于 Worker 通信的标准作用域,允许访问所有功能。
为了向后兼容,缺少 scope 声明的令牌默认为 "execution"。
令牌分发至 Worker
令牌在执行堆栈中的流动流程如下:
调度器生成令牌并将其嵌入到传递给执行器的工作负载 JSON 有效负载中。
工作负载 JSON 被传递给 Worker 进程(通过执行器特定的机制:Celery 消息、Kubernetes Pod 规范、本地子进程参数等)。
Worker 的
execute_workload()函数读取工作负载 JSON 并提取令牌。supervise()函数接收该令牌,并创建一个带有BearerAuth(token)的httpx.Client实例,用于所有执行 API HTTP 请求。令牌包含在每个请求的
Authorization: Bearer <token>请求头中。
令牌验证 (执行 API)
JWTBearer 安全依赖项会对每个请求进行一次令牌验证:
从
Authorization: Bearer请求头中提取令牌。通过
JWTValidator执行加密签名验证。验证标准声明(
exp、iat、aud— 如果已配置则验证nbf和iss)。如果缺少
scope声明,则默认为"execution"。创建带有任务实例 ID 和声明的
TIToken对象。将验证后的令牌缓存在 ASGI 请求范围内,以供整个请求生命周期使用。
路由级别的强制执行由 require_auth 处理:
对照路由的
allowed_token_types检查令牌的scope(在路由注册时由ExecutionAPIRoute根据token:*安全作用域预先计算)。强制执行
ti:self作用域 — 验证令牌的sub声明是否与{task_instance_id}路径参数匹配,从而防止 Worker 访问其他任务的端点。
令牌刷新 (执行 API)
JWTReissueMiddleware 会自动刷新接近过期的有效令牌:
在每次响应后,中间件会检查令牌的剩余有效期。
如果剩余总有效期的 20% 以下(至少 30 秒),服务器将生成一个新令牌,保留所有原始声明(包括
scope和sub)。刷新后的令牌会返回在
Refreshed-API-Token响应头中。客户端的
_update_auth()钩子会检测到此请求头,并透明地为后续请求更新BearerAuth实例。
此机制确保长时间运行的任务不会因令牌过期而丢失 API 访问权限,无需 Worker 重新进行身份验证。
无令牌撤销 (执行 API)
执行 API 令牌不受撤销机制影响。它们是短生命周期的(默认 10 分钟),并由 JWTReissueMiddleware 自动刷新,因此撤销不属于执行 API 安全模型的一部分。一旦执行 API 令牌颁发给 Worker,它在过期前将一直有效。
默认计时设置 (执行 API)
设置 |
默认值 |
|---|---|
|
600 秒(10 分钟) |
|
|
令牌刷新阈值 |
剩余有效期 20%(至少 30 秒,即在默认 600 秒令牌生命周期下,过期前约 120 秒) |
DAG 文件处理器与触发器
DAG 文件处理器和触发器是内部 Airflow 组件,它们也与执行 API 交互,但使用的是进程内 (in-process) 传输 (InProcessExecutionAPI),而不是网络传输。这种进程内 API:
使用 ASGI/WSGI 网桥直接在同一进程内运行执行 API 应用程序。
潜在地绕过了 JWT 身份验证 — JWT 承载依赖项被重写,以始终返回具有
"execution"作用域的合成TIToken,从而实际上绕过了令牌验证。同样潜在地绕过了针对特定资源的访问控制(对连接、变量和 XCom 的访问检查被重写为始终允许)。
Airflow 实现了软件防护机制,防止在这些组件中从 DAG 作者代码中进行意外的数据库直接访问。然而,由于解析 DAG 文件和执行触发器代码的子进程以与父进程相同的 Unix 用户身份运行,这些防护无法防范蓄意攻击。恶意 DAG 作者可能会检索父进程的数据库凭据(通过 /proc/<PID>/environ、配置文件或密钥管理器访问),并获得对元数据数据库和所有执行 API 操作的完全读写权限 — 无需有效的 JWT 令牌。
这与 Worker/任务执行不同,后者在部署级别实现隔离 — 敏感的数据库凭据配置在 Worker 部署配置中根本不存在,且它们仅通过执行 API 进行通信。
在默认部署中,单个 DAG 文件处理器实例为所有团队解析 DAG 文件,单个触发器实例处理所有团队的所有触发器。这意味着来自不同团队的 DAG 作者代码在同一进程中执行,并潜在地共享对进程内执行 API 和元数据数据库的访问权限。
对于需要隔离的多团队部署,部署管理员必须采取部署级别的措施,为每个团队运行单独的 DAG 文件处理器和触发器实例 — Airflow 不提供内置的每团队 DFP 或触发器实例支持。即使有单独的实例,它们也保持与父进程相同的 Unix 用户身份。为防止凭据检索,部署管理员必须实现 Unix 用户级别的隔离(以不同的低特权用户运行子进程)或网络级别的限制。
请参阅 Airflow 安全模型 了解完整的安全影响、部署加固指南以及计划的战略和战术改进。
工作负载隔离与当前限制
有关工作负载隔离保护、当前限制和计划改进的详细讨论,请参阅 工作负载隔离与当前限制。
配置参考
所有 JWT 相关配置参数:
参数 |
默认值 |
描述 |
|---|---|---|
|
如果缺失则自动生成 |
用于对令牌进行签名的对称密钥。必须在所有组件中保持一致。与 |
|
无 |
PEM 编码私钥( |
|
|
签名算法。根据密钥类型自动检测:对称密钥为 |
|
自动 ( |
放置在令牌头中的密钥 ID。对于对称密钥将被忽略。 |
|
无 |
颁发者声明 ( |
|
无 |
REST API 令牌的受众声明 ( |
|
86400 (24h) |
REST API 令牌生命周期(秒)。 |
|
3600 (1h) |
CLI 令牌生命周期(秒)。 |
|
10 |
用于令牌验证的时钟偏移容差(秒)。 |
|
无 |
用于令牌验证的 JWKS 端点 URL 或本地文件路径。与 |
|
600 (10 min) |
执行 API 令牌生命周期(秒)。 |
|
|
执行 API 令牌的受众声明。 |
重要提示
所有 Airflow 组件的时间同步至关重要。请使用 NTP(例如 ntpd 或 chrony)保持时钟同步。超过配置的 jwt_leeway 的时钟偏移将导致身份验证失败。