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。支持不含
kid的jwk。支持配置多个
jwk。支持直接从
header或query读取Token,不需要每个API都设置Token参数。支持从请求的cookie头的字段中读取Token。
当
JWT以Authorization 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认证插件支持以下算法:
签名算法 | 支持的 |
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认证插件会使用配置的parameter与parameterLocation来读取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的名字。parameterName:转发到后端的参数名称。location:转发到后端的参数位置,支持header、query、path、formData。当配置为
path时,后端path必须包含同名的参数,如/path/{userId}。当配置为
formData时,仅支持在参数映射且包体为form格式下才能正确映射到后端的表单中。
可以在
jwk字段中配置单个Key,或在jwks字段中配置多个Key。不包含
kid字段的Key只允许配置一个。包含
kid的Key可以配置多个,但kid必须唯一。
3. 校验规则
插件会通过配置的
parameter与parameterToken来获取到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认证插件数据集
登录API网关控制台,在左侧导航栏单击。
在插件列表页面,选择插件数据集。
单击右上角的创建数据集,在弹出框中自定义数据集的名称,类型选择JWT_JWK_LIST,单击确定即可生成数据集。
进入刚生成的数据集,单击右上角的创建数据集条目,在数据值中配置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=073957d8d2823be4f6c0cad23c7645585.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 # 执行黑名单逻辑时以此参数作为判断条件,此参数的值在blockByDataSet的数据集中,就block
blockByDataSet: 87b65008e92541938537b1a4a236eda5 # 黑名单数据集
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 | 当在 |
403 | S403JU | Claim | 当在 |
403 | A403JT | Invalid JWT: ${Reason} | 请求中提供的 |
400 | I400JD | JWT Deserialize Failed: | 请求中提供的 |
403 | A403JK | No matching JWK, | 请求 |
403 | A403JE | JWT is expired at | 请求中提供的 |
400 | I400JP | Invalid JWT plugin config: ${JWT} |
|
当出现非预期应答码时,请检查HTTP应答中的X-Ca-Error-Code头中获取ErrorCode,从X-Ca-Error-Message头中获取ErrorMessage。当出现A403JT或I400JD错误码时,可访问jwt.io网站来检查自己的Token合法性与格式。
7. 使用限制
插件元数据的大小限制为50KB。
转发参数列表最大限制为16个,
claimName与parameterName长度不超过32个字符,仅支持[A-Za-z0-9-_]。JWK的
alg支持列表为:RS256, RS384, RS512, ES256, ES384, ES512, HS256, HS384, HS512。