基于 JWT 的 token 认证
认证流程
上图是 API 网关利用 JWT 实现认证的整个业务流程时序图,下面我们用文字来详细描述图中标注的步骤:
- 客户端向 API 网关发起认证请求,请求中一般会携带终端用户的用户名和密码;
- API 网关将请求直接转发给后端服务;
- 后端服务读取请求中的验证信息(比如用户名、密码)进行验证,验证通过后使用私钥生成标准的 token,返回给 API 网关;
- API 网关将携带 token 的应答返回给客户端,客户端需要将这个 token 缓存到本地;
- 客户端向 API 网关发送业务请求,请求中携带 token;
- API 网关使用用户设定的公钥对请求中的 token 进行验证,验证通过后,将请求透传给后端服务;
- 后端服务进行业务处理后应答;
- API 网关将业务应答返回给客户端。
JWT
Json Web Token(JWT),是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准 RFC7519。JWT 一般可以用作独立的身份验证令牌,可以包含用户标识、用户角色和权限等信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,特别适用于分布式站点的登录场景。
JWT 的主要构成
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
如上面的例子所示,JWT 就是一个字符串,由三部分通过 .
分隔的子字符串分别构成:
- Header(头部)
- Payload(数据)
- Signature(签名)
Header
JWT 的头部承载两个信息:
- 声明类型,这里是 JWT
- 声明加密的算法
完整的头部就像下面这样的 JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后将头部进行 Base64 编码(该编码是可以对称解码的),构成了第一部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Payload
载荷就是存放有效信息的地方。定义细节如下 (这些载荷都是可选的,是标准建议的而已):
iss:令牌颁发者。表示该令牌由谁创建,该声明是一个字符串
sub: Subject Identifier,iss提供的终端用户的标识,在iss范围内唯一,最长为255个ASCII个字符,区分大小写
aud:Audience(s),令牌的受众,分大小写的字符串数组
exp:Expiration time,令牌的过期时间戳。超过此时间的token会作废, 该声明是一个整数,是1970年1月1日以来的秒数
iat: 令牌的颁发时间,该声明是一个整数,是1970年1月1日以来的秒数
jti: 令牌的唯一标识,该声明的值在令牌颁发者创建的每一个令牌中都是唯一的,为了防止冲突,它通常是一个密码学随机值。这个值相当于向结构化令牌中加入了一个攻击者无法获得的随机熵组件,有利于防止令牌猜测攻击和重放攻击。
也可以新增用户系统需要使用的自定义字段,比如下面的例子添加了 name
用户昵称:
{
"sub": "1234567890",
"name": "John Doe"
}
然后将其进行 Base64 编码,得到 Jwt 的第二部分:
JTdCJTBBJTIwJTIwJTIyc3ViJTIyJTNBJTIwJTIyMTIzNDU2Nzg5MCUyMiUyQyUwQSUyMCUyMCUyMm5hbWUlMjIlM0ElMjAlMjJKb2huJTIwRG9lJTIyJTBBJTdE
Signature
这个部分需要 Base64 编码后的 Header 和 Base64 编码后的 Payload 使用 .
连接组成的字符串,然后通过 Header 中声明的加密方式进行加密($secret
表示用户的私钥),然后就构成了 jwt 的第三部分。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, '$secret');
JWT 的优缺点
优点
- 紧凑且自包含:JWT 使用 JSON 格式存储信息,可以将所有需要传递的数据都存储在一个 token 中,避免了多次查询数据库或其他存储系统。
- 无状态:由于 JWT 包含了所有必要的信息,服务器无需在自己的存储系统中存储用户的认证信息,使得服务器可以更好地进行水平扩展。
- 可扩展性:JWT 可以轻松地扩展为支持更多的功能和信息,例如使用 JWE 对 JWT 进行加密。
- 安全性:JWT 使用签名来验证其完整性和真实性,防止被篡改,防 CSRF 攻击
缺点
- 无法撤销:一旦 JWT 签发后,在过期之前都是有效的,无法主动撤销。如果需要撤销某个用户的访问权限,则需要在服务端保存黑名单或者修改密钥。
- 数据量较大:由于 JWT 包含了所有必要的信息,其数据量可能较大。在每次请求中传递大量数据可能会导致网络负载增加。
- 无法处理会话状态变更:由于 JWT 是无状态的,服务器无法在生成后对其进行修改。如果需要处理会话状态变更(例如修改用户权限),则需要重新生成新的 JWT。
JWT、JWS、JWE
区别就是用处不同,token,签名,加密
JWT(JSON Web Token)是一种用于认证和授权的开放标准,定义了一种紧凑且自包含的方式来传递信息。JWT 通常由三部分组成:头部(header)、载荷(payload)和签名(signature)。
JWS(JSON Web Signature)是 JWT 的一种实现方式,用于对 JWT 进行数字签名以保证其完整性和真实性。JWS 使用私钥对整个 JWT 进行签名,接收方可以使用公钥来验证签名是否有效。
JWE(JSON Web Encryption)是另一种 JWT 的实现方式,用于对 JWT 进行加密以保护其内容的机密性。JWE 使用公钥加密整个 JWT,只有拥有相应私钥的接收方才能解密并获取其中的信息。
JWT 的实现
JWT 主要由三部分组成,前两步分就是 base64 编码,后一部分签名需要算法。算法涉及到对称加密和非对称加密,对称加密常见的是 HS256,非对称加密常见的就是 RS256。
确定了算法后,就可在 在线JWT生成器 生成对应的密钥对。
HS256 如下所示:
对称加密,k 即是密钥,复制到 token 生成器即可。验证也是同一玩意儿
RS256 如下所示:
那些乱七八糟的字段是描述加密算法的,不用管,主要是公钥和私钥需要注意,复制第一部分内容到 token 生成的库就行了,复制最后一部分到验证 token 签名的库。
JWT 的应用
阿里云 API 网关 JWT 插件
HS256 的配置文件
密码一定要 base64 编码
那个 k 就是密码,默认的是 n,但是 HS256 是 k,一定注意要 base64 编码
---
parameter: authorization # 从指定的参数中获取JWT, 对应API的参数
parameterLocation: header # API为映射模式时可选, API为透传模式下必填, 用于指定JWT的读取位置, 仅支持`query`,`header`
claimParameters: # claims参数转换, 网关会将jwt claims映射为后端参数
- claimName: aud # claim名称,支持公共和私有
parameterName: authorization # 映射后参数名称
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: oct
alg: HS256
k: SmJnaXZMS04yVTN5anlDY2hPZFJ3TkJPeFVCNVZOSmliNFVHSnBUVnZDZDdtWWtiRmd4RHlueERGS29qRnVVbmsrTXBuWGQxeEY2N2FFQk1vTndGVmRrdzhML3Y2c3lmVU16NzY2TDh3RnRWYkJMK0lnZVB6K0JNMVJXeUVnbnhXY0FXaTdXL1ZYZ1dESG5xbTRTQUloVTNxbHdZanQ5VXByUFdPcEZTYjJXYzJCbjJCVnJCTHJUTFVQRmljOWlRQVljK1pob01YS2VzS3FIVUN3LzcwK2pRRVMyRis2dFVqcTlVdko4aDQ3WFpuQ21iaGgwN1lhOFhCQzB0S0JqdGNyaDFLVWxHc2Vxbk5uQmZheXYrMkIwNVNBcUlKWnk2aWVCTU1NR3Z1M1dlMFJGRmFrK1B1VURnQXhJVm9LaGVRPT0=
默认配置文件
阿里云如何配置 HS256 算法的 JWT 认证插件,基础配置如下,需要修改为 HS256 算法的示例
---
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
kid: O8fpdhrViq2zaaaBEWZITz # 在只配置一个JWK时,kid是可选的,但如果中JWT包含了kid,网关会校验kid的一致性
alg: RS256
n: qSVxcknOm0uCq5vGsOmaorPDzHUubBmZZ4UXj-9do7w9X1uKFXAnqfto4TepSNuYU2bA_-tzSLAGBsR-BqvT6w9SjxakeiyQpVmexxnDw5WZwpWenUAcYrfSPEowFYgqZwJQMN8ptxkd0170PFauwACOx4Hfr-9FPGy8NCoIO4MfLXzJ3mJ7xqgIZp3NIOGXz-GIAbCf13ii7kSStpYqN3L_zzpvXUAos1FJ9IPXRV84tIZpFVh2lmRh0h8ImK-vI42dwlD_hOIzayL1Xno2R0T-d5AwTSdnep7g-Fwu8-sj4cCRWq3bd61Zs2QOJ8iustH0vSRMYdP5oYQ