JWT 认证
功能说明
jwt-auth
插件实现了基于 JWT(JSON Web Tokens) 进行认证鉴权的功能,支持从 HTTP 请求的 URL 参数、请求头、Cookie 字段解析 JWT,同时验证该 Token 是否有权限访问。
配置字段
全局配置
名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
---|---|---|---|---|
consumers | array of object | 必填 | - | 配置服务的调用者,用于对请求进行认证 |
global_auth | bool | 选填 | - | 若配置为 true,则全局生效认证机制; 若配置为 false,则只对做了配置的域名和路由生效认证机制; 若不配置则仅当没有域名和路由配置时全局生效(兼容机制) |
consumers
中每一项的配置字段说明如下:
名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
---|---|---|---|---|
name | string | 必填 | - | 配置该 consumer 的名称 |
jwks | string | 必填 | - | https://www.rfc-editor.org/rfc/rfc7517 指定的 json 格式字符串,是由验证 JWT 中签名的公钥(或对称密钥)组成的 Json Web Key Set |
issuer | string | 必填 | - | JWT 的签发者,需要和 payload 中的 iss 字段保持一致 |
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 时,追加同名请求头 |
域名和路由级配置
名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
---|---|---|---|---|
allow | array of string | 必填 | - | 对于符合匹配条件的请求,配置允许访问的 consumer 名称 |
注意:
- 对于通过认证鉴权的请求,请求的header会被添加一个
X-Mse-Consumer
字段,用以标识调用者的名称。
配置示例
对特定路由或域名开启
以下配置将对网关特定路由或域名开启 Jwt Auth 认证和鉴权,注意如果一个 JWT 能匹配多个 jwks
,则按照配置顺序命中第一个匹配的 consumer
。
全局配置
路由级配置
对 route-a 和 route-b 这两个路由做如下配置:
对 *.exmaple.com 和 test.com 在这两个域名做如下配置:
每条匹配规则下的 allow
字段用于指定该匹配条件下允许访问的调用者列表;
若是在控制台进行配置,此例指定的 route-a
和 route-b
即在控制台创建路由时填写的路由名称,当匹配到这两个路由时,将允许 name
为 consumer1
的调用者访问,其他调用者不允许访问;
此例指定的 *.example.com
和 test.com
用于匹配请求的域名,当发现域名匹配时,将允许 name
为 consumer2
的调用者访问,其他调用者不允许访问。
认证成功后,请求的header中会被添加一个 X-Mse-Consumer
字段,其值为调用方的名称,例如 consumer-1
。
根据该配置,下列请求可以允许访问
假设以下请求会匹配到 route-a 这条路由。
将 JWT 设置在 URL 参数中。
将 JWT 设置在 HTTP 请求头中。
认证鉴权通过后,请求的 header 中会被添加一个 X-Mse-Consumer
字段,在此例中其值为 consumer1
,用以标识调用方的名称。
下列请求将拒绝访问
请求未提供 JWT,返回 401。
根据请求提供的 JWT 匹配到的调用者无访问权限,返回 403。
常见错误码说明
HTTP 状态码 | 出错信息 | 原因说明 |
---|---|---|
401 | Jwt missing | 请求头未提供JWT |
401 | Jwt expired | JWT已经过期 |
401 | Jwt verification fails | JWT payload校验失败,如iss不匹配 |
403 | Access Denied | 无权限访问当前路由 |
机制说明
1 基于 token 的认证
1.1 简介
很多对外开放的 API 需要识别请求者的身份,并据此判断所请求的资源是否可以返回给请求者。token 就是一种用于身份验证的机制,基于这种机制,应用不需要在服务端保留用户的认证信息或者会话信息,可实现无状态、分布式的 Web 应用授权,为应用的扩展提供了便利。
1.2 流程描述
上图是网关利用 JWT 实现认证的整个业务流程时序图,下面我们用文字来详细描述图中标注的步骤:
客户端向 API 网关发起认证请求,请求中一般会携带终端用户的用户名和密码;
网关将请求直接转发给后端服务;
后端服务读取请求中的验证信息(比如用户名、密码)进行验证,验证通过后使用私钥生成标准的 token,返回给网关;
网关将携带 token 的应答返回给客户端,客户端需要将这个 token 缓存到本地;
客户端向 API 网关发送业务请求,请求中携带 token;
网关使用用户设定的公钥对请求中的 token 进行验证,验证通过后,将请求透传给后端服务;
后端服务进行业务处理后应答;
网关将业务应答返回给客户端。
在这个整个过程中, 网关利用 token 认证机制,实现了用户使用自己的用户体系对自己API进行授权的能力。下面我们就要介绍网关实现 token 认证所使用的结构化令牌 Json Web Token(JWT)。
1.3 JWT
1.3.1 简介
Json Web Toke(JWT),是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准 RFC7519。JWT 一般可以用作独立的身份验证令牌,可以包含用户标识、用户角色和权限等信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,特别适用于分布式站点的登录场景。
1.3.2 JWT的构成
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
如上面的例子所示,JWT 就是一个字符串,由三部分构成:
- Header(头部)
- Payload(数据)
- Signature(签名)
Header
JWT 的头部承载两个信息:
- 声明类型,这里是 JWT
- 声明加密的算法
网关支持的加密算法如下:
完整的头部就像下面这样的 JSON:
然后将头部进行 Base64 编码(该编码是可以对称解码的),构成了第一部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Payload
载荷就是存放有效信息的地方。定义细节如下:
也可以新增用户系统需要使用的自定义字段,比如下面的例子添加了 name 用户昵称:
然后将其进行 Base64 编码,得到JWT的第二部分:
JTdCJTBBJTIwJTIwJTIyc3ViJTIyJTNBJTIwJTIyMTIzNDU2Nzg5MCUyMiUyQyUwQSUyMCUyMCUyMm5hbWUlMjIlM0ElMjAlMjJKb2huJTIwRG9lJTIyJTBBJTdE
Signature
这个部分需要 Base64 编码后的 Header 和 Base64 编码后的 Payload 使用 . 连接组成的字符串,然后通过 Header 中声明的加密方式进行加密($secret 表示用户的私钥),然后就构成了 jwt 的第三部分。
将这三部分用 . 连接成一个完整的字符串,就构成了 1.3.2 节最开始的 JWT 示例。
1.3.3 时效
网关会验证 token 中的 exp 字段,一旦这个字段过期了,网关会认为这个 token 无效而将请求直接打回。过期时间这个值必须设置。
1.3.4 JWT 的几个特点
- JWT 默认是不加密,不能将秘密数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
- JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用HTTPS 协议传输。
2 用户系统如何应用 JWT 插件保护 API
2.1 生成一对 JWK(JSON Web 密钥)
方法一、在线生成:
用户可以在这个站点https://mkjwk.org 生成用于 token 生成与验证的私钥与公钥, 私钥用于授权服务签发 JWT,公钥配置到 JWT 插件中用于网关对请求验签,注意网关使用的 jwks 格式配置,下图中Public Key需要放到keys结构体中,如:{"keys":[{"kty":"RSA","e":"AQAB",...}]}
方法二、本地生成:
本文应用 Java 示例说明,其他语言用户也可以找到相关的工具生成密钥对。 新建一个 Maven 项目,加入如下依赖:
使用如下的代码生成一对RSA密钥:
2.2 使用 JWK 中的私钥实现颁发 token 的认证服务
需要使用 2.1 节中在线生成的 Keypair JSON 字符串(三个方框内的第一个)或者本地生成的 privateKeyString JSON 字符串作为私钥来颁发 token,用于授权可信的用户访问受保护的 API,具体实现可以参考下方示例。 向客户颁发token的形式由用户根据具体的业务场景决定,可以将颁发token的功能部署到生产环境,配置成普通 API 后由访问者通过用户名密码获得,也可以直接在本地环境生成 token 后,直接拷贝给指定用户使用。