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的名字,当配置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. 校验规则
插件会通过配置的
paramater
与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. 配置案例
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...
4.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
4.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....
5. 相关错误码
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合法性与格式。
6. 使用限制
插件元数据的大小限制为50KB。
转发参数列表最大限制为16个,
claimName
与parameterName
长度不超过32个字符, 仅支持[A-Za-z0-9-_]。JWK的
alg
支持列表为:RS256, RS384, RS512, ES256, ES384, ES512, HS256, HS384, HS512。