Json Web Token(RFC7519), 是一种便捷的可用于网关进行请求认证的j鉴权方案,API 网关可以通过托管用户的Public JWK实现对请求进行JWT认证,并将claims作为后端参数转发给后端,已方便用户简化开发后端应用的开发。

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

  • 不再需要额外配置一个授权API,可以用任何方式来生成与分发JWT,API网关仅负责通过公钥JWK认证JWT
  • 支持不含kidjwk
  • 支持配置多个jwk
  • 支持直接从headerquery读取Token,不需要每个API都设置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`超期时间的检查
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上配置了与paramter配置同名的参数时,paramterLocation可以忽略,否则会在调用时报错
  • 如果使用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的名字, 支持配置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插件会校验时间字段的合法性
  • 默认情况下,API网关会校验Token的exp过期时间字段,如果希望跳过exp的超期检查,需要配置ignoreExpirationCheck: true
  • 如果配置了tokenParameters字段转发,当Token的claims中包含对应的字段时,claim字段会转发给后端,不存在的claim不会转发

4. 配置案例

4.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
			

4.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. 相关错误码

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合法性与格式。

6. 使用限制

  • 插件元数据的大小限制为16380个字符。
  • 转发参数列表最大限制为16个, claimNameparameterName长度不超过32个字符, 仅支持[A-Za-z0-9-_]。
  • JWK的alg支持列表为: RS256, RS384, RS512, ES256, ES384, ES512, HS256, HS384, HS512。