JSON Web Token
JSON Web Tokens (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
来禁用用户缓存。有关此属性的更多信息,请参阅 opensearch.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 负载中存储用户角色的键。该值必须是逗号分隔的角色列表。您可以将 roles_key 配置为列表,以从嵌套的 JWT 声明中提取角色。 |
required_audience | JWT 必须指定的目标受众名称。您可以设置单个值(例如,project1 )或多个逗号分隔的值(例如,project1,admin )。如果设置了多个值,JWT 必须至少有一个必需的受众。此参数对应于 JWT 的 aud 声明。 |
required_issuer | JSON 负载中存储的 JWT 的目标颁发者。这对应于 JWT 的 iss 声明。 |
jwt_clock_skew_tolerance_seconds | 设置一个时间窗口(以秒为单位),以补偿 JWT 身份验证服务器和 OpenSearch 节点时钟时间之间的任何差异,从而防止因时间不对齐导致的身份验证失败。安全插件默认设置为 30 秒。使用此设置可应用自定义值。 |
由于 JWT 是自包含的,并且用户是在 HTTP 级别进行身份验证的,因此不需要额外的 authentication_backend
。将此值设置为 noop
。
对称密钥算法:HMAC
基于哈希的消息认证码 (HMAC) 是一组算法,通过共享密钥提供消息签名的方式。该密钥在身份验证服务器和安全插件之间共享。它必须在 signing_key
设置中配置为 base64 编码的值:
jwt_auth_domain:
...
config:
signing_key: "a3M5MjEwamRqOTAxOTJqZDE="
...
非对称密钥算法:RSA 和 ECDSA
RSA 和 ECDSA 是非对称加密和数字签名算法,它们使用公钥/私钥对来签署和验证令牌。这意味着它们使用私钥来签署令牌,而安全插件只需要知道公钥即可验证。
由于无法使用公钥颁发新令牌——并且可以对令牌的创建者做出有效假设——RSA 和 ECDSA 被认为比 HMAC 更安全。
要使用 RS256,您只需在 JWT 配置中将(非 base64 编码的)RSA 公钥配置为 signing_key
:
jwt_auth_domain:
...
config:
signing_key: |-
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQK...
-----END PUBLIC KEY-----
...
安全插件会自动检测算法(RSA/ECDSA)。如有必要,您可以将密钥分成多行。
HTTP 请求的 Bearer 认证
在 HTTP 请求中传输 JWT 最常见的方式是将其作为 HTTP 头部与 Bearer 认证方案一起添加:
Authorization: Bearer <JWT>
头部的默认名称是 Authorization
。如果您的身份验证服务器或代理需要,您也可以使用 jwt_header
配置键来使用不同的 HTTP 头部名称。
与 HTTP 基本认证一样,在 HTTP 请求中传输 JWT 时,您应该使用 HTTPS 而不是 HTTP。
HTTP 请求的查询参数
尽管在 HTTP 请求中传输 JWT 最常见的方式是使用头部字段,但安全插件也支持参数。使用以下键配置 GET
参数的名称:
config:
signing_key: ...
jwt_url_parameter: "parameter_name"
subject_key: ...
roles_key: ...
与 HTTP 基本认证一样,您应该使用 HTTPS 而不是 HTTP。
已验证的注册声明
以下注册声明会自动验证:
- “iat”(颁发时间)声明
- “nbf”(不早于)声明
- “exp”(过期时间)声明
支持的格式和算法
安全插件支持所有标准算法的数字签名紧凑型 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 时,OpenSearch 会验证签名。签名会在每个身份验证请求中进行验证。
您无需将用于验证的加密密钥存储在本地 config.yml
文件的 authc
部分,而是可以指定一个 JSON Web Key Set (JWKS) 端点,以从颁发者服务器上的位置检索密钥。这种 JWT 验证方法有助于简化公钥和证书的管理。
在 OpenSearch 中,这种验证方法利用了 OpenID Connect 身份验证域配置。要指定 JWKS 端点,请将配置中的 openid_connect_url
设置替换为 jwks_uri
设置,并将 URL 作为其值添加到设置中。示例如下:
openid_auth_domain:
http_enabled: true
transport_enabled: true
order: 0
http_authenticator:
type: openid # use the OpenID Connect domain, since JWT is part of this authentication.
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 所需的密钥。有关 JSON Web Key 内容和格式的更多信息,请参阅 JSON Web Key (JWK) 格式。
常见问题故障排除
本节详细介绍了如何对安全配置中的常见问题进行故障排除。
验证正确的声明
确保 JWT 令牌包含正确的 iat
(颁发时间)、nbf
(不早于)和 exp
(过期时间)声明,所有这些声明都会被 OpenSearch 自动验证。
JWT URL 参数
当使用包含默认管理员角色 all_access
的 JWT URL 参数(例如,curl https://:9200?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
直接映射到内部用户而不是后端角色。为此,请导航到 Security > Roles > all_access 并选择 Mapped users 选项卡。选择 Manage mapping 并在 Users 部分添加“admin”。
然后用户应该会出现在 Mapped Users 选项卡上。
OpenSearch Dashboards 配置
尽管 JWT URL 参数认证在直接查询 OpenSearch 时有效,但在用于访问 OpenSearch Dashboards 时会失败。
解决方案: 确保 opensearch_dashboards.yml
配置文件中存在以下行:
opensearch_security.auth.type: "jwt"
opensearch_security.jwt.url_param: <your-param-name-here>