JSON Web 令牌
JSON Web 令牌 (JWT) 是基于 JSON 的访问令牌,用于声明一个或多个声明。它们通常用于实现单点登录 (SSO) 解决方案,属于基于令牌的身份验证系统类别。JWT 的基本信息传输和身份验证生命周期描述如下:
用户通过提供凭据(例如,用户名和密码)登录到身份验证服务器。
身份验证服务器验证凭据。
身份验证服务器创建访问令牌并对其进行签名。
身份验证服务器将令牌返回给用户。
用户存储访问令牌。
用户将访问令牌与每个请求一起发送到要使用的服务。
服务验证令牌并授予或拒绝访问。
获得授予的访问权限后,用户在令牌过期时间之前都可以访问。过期时间通常由令牌颁发者在令牌的有效载荷中设置。
JWT 是自包含的,因为它包含验证用户所需的所有信息。令牌是 base64 编码的、已签名的 JSON 对象。
JWT 元素
JWT 由三部分组成:
头部
有效载荷
签名
头部
头部包含有关使用的签名机制的信息,包括用于编码令牌的算法。以下示例显示了头部的典型属性和值:
{
"alg": "HS256",
"typ": "JWT"
}
在这种情况下,头部声明消息使用哈希算法 HMAC-SHA256 进行签名。
有效载荷
JWT 的有效载荷包含 JWT 声明。声明是关于令牌用户的一条信息,用作唯一标识符。这使得令牌颁发者能够验证身份。声明是名称-值对,有效载荷通常包含多个声明。虽然添加声明的选项很多,但良好的做法是避免添加太多声明并使有效载荷过大,这将违背 JWT 紧凑的初衷。
有三种类型的声明:
注册声明 由 JWT 规范定义,包含一组具有保留名称的标准声明。这些声明的一些示例包括令牌颁发者 (iss)、过期时间 (exp) 和主题 (sub)。
公共声明则是由共享令牌的各方自行定义的。它们可以包含任意信息,例如用户名和用户的角色。作为预防措施,规范建议要么注册名称,要么至少确保名称与其他声明无冲突。
私有声明提供了另一种向有效载荷分配自定义信息的方式:例如,电子邮件地址。因此,它们也被称为自定义声明。共享令牌的双方必须就它们的使用达成一致,因为它们既不被视为已注册声明,也不被视为公共声明。
以下示例显示了这些 JSON 属性作为名称-值对:
{
"iss": "example.com",
"exp": 1300819380,
"name": "John Doe",
"roles": "admin, devops"
}
签名
令牌颁发者通过对 base64 编码的头部和有效载荷应用加密哈希函数来生成令牌的签名。接收 JWT 的客户端在传输的最后一步解密并验证此签名。
这三个部分——头部、有效载荷和签名——使用句点连接起来以形成完整的 JWT:
encoded = base64UrlEncode(header) + "." + base64UrlEncode(payload)
signature = HMACSHA256(encoded, 'secretkey');
jwt = encoded + "." + base64UrlEncode(signature)
示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI
配置 JWT
如果使用 JWT 作为唯一的身份验证方法,请通过将 plugins.security.cache.ttl_minutes 属性设置为 0 来禁用用户缓存。有关此属性的更多信息,请参阅 udbsx.yml。
设置身份验证域并选择 jwt 作为 HTTP 身份验证类型。因为令牌已经包含验证请求所需的所有信息,challenge 必须设置为 false,authentication_backend 设置为 noop:
jwt_auth_domain:
http_enabled: true
transport_enabled: true
order: 0
http_authenticator:
type: jwt
challenge: false
config:
signing_key: "base64 encoded key"
jwt_header: "Authorization"
jwt_url_parameter: null
subject_key: null
roles_key: null
required_audience: null
required_issuer: null
jwt_clock_skew_tolerance_seconds: 20
authentication_backend:
type: noop
下表列出了配置参数。
| 名称 | 描述 |
|---|---|
signing_key |
用于验证令牌的签名密钥。如果使用对称密钥算法,这是 Base64 编码的共享密钥。如果使用非对称算法,算法包含公钥。要传递多个密钥,请使用逗号分隔的列表或枚举密钥。 |
jwt_header |
传输令牌的 HTTP 头部。这通常是带有 Bearer 方案的 Authorization 头部,Authorization: Bearer <token>。默认为 Authorization。用 Authorization 以外的值替换此字段会阻止审计日志正确地从审计消息中编辑 JWT 头部。建议用户在使用带有审计日志的 JWT 时仅使用 Authorization。 |
jwt_url_parameter |
如果令牌不是在 HTTP 头部中传输,而是作为 URL 参数传输,请在此处定义参数名称。 |
subject_key |
JSON 有效载荷中存储用户名的键。如果未设置,则使用主题 注册声明。 |
roles_key |
JSON 有效载荷中存储用户角色的键。此键的值必须是逗号分隔的角色列表。 |
required_audience |
JWT 必须指定的受众名称。可以设置单个值(例如 project1)或多个逗号分隔的值(例如 project1,admin)。如果设置多个值,JWT 必须至少有一个必需的受众。此参数对应于 JWT 的 aud 声明。 |
required_issuer |
JSON 有效载荷中存储的 JWT 的目标颁发者。这对应于 JWT 的 iss 声明。 |
jwt_clock_skew_tolerance_seconds |
设置一个时间窗口(以秒为单位),以补偿 JWT 身份验证服务器和 UDB-SX 节点时钟时间之间的任何差异,从而防止因时间错位导致身份验证失败。Security 将 30 秒设为默认值。使用此设置应用自定义值。 |
由于 JWT 是自包含的,用户在 HTTP 级别进行身份验证,因此不需要额外的 authentication_backend。将此值设置为 noop。
对称密钥算法:HMAC
基于哈希的消息认证码 (HMAC) 是一组算法,通过共享密钥提供签名消息的方式。密钥在身份验证服务器和 Security 插件之间共享。必须在 signing_key 设置中配置为 base64 编码的值:
jwt_auth_domain:
...
config:
signing_key: "a3M5MjEwamRqOTAxOTJqZDE="
...
非对称密钥算法:RSA 和 ECDSA
RSA 和 ECDSA 是非对称加密和数字签名算法,使用公钥/私钥对来签名和验证令牌。这意味着它们使用私钥对令牌进行签名,而 Security 插件只需要知道公钥即可验证它。
由于无法使用公钥颁发新令牌——并且可以对令牌的创建者做出有效假设——RSA 和 ECDSA 被认为比 HMAC 更安全。
要使用 RS256,只需将(非 base64 编码的)RSA 公钥作为 signing_key 配置在 JWT 配置中:
jwt_auth_domain:
...
config:
signing_key: |-
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQK...
-----END PUBLIC KEY-----
...
Security 插件会自动检测算法(RSA/ECDSA)。如有必要,您可以将密钥分成多行。
HTTP 请求的承载者身份验证
在 HTTP 请求中传输 JWT 的最常见方式是将其作为带有承载者身份验证方案的 HTTP 头部添加:
Authorization: Bearer <JWT>
头部的默认名称是 Authorization。如果您的身份验证服务器或代理需要,您也可以使用不同的 HTTP 头部名称,通过 jwt_header 配置键进行设置。
与 HTTP 基本身份验证一样,在 HTTP 请求中传输 JWT 时应使用 HTTPS 而不是 HTTP。
HTTP 请求的查询参数
虽然在 HTTP 请求中传输 JWT 的最常见方式是使用头部字段,但 Security 插件也支持参数。使用以下键配置 GET 参数的名称:
config:
signing_key: ...
jwt_url_parameter: "parameter_name"
subject_key: ...
roles_key: ...
与 HTTP 基本身份验证一样,应使用 HTTPS 而不是 HTTP。
已验证的已注册声明
以下已注册声明会自动验证:
“iat”(签发时间)声明
“nbf”(不早于)声明
“exp”(过期时间)声明
支持的格式和算法
Security 插件支持具有所有标准算法的数字签名的紧凑型 JWT:
HS256: HMAC using SHA-256
HS384: HMAC using SHA-384
HS512: HMAC using SHA-512
RS256: RSASSA-PKCS-v1_5 using SHA-256
RS384: RSASSA-PKCS-v1_5 using SHA-384
RS512: RSASSA-PKCS-v1_5 using SHA-512
PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
ES256: ECDSA using P-256 and SHA-256
ES384: ECDSA using P-384 and SHA-384
ES512: ECDSA using P-521 and SHA-512
使用 JWKS 端点验证 JWT
验证已签名 JWT 的签名是授予用户访问权限的最后一步。当客户端发送带有 REST 请求的 JWT 时,UDB-SX 会验证签名。每个身份验证请求都会验证签名。
与其将用于验证的加密密钥存储在本地 config.yml 文件的 authc 部分中,不如指定一个 JSON Web 密钥集 (JWKS) 端点来从其颁发者服务器上的位置检索密钥。这种验证 JWT 的方法有助于简化公钥和证书的管理。
openid_auth_domain:
http_enabled: true
transport_enabled: true
order: 0
http_authenticator:
type: openid # 使用 OpenID Connect 域,因为 JWT 是此身份验证的一部分。
challenge: false
config:
subject_key: preferred_username
roles_key: roles
jwks_uri: https://keycloak.example.com:8080/auth/realms/master/.well-known/jwks-keys.json
authentication_backend:
type: noop
该端点应由 JWT 颁发者记录。您可以使用它来检索验证已签名 JWT 所需的密钥。
常见问题排查
本节详细说明如何排查安全配置中的常见问题。
验证正确的声明
确保 JWT 令牌包含正确的 iat(签发时间)、nbf(不早于)和 exp(过期)声明,所有这些都由 UDB-SX 自动验证。
JWT URL 参数
当使用包含默认管理员角色 all_access 的 JWT URL 参数时(例如,curl http://localhost:10200?jwtToken=<jwt-token>),请求失败并抛出以下错误:
{
"error":{
"root_cause":[
{
"type":"security_exception",
"reason":"no permissions for [cluster:monitor/main] and User [name=admin, backend_roles=[all_access], requestedTenant=null]"
}
],
"type":"security_exception",
"reason":"no permissions for [cluster:monitor/main] and User [name=admin, backend_roles=[all_access], requestedTenant=null]"
},
"status":403
}
要更正此问题,请确保 all_access 角色直接映射到内部用户,而不是映射到后端角色。为此,请导航到 安全 > 角色 > all_access 并选择 映射的用户 选项卡。选择 管理映射 并将 “admin” 添加到 用户 部分。

然后用户应出现在 映射的用户 选项卡上。

UDB-SX Dashboards 配置
尽管 JWT URL 参数身份验证在直接查询 UDB-SX 时有效,但在用于访问 UDB-SX Dashboards 时失败。
解决方案: 确保 udbsx_dashboards.yml 配置文件中存在以下行:
udbsx_security.auth.type: "jwt"
udbsx_security.jwt.url_param: <您的参数名称>