简介
JSON Web Token,简称JWT,是一种开放的、行业标准(RFC7519)的认证方式
JWT(JSON Web Token)
认证流程
- 用户输入账号密码, 向服务器发出登录请求
- 服务器验证账号密码成功, 返回给客户端一个token (token 包含了用户身份)
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,从而识别用户身份, 并返回数据
相比session,服务器是不存储任何身份相关的数据, 即服务器为无状态的。
数据结构
JWT 由三段数据构成:
- header :以JSON存储的JWT元数据
- payload :JSON
- signature:由header和payload生成的签名
彼此用 . 相连:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ:
(1) header
{ "alg": "HS256", "typ": "JWT" }
alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256), 可选择算法包括:RSA
typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT
(2) payload Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据, 7个官方的标准字段如下:
- iss (issuer):token 的签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):token面向的用户
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):token 的唯一编号 可以添加自定义字段, 比如用于用户认证的 userId
(3) signature Signature 部分是对前两部分的签名,防止数据篡改.
签名过程是: 将 base64Url 编码后的 header 和 payload 使用指定的 密匙 和 加密算法 加密, 对加密后的结果进行 base64Url 编码, 就得到了最终的签名.
const Header = base64UrlEncode(header)
const Payload = base64UrlEncode(payload)
const signature = alg(
Header + "." + Payload,
secret
)
const JWT = Header + "." + Payload + "." + base64UrlEncode(signature)
JWK (JSON Web Key)
Json Web Key ( 缩写 JWK ) 是表示密匙的 JSON 数据结构, 里面包含该密匙对应的属性和值.
JWK 的数据结构demo:
{
"kty":"EC",
"crv":"P-256",
"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
"kid":"Public key used in JWS spec Appendix A.3 example"
}
- 密钥是椭圆曲线[DSS]密钥,
- 与P-256椭圆曲线一起使用
- x和y为椭圆曲线的两个方向上的坐标的base64url编码值
- kid 为密钥标识符
参数说明
kty (key type)参数
kty (key type)参数表示与密钥一起使用的加密算法系列,例如 RSA 或 EC
use (public key use)参数
use(public key use)参数表示 公钥 的预期用途, 主要用两个值( 用途 ):
- enc 加密数据
- sig 验证数据上的签名
key_ops (key operations) 参数
key_ops (key operations) 参数 表示 密匙 (这个密匙可以是公匙, 私匙, 和对称密匙)将要使用的操作, 比如加密, 签名, 解密等等
alg (algorithm) 参数
alg (algorithm) 参数 表示配合 密匙 使用的具体的加密算法
kid (key ID) 参数
kid (key ID) 参数 用于匹配特定的密匙, 比如在一组 JWK (JWKs) 中 选择某个 JWK
n 参数 和 e 参数
使用 RSA 算法时的公匙参数.
{ 'e': 'AQAB',
'kid': 'oyYwZSLCLVVPHdVp0jXIcLNpGn6dMCumlY-6wSenmFo',
'kty': 'RSA',
'n': 't1cKkQqPh8iOv5BhKh7Rx6A2-1ldpO_jczML_0GBKu4X-lHrY8YbJrt29jyAXlWM8vHC7tXsqgUG-WziRD0D8nhnh10XC14SeH-3mVuBqph-TqhXTWsh9gtAIbeUHJjEI4I79QK4_wquPHHIGZBQDQQnuMh6vAS3VaUYJdEIoKvUBnAyY35kJZgyJSbrxLsEExL2zujUD_OY-_In2bq_3rFtDGNlgHyC7Gu2zXSXvfOA4O5m9BBXOc7eEqj7PoOKNaTxLN3YcuRtgR6NIXL4KLb6oyvIzoeiprt4-9q7sc3Dnkc5EV9kwWlEW2DHzhP6VYca0WXIIXc53U1AM3ewxw'
}
如上就是将 RSA 公匙导出之后的JWK结构. kid 为该公匙的唯一标识.
x5u (X.509 URL) 参数
x5u (X.509 URL) 参数是一个 URI, 该 URI 指向 X.509 公匙证书资源, 该资源以 .pem 的形式提供证书.
x5c (X.509 certificate chain) 参数
x5c (X.509 certificate chain) 参数是一个 PKIX 证书的数组, 表示特殊的公匙, 数组中的每个字符串都是 base6 4编码后的 PKIX证书值. 包含密钥值的 PKIX 证书必须是第一个证书.
可以附加其他证书,随后的每个证书都是用来证明前一个证书的证书.第一个证书中的密钥必须与 JWK 的其他成员表示的公钥匹配
相当于是 xxx.crt 的内容放到 x5c 里面.
x5t (X.509 certificate SHA-1 thumbprint) 参数
x5t (X.509 certificate SHA-1 thumbprint) 参数是 X.509 证书 的 base64 编码后的 SHA-1 摘要.
证书中的 密钥 必须与 JWK 的其他成员表示的公钥匹配
JWKS (JSON Web Key Set)
Json Web Key Set ( 缩写 JWK) 则是表示一组 JWK 的 JSON 数据结构
JWKS 数据结构
如下是 JWKS 的数据结构demo:
{
"keys": [{
"alg": "RS256",
"kty": "RSA",
"use": "sig",
"x5c": [ "MIIC+DCCAeCgAwIBAgIJBIGjYW6hFpn2MA0GCSqGSIb3DQEBBQUAMCMxITAfBgNVBAMTGGN1c3RvbWVyLWRlbW9zLmF1dGgwLmNvbTAeFw0xNjExMjIyMjIyMDVaFw0zMDA4MDEyMjIyMDVaMCMxITAfBgNVBAMTGGN1c3RvbWVyLWRlbW9zLmF1dGgwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMnjZc5bm/eGIHq09N9HKHahM7Y31P0ul+A2wwP4lSpIwFrWHzxw88/7Dwk9QMc+orGXX95R6av4GF+Es/nG3uK45ooMVMa/hYCh0Mtx3gnSuoTavQEkLzCvSwTqVwzZ+5noukWVqJuMKNwjL77GNcPLY7Xy2/skMCT5bR8UoWaufooQvYq6SyPcRAU4BtdquZRiBT4U5f+4pwNTxSvey7ki50yc1tG49Per/0zA4O6Tlpv8x7Red6m1bCNHt7+Z5nSl3RX/QYyAEUX1a28VcYmR41Osy+o2OUCXYdUAphDaHo4/8rbKTJhlu8jEcc1KoMXAKjgaVZtG/v5ltx6AXY0CAwEAAaMvMC0wDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUQxFG602h1cG+pnyvJoy9pGJJoCswDQYJKoZIhvcNAQEFBQADggEBAGvtCbzGNBUJPLICth3mLsX0Z4z8T8iu4tyoiuAshP/Ry/ZBnFnXmhD8vwgMZ2lTgUWwlrvlgN+fAtYKnwFO2G3BOCFw96Nm8So9sjTda9CCZ3dhoH57F/hVMBB0K6xhklAc0b5ZxUpCIN92v/w+xZoz1XQBHe8ZbRHaP1HpRM4M7DJk2G5cgUCyu3UBvYS41sHvzrxQ3z7vIePRA4WF4bEkfX12gvny0RsPkrbVMXX1Rj9t6V7QXrbPYBAO+43JvDGYawxYVvLhz+BJ45x50GFQmHszfY3BR9TPK8xmMmQwtIvLu1PMttNCs7niCYkSiUv2sc2mlq1i3IashGkkgmo="
],
"n": "yeNlzlub94YgerT030codqEztjfU_S6X4DbDA_iVKkjAWtYfPHDzz_sPCT1Axz6isZdf3lHpq_gYX4Sz-cbe4rjmigxUxr-FgKHQy3HeCdK6hNq9ASQvMK9LBOpXDNn7mei6RZWom4wo3CMvvsY1w8tjtfLb-yQwJPltHxShZq5-ihC9irpLI9xEBTgG12q5lGIFPhTl_7inA1PFK97LuSLnTJzW0bj096v_TMDg7pOWm_zHtF53qbVsI0e3v5nmdKXdFf9BjIARRfVrbxVxiZHjU6zL6jY5QJdh1QCmENoejj_ytspMmGW7yMRxzUqgxcAqOBpVm0b-_mW3HoBdjQ", "e": "AQAB",
"kid": "NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg",
"x5t": "NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg"
}]
}
JWKs 必须存在参数 keys, 对应的值是 JWK 数组.
JWKS 作用
实际上, JWKS 是一种发布公匙的方式, 当然还有其他方式, 比如安装证书
但是使用 JWTs , 我们不用在服务器上安装任何东西, 只需要一个 JWKs URL, 以及对应的 npm 包, 就可以轻松的获取公匙, 并且验证 JWT.
JWKS 验证 JWT 流程
JWT 的验证规则是:
- JWT 的 payload 中有 issuer 属性,首先通过 issuer 匹配 JWKS, 比如 istio 配置了多个 issuer.
- JWT 的 header 中有 kid 属性,第二步在 jwks 的公钥列表中,找到匹配 kid 的公钥
- 使用找到的公钥进行 JWT 签名验证
jsonwebtoken 使用 JWKS 实例
// Verify using getKey callback
// Example uses https://github.com/auth0/node-jwks-rsa as a way to fetch the keys.
var jwksClient = require('jwks-rsa');
var client = jwksClient({ jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json' });
function getKey(header, callback){
client.getSigningKey(header.kid, function(err, key) {
var signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
jwt.verify(token, getKey, options, function(err, decoded) {
console.log(decoded.foo) // bar
});
jwt 使用 JWKS 验证一个 token 时, 需要提供一个获取 公匙 的函数, 上面是 getKey
- 首先将 token 的 header 解码, 作为参数传入到 getKey 函数执行
- jwksClient 从 uri 获取 jwks.json 资源, 根据 header.kid 去匹配对应的 jwt 配置
- 将匹配到的 jwt 配置转化为 公匙
- 使用 公匙 验证token
参考链接
Json Web Key
JWKS json-web-key-sets
jwt.io/
使用 Istio 进行 JWT 身份验证(充当 API 网关)
git - jsonwebtoken