JWT认证插件

Json Web Token(RFC7519), 是一种便捷的可用于网关进行请求认证的鉴权方案,API 网关可以通过托管用户的Public JWK实现对请求进行JWT认证,并将claims作为后端参数转发给后端,以方便用户简化开发后端应用的开发。本文主要是JWT认证插件的配置,如果需要了解JWT的token认证流程及基础知识可以参考基于JWT的token认证

原有在API上配置的OpenId Connect功能目前可以通过JWT认证插件实现,推荐使用JWT认证插件配置,相比配置在API上的OpenId Connect配置,JWT认证插件具备以下优势:

  • 不再需要额外配置一个授权API,可以用任何方式来生成与分发JWT,API网关仅负责通过公钥JWK认证JWT

  • 支持不含kidjwk

  • 支持配置多个jwk

  • 支持直接从headerquery读取Token,不需要每个API都设置Token参数

  • 支持从请求的cookie头的字段中读取Token

  • JWTAuthorization bearer {token}方式传输时,可以通过parameter: Authorization, parameterLocation: header方式配置已实现正确的Token读取

  • 通过添加preventJtiReplay: true配置,可支持基于claim:jti的防重放检查

  • 通过添加bypassEmptyToken: true配置,可在Token不存在时跳过验证直接转发给后端

  • 通过添加ignoreExpirationCheck: true配置,可忽略Token的exp超期校验

如果你配置了JWT认证插件并绑定到了已配置了OpenId Connect功能的API上,原有API上的OpenId Connect的功能将被插件的配置覆盖。

1. 获取JWK (Json Web Key)

JWT认证插件通过Json Web Key(RFC7517),实现JWT的签名与认证,配置JWT认证插件首先需要生成一个有效的Json Web Key,您可以通过自行生成,或搜索Json Web Key Generator寻找可用的在线生成工具,如mkjwk.org,一个可用的Json Web Key大概如下所示,其中私钥用于对Token进行签名,公钥需要配置在JWT认证插件中用于对Token进行签名,一个合法的JWK大概格式如下:

{
  "kty": "RSA",
  "e": "AQAB",
  "kid": "O9fpdhrViq2zaaaBEWZITz",
  "use": "sig",
  "alg": "RS256",
  "n": "qSVxcknOm0uCq5vGsOmaorPDzHUubBmZZ4UXj-9do7w9X1uKFXAnqfto4TepSNuYU2bA_-tzSLAGBsR-BqvT6w9SjxakeiyQpVmexxnDw5WZwpWenUAcYrfSPEoNU-0hAQwFYgqZwJQMN8ptxkd0170PFauwACOx4Hfr-9FPGy8NCoIO4MfLXzJ3mJ7xqgIZp3NIOGXz-GIAbCf13ii7kSStpYqN3L_zzpvXUAos1FJ9IPXRV84tIZpFVh2lmRh0h8ImK-vI42dwlD_hOIzayL1Xno2R0T-d5AwTSdnep7g-Fwu8-sj4cCRWq3bd61Zs2QOJ8iustH0vSRMYdP5oYQ"
}        

这里展示的是JSON格式,当使用YAML格式配置插件,需要转换*

  • JWT认证插件只需要配置Public Key, 请妥善保存好您的Private Key,目前JWT认证插件支持以下算法:

签名算法

支持的alg取值

RSASSA-PKCS1-V1_5 with SHA-2

RS256, RS384, RS512

Elliptic Curve (ECDSA) with SHA-2

ES256, ES384, ES512

HMAC using SHA-2

HS256, HS384, HS512

重要

当配置HS256,HS384,HS512类型的Key时,密钥需要为Base64 UrlEncode后的值,如遇到Invalid Signature问题,请检查您的Key的格式是否与生成Token的Key一致

2. 插件配置

您可以选择JSON或者YAML格式的来配置您的插件,两种格式的schema相同,请搜索yaml to json转换工具来进行配置格式的转换,YAML格式的模板见下表。

---
parameter: X-Token           # 从指定的参数中获取JWT, 对应API的参数 
parameterLocation: header    # API为映射模式时可选, API为透传模式下必填, 用于指定JWT的读取位置, 仅支持`query`,`header`
preventJtiReplay: false      # 是否开启针对`jti`的防重放检查, 默认: false
bypassEmptyToken: false      # 当`jwt`为空时是否允许验证通过
ignoreExpirationCheck: false # 是否允许忽略`exp`超期时间的检查
orAppAuth: false             # 默认为false,阿里云APP认证和JWT插件都会校验;为true时,阿里云APP认证通过后将不再校验JWT插件,JWT插件通过后将不再校验阿里云APP认证
claimParameters:             # claims参数转换, 网关会将jwt claims映射为后端参数
- claimName: aud             # claim名称,支持公共和私有
  parameterName: X-Aud       # 映射后参数名称
  location: header           # 映射后参数位置, 支持`query,header,path,formData`
- claimName: userId          # claim名称,支持公共和私有
  parameterName: userId      # 映射后参数名称
  location: query            # 映射后的参数位置, 支持`query,header,path,formData`
#
# `Json Web Key`的`Public Key`
jwk:
  kty: RSA
  e: AQAB
  use: sig
  alg: RS256
  n: qSVxcknOm0uCq5vGsOmaorPDzHUubBmZZ4UXj-9do7w9X1uKFXAnqfto4TepSNuYU2bA_-tzSLAGBsR-BqvT6w9SjxakeiyQpVmexxnDw5WZwpWenUAcYrfSPEoNU-0hAQwFYgqZwJQMN8ptxkd0170PFauwACOx4Hfr-9FPGy8NCoIO4MfLXzJ3mJ7xqgIZp3NIOGXz-GIAbCf13ii7kSStpYqN3L_zzpvXUAos1FJ9IPXRV84tIZpFVh2lmRh0h8ImK-vI42dwlD_hOIzayL1Xno2R0T-d5AwTSdnep7g-Fwu8-sj4cCRWq3bd61Zs2QOJ8iustH0vSRMYdP5oYQ
#
# 可以支持配置多个JWK, 可以与jwk字段一起配置
# 配置多个JWK时,kid是必选的,如果JWT中没有提供kid,则校验失败
jwks:
- kid: O9fpdhrViq2zaaaBEWZITz         # 在只配置一个JWK时,kid是可选的,但如果中JWT包含了kid,网关会校验kid的一致性
  kty: RSA
  e: AQAB
  use: sig
  alg: RS256
  n: qSVxcknOm0uCq5v....
- kid: 10fpdhrViq2zaaaBEWZITz         # 在只配置一个JWK时,kid是可选的,但如果中JWT包含了kid,网关会校验kid的一致性
  kty: RSA
  e: AQAB
  use: sig
  alg: RS256
  n: qSVxcknOm0uCq5v...                
  • JWT认证插件会使用配置的parameterparameterLocation来读取JWT的值,如:parameter: X-Token, parameterLocation: header表示从请求的X-Token头读取JWT

  • 如果API上配置了与parameter配置同名的参数时,parameterLocation可以忽略,否则会在调用时报错

  • 如果使用Authorization头传输Token,如:Authorization bearer {token}, 可以通过parameter: Authorization, parameterLocation: header方式配置,JWT插件可以正确读取到Token的取值

  • 当配置了preventJtiReplay: true时,插件会使用claims中的jti字段进行防重放检查

  • 当配置了bypassEmptyToken: true时,当Token参数为空时,允许跳过检查,直接转发请求给后端

  • 当配置了ignoreExpirationCheck: true时,会跳过exp的超期检查,否则网关会检查Token是否已超期

  • 如果需要API网关将Token中的claims转发给后端应用,可以通过tokenParameters字段配置转发参数

    • claimName: token中claim的名字,当配置HS256,HS384,HS512类型的Key时,密钥为Base64 UrlEncode后的值,如遇到Invalid Signature问题,请检查您的Key的格式是否与生成Token的Key一致支持配置kid

    • parameterName:转发到后端的参数名称

    • location:转发到后端的参数位置,支持header,query,path,formData

      • 当配置为path时,后端path必须包含同名的参数,如/path/{userId}

      • 当配置为formData时,仅支持在参数映射且包体为form格式下才能正确映射到后端的form中

  • 可以在jwk字段中配置单个Key,或在jwks字中配置多个Key

    • 不包含kid字段的Key只允许配置一个

    • 包含kid的Key可以配置多个,但kid必须唯一

3. 校验规则

  • 插件会通过配置的paramaterparameterToken来获取到Token, 如果希望在Token不存在时仍然转发请求给后端,需要配置bypassEmptyToken: true

  • 当配置多个Key是的选择原则为

    • 优先选择与请求Token中kid一致的Key来进行签名校验

    • 不含kid的Key只能配置一个,当不存在kid与请求Token一致的情况时,使用不含kid的Key执行签名校验

    • 如果没有配置不含kid的Key,如果请求Token中未包含kid,或通过kid未匹配到Key则报错A403JK

  • 如果Token中包含了iat, nbf, exp字段,JWT插件会校验时间字段的合法性,时间单位是秒(s)。

  • 默认情况下,API网关会校验Token的exp过期时间字段,如果希望跳过exp的超期检查,需要配置ignoreExpirationCheck: true

  • 如果配置了tokenParameters字段转发,当Token的claims中包含对应的字段时,claim字段会转发给后端,不存在的claim不会转发

4. JWT认证插件支持插件数据集

4.1 创建JWT认证插件数据集

  1. 登录API网关控制台,在左侧栏导航栏单击API管理 > 插件管理

  2. 在插件列表页面,选择插件数据集

  3. 单击右上角的创建数据集,在弹出框中自定义数据集的名称,类型选择JWT_JWK_LIST,单击确定即可生成数据集。

  4. 进入刚生成的数据集,单击右上角的创建数据集条目,在数据值中配置JWT认证插件支持的JWK,在过期时间中配置公钥的有效期。

    重要

    当配置多个JWK时,需要使用不同的kid,JWK数据值的顺序要按照以下示例顺序配置。

    kty: RSA
    e: AQAB
    use: sig
    kid: N3h666
    alg: RS256
    n: qfzaxmlnl...

4.2 JWT认证插件配置插件数据集示例

---
parameter: X-Token         # 从指定的参数中获取JWT, 对应API的参数
parameterLocation: header  # API为映射模式时可选, API为透传模式下必填, 用于指定JWT的读取位置, 仅支持`query`,`header`
claimParameters:           # claims参数转换, 网关会将jwt claims映射为后端参数
- claimName: aud           # claim名称,支持公共和私有
  parameterName: X-Aud     # 映射后参数名称
  location: header         # 映射后参数位置, 支持`query,header,path,formData`
- claimName: userId        # claim名称,支持公共和私有
  parameterName: userId    # 映射后参数名称
  location: query          # 映射后的参数位置, 支持`query,header,path,formData`
preventJtiReplay: false    # 是否开启针对`jti`的防重放检查, 默认: false
ignoreExpirationCheck: true  # 是否允许忽略`exp`超期时间的检查
jwkListDataSet: cb4f000b6b8244329ac25XXXc8a4f9d6  # 插件数据集ID
说明

如果不生效需提交工单升级实例版本。

5. 配置案例

5.1. 配置单个JWK

---
parameter: X-Token         # 从指定的参数中获取JWT, 对应API的参数
parameterLocation: header  # API为映射模式时可选, API为透传模式下必填, 用于指定JWT的读取位置, 仅支持`query`,`header`
claimParameters:           # claims参数转换, 网关会将jwt claims映射为后端参数
- claimName: aud           # claim名称,支持公共和私有
  parameterName: X-Aud     # 映射后参数名称
  location: header         # 映射后参数位置, 支持`query,header,path,formData`
- claimName: userId        # claim名称,支持公共和私有
  parameterName: userId    # 映射后参数名称
  location: query          # 映射后的参数位置, 支持`query,header,path,formData`
preventJtiReplay: false    # 是否开启针对`jti`的防重放检查, 默认: false
#
# `Json Web Key`的`Public Key`
jwk:
  kty: RSA
  e: AQAB
  use: sig
  alg: RS256
  n: qSVxcknOm0uCq5vGsOmaorPDzHUubBmZZ4UXj-9do7w9X1uKFXAnqfto4TepSNuYU2bA_-tzSLAGBsR-BqvT6w9SjxakeiyQpVmexxnDw5WZwpWenUAcYrfSPEoNU-0hAQwFYgqZwJQMN8ptxkd0170PFauwACOx4Hfr-9FPGy8NCoIO4MfLXzJ3mJ7xqgIZp3NIOGXz-GIAbCf13ii7kSStpYqN3L_zzpvXUAos1FJ9IPXRV84tIZpFVh2lmRh0h8ImK-vI42dwlD_hOIzayL1Xno2R0T-d5AwTSdnep7g-Fwu8-sj4cCRWq3bd61Zs2QOJ8iustH0vSRMYdP5oYQ
                        

5.2. 配置多个JWK

---
parameter: Authorization   # 从Authorization头中获取Token 
parameterLocation: header  # 从Authorization头中获取Token
claimParameters:           # claims参数转换, 网关会将jwt claims映射为后端参数
- claimName: aud           # claim名称,支持公共和私有
  parameterName: X-Aud     # 映射后参数名称
  location: header         # 映射后参数位置, 支持`query,header,path,formData`
- claimName: userId        # claim名称,支持公共和私有
  parameterName: userId    # 映射后参数名称
  location: query          # 映射后的参数位置, 支持`query,header,path,formData`
preventJtiReplay: true     # 是否开启针对`jti`的防重放检查, 默认: false
jwks:
- kid: O9fpdhrViq2zaaaBEWZITz         # 配置多个JWK时,需要使用不同的`kid`
  kty: RSA
  e: AQAB
  use: sig
  alg: RS256
  n: qSVxcknOm0uCq5v....
- kid: 10fpdhrViq2zaaaBEWZITz         # 配置多个JWK时,需要使用不同的`kid`
  kty: RSA
  e: AQAB
  use: sig
  alg: RS256
  n: qSVxcknOm0uCq5v...

5.3. 从请求的Cookie中的字段中读取Token

---
parameter: cookie         # 从指定的参数中获取JWT, 对应API的参数
parameterLocation: header  # API为映射模式时可选, API为透传模式下必填, 用于指定JWT的读取位置, 仅支持`query`,`header`
parameterSection: token    # 例如cookie的值为 username=tom ; token=abcsef
claimParameters:           # claims参数转换, 网关会将jwt claims映射为后端参数
- claimName: aud           # claim名称,支持公共和私有
  parameterName: X-Aud     # 映射后参数名称
  location: header         # 映射后参数位置, 支持`query,header,path,formData`
- claimName: userId        # claim名称,支持公共和私有
  parameterName: userId    # 映射后参数名称
  location: query          # 映射后的参数位置, 支持`query,header,path,formData`
preventJtiReplay: true     # 是否开启针对`jti`的防重放检查, 默认: false
jwks:
- kid: O9fpdhrViq2zaaaBEWZITz         # 配置多个JWK时,需要使用不同的`kid`
  kty: RSA
  e: AQAB
  use: sig
  alg: RS256
  n: qSVxcknOm0uCq5v....
- kid: 10fpdhrViq2zaaaBEWZITz         # 配置多个JWK时,需要使用不同的`kid`
  kty: RSA
  e: AQAB
  use: sig
  alg: RS256
  n: qSVxcknOm0uCq5v...

有些Web场景,为了安全考虑,用户想把Token放在Cookie的一个指定的字段保存,API网关的JWT插件支持从请求的Cookie的字段中读取Token,使用parameterSection参数特性就可以指定字段名称了。比如请求的Cookie头如下所示,API网关就可以把Cookie中的Token提取出来。

Cookie: acw_tc=123; token=0QzRCMDBBQUYwRjE1MjYxQzU0QjY4NEM5MTc1NTQ1OUVCOTIzNzA4RDk3MDg5MzlDOTMQTVENDZCRUI1NkYyMEUyO; csrf=073957d8d2823be4f6c0cad23c764558

5.4 配置黑名单

JWT认证插件黑名单的使用场景是屏蔽已经获得了正式Token,但被拉到黑名单的用户请求。API网关的JWT认证插件为这种场景结合插件数据集的能力上线了根据Token中解密出来的claim参数进行拒绝请求的能力。API网关不仅能够拒绝命中了Blocking条件的请求,还能自定义拒绝的应答。具体的配置方法如下,请注意所有以block开头的参数定义:

---
parameter: Authorization   # 从Authorization头中获取Token 
parameterLocation: header  # 从Authorization头中获取Token
claimParameters:           # claims参数转换, 网关会将jwt claims映射为后端参数
- claimName: aud           # claim名称,支持公共和私有
  parameterName: X-Aud     # 映射后参数名称
  location: header         # 映射后参数位置, 支持`query,header,path,formData`
- claimName: userId        # claim名称,支持公共和私有
  parameterName: userId    # 映射后参数名称
  location: query          # 映射后的参数位置, 支持`query,header,path,formData`
blockClaimParameterName: userId  # 映射后的参数位置, 支持`query,header,path,formData`
blockByDataSet: 87b65008e92541938537b1a4a236eda5  # 映射后的参数位置, 支持`query,header,path,formData`
blockStatusCode: 403       # 拒绝请求返回的应答StatusCode定义
blockResponseHeaders:      # 拒绝请求返回的应答头定义
  Content-Type: application/xml
blockResponseBody:         # 拒绝请求返回的Body定义
  <Reason>be blocked</Reason>
jwks:
- kid: O9fpdhrViq2zaaaBEWZITz         # 配置多个JWK时,需要使用不同的`kid`
  kty: RSA
  e: AQAB
  use: sig
  alg: RS256
  n: qSVxcknOm0uCq5v....

6. 相关错误码

Status

Code

Message

Description

400

I400JR

JWT required

未找到JWT参数

403

S403JI

Claim jti is required when preventJtiReplay:true

当在JWT授权插件中配置了防重放功能时,请求未提供有效的jti

403

S403JU

Claim jti in JWT is used

当在JWT授权插件中配置了防重放功能时,请求提供的jti已被使用

403

A403JT

Invalid JWT: ${Reason}

请求中提供的JWT非法

400

I400JD

JWT Deserialize Failed: ${Token}

请求中提供的JWT解析失

403

A403JK

No matching JWK, kid:${kid} not found

请求JWT中的kid没有匹配的JWK

403

A403JE

JWT is expired at ${Date}

请求中提供的JWT已过期

400

I400JP

Invalid JWT plugin config: ${JWT}

JWT授权插件配置错误

当出现非预期应答码时,请检查HTTP应答中的X-Ca-Error-Code头中获取ErrorCode,从X-Ca-Error-Message头中获取ErrorMessage当出现A403JTI400JD错误码时,可访问jwt.io网站来检查自己的Token合法性与格式。

7. 使用限制

  • 插件元数据的大小限制为50KB。

  • 转发参数列表最大限制为16个,claimNameparameterName长度不超过32个字符,仅支持[A-Za-z0-9-_]。

  • JWK的alg支持列表为:RS256, RS384, RS512, ES256, ES384, ES512, HS256, HS384, HS512。