Link Search Menu Expand Document Documentation Menu

JSON Web Token

JSON Web Tokens (JWT) 是基于 JSON 的访问令牌,用于断言一个或多个声明。它们通常用于实现单点登录 (SSO) 解决方案,并属于基于令牌的身份验证系统类别。JWT 的基本信息传输和身份验证生命周期描述如下步骤:

  1. 用户通过提供凭据(例如,用户名和密码)登录到身份验证服务器。
  2. 身份验证服务器验证凭据。
  3. 身份验证服务器创建并签署访问令牌。
  4. 身份验证服务器将令牌返回给用户。
  5. 用户存储访问令牌。
  6. 用户在每次请求中使用该令牌向其希望使用的服务发送访问令牌。
  7. 服务验证令牌并授予或拒绝访问。
  8. 获得授权后,用户可以访问直到令牌的过期时间。过期时间通常由颁发者在令牌的负载中设置。

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 必须设置为 falseauthentication_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”。

image

然后用户应该会出现在 Mapped Users 选项卡上。

image

OpenSearch Dashboards 配置

尽管 JWT URL 参数认证在直接查询 OpenSearch 时有效,但在用于访问 OpenSearch Dashboards 时会失败。

解决方案: 确保 opensearch_dashboards.yml 配置文件中存在以下行:

opensearch_security.auth.type: "jwt"
opensearch_security.jwt.url_param: <your-param-name-here>