2 个版本
0.2.1 | 2023 年 9 月 27 日 |
---|---|
0.2.0 | 2023 年 9 月 27 日 |
#7 in #json-web-token
每月下载 140 次
45KB
734 行
JWKS-Client 是一个用 Rust 编写的库,用于使用 JSON Web 密钥存储解码和验证 JWT 令牌。
重大变更
现在版本为 2.0。由于 Genna Wingert 的贡献,支持 async/await。需要 Rust 稳定版 1.39 或更高版本
** 重要 **
JWKS-Client 是专门为与使用 Rocket 的项目一起使用而设计的。遗憾的是,crates.io 中的 Rocket 版本与 JWKS-Client 所需的 Ring 版本不兼容。在下一个 Rocket 版本发布之前,请考虑在您的 Cargo.toml
中使用以下内容
[dependencies]
jwks-client = "0.1.4"
rocket = { git = "https://github.com/jfbilodeau/Rocket", version = "0.5.0-dev"}
# Other dependencies...
[dependencies.rocket_contrib]
version = "0.5.0-dev"
git = "https://github.com/jfbilodeau/Rocket"
# Other options...
功能
库范围
- 无 panic!
- 使用 Rust 稳定版 (1.40) 构建
- 专为生产系统设计(不是学术项目)
- 简洁的结果(例如,请参阅 error::Type)
JWKS 密钥存储
- 从 HTTP 地址下载密钥集
- 将 JWT 令牌解码为头部、有效负载和签名
- 验证令牌签名、过期和提前通知
- 确定何时刷新密钥
JWT
- 在用户定义的结构体中传输头部和有效负载。请参阅下面的示例[^1]
- 标准头部和有效负载字段的访问器
JWKS-Client 是专门为了解码 GCP/Firebase JWT 而创建的,但应该可以进行少量或无修改地使用。请与我联系,提出对不同的 JWKS 密钥存储的支持。欢迎反馈、建议、投诉和批评。
基本用法
以下演示了如何从 HTTP 地址加载一组密钥并使用这些密钥验证 JWT 令牌
use jwks_client::error::Error;
use jwks_client::keyset::KeyStore;
#[tokio::main]
async fn main() {
let jkws_url = "https://raw.githubusercontent.com/jfbilodeau/jwks-client/0.1.8/test/test-jwks.json";
let key_set = KeyStore::new_from(jkws_url.to_owned()).await.unwrap();
// ...
let token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.eTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
match key_set.verify(token) {
Ok(jwt) => {
println!("name={}", jwt.payload().get_str("name").unwrap());
}
Err(Error { msg, typ: _ }) => {
eprintln!("Could not verify token. Reason: {}", msg);
}
}
}
JWKS-Client 可以用来简单地解码 JWT 令牌而不验证签名。
use jwks_client::keyset::KeyStore;
fn main() {
let key_store = KeyStore::new();
let token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.eTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
let jwt = key_store.decode(token).unwrap();
if jwt.expired().unwrap_or(false) {
println!("Sorry, token expired")
} else {
let result = jwt.payload().get_str("name");
match result {
Some(name) => {
println!("Welcome, {}!", name);
}
None => {
println!("Welcome, anonymous");
}
}
}
}
JWKS-Client 提供描述性的错误结果
use jwks_client::error::{Error, Type};
use jwks_client::keyset::KeyStore;
#[rustfmt::skip]
#[tokio::main]
async fn main() {
let url = "https://raw.githubusercontent.com/jfbilodeau/jwks-client/0.1.8/test/test-jwks.json";
let token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.eTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
let key_set = KeyStore::new_from(url.to_owned()).await.unwrap();
match key_set.verify(token) {
Ok(jwt) => {
println!("name={}", jwt.payload().get_str("name").unwrap());
}
Err(Error {
msg,
typ: Type::Header,
}) => {
eprintln!("Problem with header. Message: {}", msg);
}
Err(Error {
msg,
typ: Type::Payload,
}) => {
eprintln!("Problem with payload. Message: {}", msg);
}
Err(Error {
msg,
typ: Type::Signature,
}) => {
eprintln!("Problem with signature. Message: {}", msg);
}
Err(Error {
msg: _,
typ: Type::Expired,
}) => {
eprintln!("Token is expired.");
}
Err(Error {
msg: _,
typ: Type::Early,
}) => {
eprintln!("Too early to use token.");
}
Err(e) => {
eprintln!("Something else went wrong. Message {:?}", e);
}
}
}
[^1] JWKS-Client 可以将 JWT 有效负载(声明)解码为结构体
use serde_derive::Deserialize;
use jwks_client::keyset::KeyStore;
fn main() {
#[derive(Deserialize)]
pub struct MyClaims {
pub iss: String,
pub name: String,
pub email: String,
}
let url = "https://raw.githubusercontent.com/jfbilodeau/jwks-client/0.1.8/test/test-jwks.json";
let key_store = KeyStore::new_from(url.to_owned()).unwrap();
let token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJuYW1lIjoiQWRhIExvdmVsYWNlIiwiaXNzIjoiaHR0cHM6Ly9jaHJvbm9nZWFycy5jb20vdGVzdCIsImF1ZCI6InRlc3QiLCJhdXRoX3RpbWUiOjEwMCwidXNlcl9pZCI6InVpZDEyMyIsInN1YiI6InNidTEyMyIsImlhdCI6MjAwLCJleHAiOjUwMCwibmJmIjozMDAsImVtYWlsIjoiYWxvdmVsYWNlQGNocm9ub2dlYXJzLmNvbSJ9.eTQnwXrri_uY55fS4IygseBzzbosDM1hP153EZXzNlLH5s29kdlGt2mL_KIjYmQa8hmptt9RwKJHBtw6l4KFHvIcuif86Ix-iI2fCpqNnKyGZfgERV51NXk1THkgWj0GQB6X5cvOoFIdHa9XvgPl_rVmzXSUYDgkhd2t01FOjQeeT6OL2d9KdlQHJqAsvvKVc3wnaYYoSqv2z0IluvK93Tk1dUBU2yWXH34nX3GAVGvIoFoNRiiFfZwFlnz78G0b2fQV7B5g5F8XlNRdD1xmVZXU8X2-xh9LqRpnEakdhecciFHg0u6AyC4c00rlo_HBb69wlXajQ3R4y26Kpxn7HA";
let jwt = key_store.decode(token).unwrap();
let claims = jwt.payload().into::<MyClaims>().unwrap();
println!("Issuer: {}", claims.iss);
println!("Name: {}", claims.name);
println!("Email: {}", claims.email);
}
历史
-
0.2.0
- 重大变更:支持 async/await(感谢 Genna Wingert)
-
0.1.8
- 修复了问题 https://github.com/jfbilodeau/jwks-client/issues/1(感谢Tim Schuster的汇报和协助)
-
0.1.7
- 更新了依赖项
-
0.1.6
- 添加了
key_set::KeyStore::should_refresh()
来测试是否应该刷新密钥 - 添加了
key_set::KeyStore::refresh_interval
以确定在密钥过期之前应该提前多久刷新它们 - 一些更多的文档
- 添加了
-
0.1.5:
- 在
Cargo.toml
中添加了readme = "README.md"
- 在
-
0.1.4:
- 更新了文档——特别是如何使用JWKS-Client与Rocket配合使用
- 增加了从
KeyStore
确定是否应该刷新密钥的功能 - 修复了本页面的示例——它们现在直接来自
./examples/*
-
0.1.3:
- 将许可证更改为MIT/Apache
- 将演示移至
./example
- 增加了根据缓存控制头验证在密钥库中是否需要刷新密钥的功能
-
0.1.2:(对于破坏性更改表示歉意)
- 将模块
jwks
重命名为keyset
- 将结构体
Jwks
重命名为KeyStore
- 稍微扩展了文档
- 修复了一些示例
- 将模块
-
0.1.1:原始版本
待办事项
- 更多的文档:P
- 自动刷新密钥
(用❤️和Rust制作)
依赖项
~11–24MB
~479K SLoC