#json #jwt #tokens #key-pair #authentication #jws

keygate-jwt

为 Rust 提供的简单易用、安全且具有观点的 JWT (JSON Web Tokens) 实现

8 个稳定版本

1.4.2 2023 年 10 月 10 日
1.4.1 2023 年 3 月 9 日
1.4.0 2023 年 2 月 27 日
1.2.1 2023 年 1 月 31 日
1.1.0 2022 年 10 月 31 日

#185 in 加密学

每月 21 次下载

MIT 许可证

92KB
2K SLoC

GitHub CI Docs.rs crates.io

keygate-jwt

一个新的 JWT (JSON Web Tokens) 实现,专注于简洁性,同时避免常见的 JWT 安全陷阱。

keygate-jwt 有观点,并且仅支持安全的签名算法

JWT 算法名称 功能 描述
EdDSA eddsa Ed25519 (推荐)
ES256 ecdsa ECDSA over p256 / SHA-256
ES384 ecdsa ECDSA over p384 / SHA-384
ES256K ecdsa ECDSA over secp256k1 / SHA-256

尽可能使用 EdDSA,但是并非所有 JWT 库都支持它,因此也支持 ecdsa

keygate-jwt 使用纯 Rust 实现并可以直接编译为 WebAssembly/WASI。

重要提示:JWT 的目的是验证数据是由知道密钥的实体创建的。它不提供任何机密性:JWT 数据仅以 BASE64 编码,并且未加密。

用法

cargo.toml:

[dependencies]
keygate-jwt = "1.0"

错误作为 keygate-jwt::Error 值返回

签名

签名需要一个密钥对:用于创建令牌的密钥和只能验证它们的公钥。

当双方最终不信任对方时,始终使用签名方案,例如客户端和 API 提供商之间交换的令牌。

密钥对和令牌创建

密钥创建

ES256

use keygate_jwt::prelude::*;

// create a new key pair for the `ES256` JWT algorithm
let key_pair = ES256KeyPair::generate();

// a public key can be extracted from a key pair:
let public_key = key_pair.public_key();

ES384

use keygate_jwt::prelude::*;

// create a new key pair for the `ES384` JWT algorithm
let key_pair = ES384KeyPair::generate();

// a public key can be extracted from a key pair:
let public_key = key_pair.public_key();

可以将密钥作为字节导出以供以后重用,或从字节导入,对于 RSA,可以从单个参数、DER 编码或 PEM 编码的数据导入。

使用 OpenSSL 和 PEM 导入私钥创建 RSA 密钥对

openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
let key_pair = RS384KeyPair::from_pem(private_pem_file_content)?;
let public_key = RS384PublicKey::from_pem(public_pem_file_content)?;

令牌创建和验证与 HS* 算法相同,只是使用密钥对创建令牌,并使用相应的公钥进行验证。

令牌创建

/// create claims valid for 2 hours
let claims = Claims::create(Duration::from_hours(2));
let token = key_pair.sign(claims)?;

令牌验证

let claims = public_key.verify_token::<NoCustomClaims>(&token, None)?;

可用的验证选项与对称算法中使用的选项相同。

高级用法

自定义声明

声明对象默认支持所有标准声明,并且可以直接或通过便捷的帮助函数进行设置。

let claims = Claims::create(Duration::from_hours(2)).
    with_issuer("Example issuer").with_subject("Example subject");

您还可以定义自己的声明。这些声明必须存在于可序列化类型中(这需要serde crate)

#[derive(Serialize, Deserialize)]
struct MyAdditionalData {
   user_is_admin: bool,
   user_country: String,
}
let my_additional_data = MyAdditionalData {
   user_is_admin: false,
   user_country: "FR".to_string(),
};

使用自定义数据创建声明

let claims = Claims::with_custom_claims(my_additional_data, Duration::from_secs(30));

使用自定义数据验证声明。注意自定义数据类型的存在

let claims = public_key.verify_token::<MyAdditionalData>(&token, None)?;
let user_is_admin = claims.custom.user_is_admin;

在验证之前查看元数据

在标签或签名验证之前,属性如密钥标识符可能有助于从一组中选择正确的密钥。

let metadata = Token::decode_metadata(&token)?;
let key_id = metadata.key_id();
let algorithm = metadata.algorithm();
// all other standard properties are also accessible

重要:您不能信任密钥ID,也不能信任算法

因此,algorithm应仅用于调试目的,永远不要用于选择密钥类型。同样,key_id应仅用于在具有相同算法的一组密钥中选择密钥。

针对重放攻击的缓解措施

keygate-jwt包含缓解重放攻击的机制

  • 可以通过with_nonce()声明函数将nonce附加到新的令牌。验证程序可以稍后拒绝任何不包括预期nonce(required_nonce验证选项)的令牌。
  • 验证程序可以拒绝创建时间过长(无论其到期日期如何)的令牌。这可以防止恶意(或受损)的签名者使用的令牌过长时间。
  • 验证程序可以拒绝在特定日期之前创建的令牌。对于特定用户,可以将最后一次成功的认证日期存储在数据库中,稍后与该选项一起使用,以拒绝更旧的(重放的)令牌。

为什么还需要另一个 JWT crate

已经有一些针对Rust的JWT crate,但它们都没有满足我们的需求

  • 不支持不安全的算法(如RSAHS256)和哈希函数(如SHA1
  • 最小化、仅Rust的依赖项

致谢

此crate基于Frank Denis的jwt-simple项目。显著变化包括引入cargo功能标志和不需要的依赖项,以及删除对不安全算法的支持。1

依赖项

~1.2–3MB
~66K SLoC