jwt-auth插件实现了基于JWT(JSON Web Tokens)进行认证鉴权的功能,支持从HTTP请求的URL参数、请求头、Cookie字段解析JWT,同时验证该Token是否有权限访问。相较于配置JWT认证鉴权,jwt-auth插件额外提供了调用方身份识别的能力,支持对不同调用方配置不同的JWT凭证。本文介绍如何配置jwt-auth插件。
插件类型
认证鉴权。
配置字段
认证配置
名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
consumers | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证。 |
global_auth | array of string | 选填(仅实例级别配置) | - | 只能在实例级别配置,若配置为true,则全局生效认证机制; 若配置为false,则只对做了配置的域名和路由生效认证机制,若不配置则仅当没有域名和路由配置时全局生效(兼容老用户使用习惯)。 |
子项consumers
中的每一项配置字段说明如下。
名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
name | string | 必填 | - | 配置该consumer的名称。 |
jwks | string | 选填。jwks和remote_jwks必填一个。 | - | JSON Web Key (JWK)指定的JSON格式字符串,是由验证JWT中签名的公钥(或对称密钥)组成的JSON Web Key Set。 |
remote_jwks | object | 选填。jwks和remote_jwks二选一。 | {"uri":"http://127.0.0.1/keys","service":"test.static","port":"80","ttl":30000,"timeout":3000} | 用于从指定服务的指定URI定时拉取JWKS,和JWKS字段同时配置时,以此字段配置拉取的JWKS优先。 |
issuer | string | 选填 | - | JWT的签发者,需要和payload中的iss字段保持一致。 |
claims | object | 选填 | - | key/value分别对应payload中的key/value,可以用于校验多个字段是否和payload中一致,例如配置aud: mobile-site,则要求payload中的aud必须是mobile-site。 |
claims_to_headers | array of object | 选填 | - | 抽取JWT的payload中指定字段,设置到指定的请求头中,然后转发给后端。 |
from_headers | array of object | 选填 | [{"name":"Authorization","value_prefix":"Bearer"}] | 从指定的请求头中抽取JWT。 |
from_params | array of string | 选填 | access_token | 从指定的URL参数中抽取JWT。 |
from_cookies | array of string | 选填 | - | 从指定的cookie中抽取JWT。 |
clock_skew_seconds | number | 选填 | 60 | 校验JWT的exp和iat字段时允许的时钟偏移量,单位为秒。 |
keep_token | bool | 选填 | true | 转发给后端时是否保留JWT。 |
只有当from_headers
,from_params
,from_cookies
均未配置时,才会使用默认值。
from_headers
中每一项的配置字段说明如下。名称
数据类型
填写要求
默认值
描述
name
string
必填
-
抽取JWT的请求Header。
value_prefix
string
必填
-
对请求Header的value去除此前缀,剩余部分作为JWT。
claims_to_headers
中每一项的配置字段说明如下。名称
数据类型
填写要求
默认值
描述
claim
string
必填
-
JWT payload中的指定字段,要求必须是字符串或无符号整数类型。
header
string
必填
-
从payload取出字段的值设置到这个请求头中,转发给后端。
override
bool
选填
true
true时,存在同名请求头会进行覆盖。
false时,追加同名请求头。
remote_jwks
中每一项的配置字段说明如下。名称
数据类型
填写要求
默认值
描述
uri
string
必填
-
请求URL。
service
string
必填
-
K8s服务填写示例:foo.default.svc.cluster.local。
Nacos服务填写示例:foo.DEFAULT-GROUP.public.nacos。
如果是名为test的DNS服务,则填写test.dns。
如果是名为test的静态IP服务,则填写test.static。
port
bool
必填
-
服务端口
timeout
bool
选填
3000
服务请求超时时间,单位是毫秒
ttl
bool
选填
30000
缓存时间,单位是毫秒。
鉴权配置(非必需)
名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
allow | array of string | 选填(非实例级别配置) | - | 只能在路由或域名等细粒度规则上配置,对于符合匹配条件的请求,配置允许访问的 consumer,从而实现细粒度的权限控制。 |
在一个规则里,鉴权配置和认证配置不可同时存在。
对于通过认证鉴权的请求,请求的Header会被添加一个
X-Mse-Consumer
字段,用以标识调用者的名称。
配置示例
全局配置认证和路由粒度进行鉴权
以下配置将对网关特定路由或域名开启JWT Auth认证和鉴权。
如果一个JWT能匹配多个jwks,则按照配置顺序命中第一个匹配的consumer。
在实例级别做如下插件配置:
consumers:
- name: consumer1
issuer: abcd
jwks: |
{
"keys": [
{
"kty": "oct",
"kid": "123",
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
"alg": "HS256"
}
]
}
- name: consumer2
issuer: abc
jwks: |
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "123",
"alg": "RS256",
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
}
]
}
在route-a
和route-b
两个路由做如下插件配置:
allow:
- consumer1
在*.example.com
和test.com
两个域名做如下插件配置:
allow:
- consumer2
此例指定的
route-a
和route-b
即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将允许name
为consumer1
的调用者访问,其他调用者不被允许访问。此例指定的
*.example.com
和test.com
用于匹配请求的域名,当发现域名匹配时,将允许name
为consumer2
的调用者访问,其他调用者不被允许访问。
根据该配置,允许访问以下请求。假设以下请求会匹配到route-a这条路由。
将JWT设置在URL参数中。
curl 'http://xxx.hello.com/test?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEy****.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
将JWT设置在HTTP请求头中。
curl http://xxx.hello.com/test -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
认证鉴权通过后,请求的Header中会被添加一个X-Mse-Consumer
字段,在此例中其值为consumer1
,用以标识调用方的名称。
下列请求将拒绝访问。
请求未提供JWT,返回401。
curl http://xxx.hello.com/test
根据请求提供的JWT匹配到的调用者无访问权限,返回403。
# consumer1不在*.example.com的allow列表里 curl 'http://xxx.example.com/test' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
网关实例级别开启,但特定路由关闭
在实例级别做如下插件配置:
global_auth: true
consumers:
- name: consumer1
issuer: abcd
jwks: |
{
"keys": [
{
"kty": "oct",
"kid": "123",
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
"alg": "HS256"
}
]
}
- name: consumer2
issuer: abc
jwks: |
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "123",
"alg": "RS256",
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
}
]
}
在route-b
路由做如下插件配置:
_disable_: true
此例route-b
即在创建网关路由时填写的路由名称。因为_disable_
设置为true
,当匹配到这个路由时,将关闭此插件功能,不进行JWT认证,所有人均可访问。
当没有匹配到route-b
路由时,因为全局配置中global_auth
设置为了true
,表明对所有请求开启认证,所以将进行JWT认证,并且允许consumer1
和consumer2
都能访问。
域名级别开启,但特定路由关闭
在实例级别做如下插件配置:
consumers:
- name: consumer1
issuer: abcd
jwks: |
{
"keys": [
{
"kty": "oct",
"kid": "123",
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
"alg": "HS256"
}
]
}
- name: consumer2
issuer: abc
jwks: |
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "123",
"alg": "RS256",
"n": "i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw"
}
]
}
在route-b
路由做如下插件配置:
_disable_: true
在*.example.com
域名做如下插件配置:
allow:
- consumer1
- consumer2
此例中route-b
即在创建网关路由时填写的路由名称。因为_disable_
设置为true
,当匹配到这个路由时,将关闭此插件功能,不进行JWT认证,所有人均可访问。
此例中指定的*.example.com
用于匹配请求的域名。当发现域名匹配时,将允许name
为consumer1
或consumer2
的调用者访问,其他调用者不允许访问。
配置规则顺序会依次执行匹配,有任一规则匹配到,就会执行对应配置,不再匹配其他规则。因为这里配置路由匹配优先级高于域名,即对当前配置的域名生效,而对域名下的特定路由关闭。
相关错误码
HTTP状态码 | 出错信息 | 原因说明 |
401 | Jwt missing | 请求头未提供JWT。 |
401 | Jwt expired | JWT已经过期。 |
401 | Jwt verification fails | JWT payload校验失败,如iss不匹配。 |
403 | Access Denied | 无权限访问当前路由。 |