62个版本
0.12.9 | 2024年2月20日 |
---|---|
0.12.7 | 2024年1月7日 |
0.12.6 | 2023年12月17日 |
0.12.1 | 2023年11月30日 |
0.2.2 | 2020年11月3日 |
#22 in Web编程
95,929 每月下载量
在 25 个 库中使用(直接使用 20 个)
175KB
4K SLoC
JWT-Simple
为Rust提供的新JWT(JSON Web Tokens)实现,侧重于简单性,同时避免常见的JWT安全陷阱。
jwt-simple
无偏见,支持所有常见的认证和签名算法
JWT算法名称 | 描述 |
---|---|
HS256 |
HMAC-SHA-256 |
HS384 |
HMAC-SHA-384 |
HS512 |
HMAC-SHA-512 |
BLAKE2B |
BLAKE2B-256 |
RS256 |
RSA with PKCS#1v1.5 padding / SHA-256 |
RS384 |
RSA with PKCS#1v1.5 padding / SHA-384 |
RS512 |
RSA with PKCS#1v1.5 padding / SHA-512 |
PS256 |
RSA with PSS padding / SHA-256 |
PS384 |
RSA with PSS padding / SHA-384 |
PS512 |
RSA with PSS padding / SHA-512 |
ES256 |
ECDSA over p256 / SHA-256 |
ES384 |
ECDSA over p384 / SHA-384 |
ES256K |
ECDSA over secp256k1 / SHA-256 |
EdDSA |
Ed25519 |
jwt-simple
可以直接编译为 WebAssembly/WASI。它与 Fastly 的 Compute 服务完全兼容。
重要提示:JWT 的目的是验证数据是由知道密钥的实体创建的。它不提供任何类型的机密性:JWT 数据简单地编码为 BASE64,并且未加密。
用法
cargo.toml
:
[dependencies]
jwt-simple = "0.12"
Rust
use jwt_simple::prelude::*;
错误以 jwt_simple::Error
值的形式返回(即 thiserror
crate 的 Error
类型的别称)。
认证(对称,HS*
JWT算法)示例
身份验证方案使用相同的密钥来创建和验证令牌。换句话说,双方最终都需要相互信任,否则验证者也可以创建任意的令牌。
密钥和令牌创建
密钥创建
use jwt_simple::prelude::*;
// create a new key for the `HS256` JWT algorithm
let key = HS256Key::generate();
可以使用 key.to_bytes()
将密钥导出为字节,并使用 HS256Key::from_bytes()
进行恢复。
令牌创建
/// create claims valid for 2 hours
let claims = Claims::create(Duration::from_hours(2));
let token = key.authenticate(claims)?;
-> 完成!
令牌验证
let claims = key.verify_token::<NoCustomClaims>(&token, None)?;
-> 完成!无需额外步骤。
密钥过期时间、起始时间、身份验证标签等将自动验证。如果给定的密钥的身份验证标签无效,函数将失败,并返回 JWTError::InvalidAuthenticationTag
。
如果需要,可以在 claims
对象中检查完整的声明集。如果使用 NoCustomClaims
,则表示应用程序仅使用标准声明集,但也可以支持应用程序定义的声明。
可以通过 ValidationOptions
结构启用可选的额外验证步骤。
let mut options = VerificationOptions::default();
// Accept tokens that will only be valid in the future
options.accept_future = true;
// Accept tokens even if they have expired up to 15 minutes after the deadline,
// and/or they will be valid within 15 minutes.
// Note that 15 minutes is the default, since it is very common for clocks to be slightly off.
options.time_tolerance = Some(Duration::from_mins(15));
// Reject tokens if they were issued more than 1 hour ago
options.max_validity = Some(Duration::from_hours(1));
// Reject tokens if they don't include an issuer from that set
options.allowed_issuers = Some(HashSet::from_strings(&["example app"]));
// see the documentation for the full list of available options
let claims = key.verify_token::<NoCustomClaims>(&token, Some(options))?;
请注意,allowed_issuers
和 allowed_audiences
不是字符串,而是字符串的集合(使用 Rust 标准库中的 HashSet
类型),因为应用程序可以允许多个返回值。
签名(非对称,RS*
、PS*
、ES*
和EdDSA
算法)示例
签名需要一个密钥对:用于创建令牌的密钥和只能验证它们的公钥。
如果双方最终不信任对方,则始终应使用签名方案,例如客户端和 API 提供者之间交换的令牌。
密钥对和令牌创建
密钥创建
ES256
use jwt_simple::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 jwt_simple::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 和算法都不值得信赖。这是 JWT 标准的一个无法修复的设计缺陷。
因此,algorithm
应仅用于调试目的,绝不能用于选择密钥类型。同样,key_id
应仅用于选择为同一算法制作的密钥集中的密钥。
如果最初使用签名方案创建了令牌,则至少必须禁止使用 HS*
进行验证。
创建和附加密钥标识符
密钥标识符指示验证者应使用哪个公钥(或共享密钥)进行验证。它们可以随时附加到现有的共享密钥、密钥对和公钥上。
let public_key_with_id = public_key.with_key_id(&"unique key identifier");
而不是将其委托给应用程序,jwt-simple
也可以为现有密钥创建这样的标识符。
let key_id = public_key.create_key_id();
这创建了一个文本编码的标识符,将其附加并返回。
如果标识符已附加到共享密钥或密钥对上,则使用它们创建的令牌将包括它。
减轻重放攻击的措施
jwt-simple
包含缓解重放攻击的机制。
- 可以使用
create_nonce()
声明函数创建和附加到新令牌上的非ces。验证程序可以稍后拒绝任何不包括预期的非ces(required_nonce
验证选项)的令牌。 - 验证程序可以拒绝创建时间过长的令牌,无论它们的过期日期是什么。这可以防止恶意(或受损)签发者使用的令牌时间过长。
- 验证程序可以拒绝在某个日期之前创建的令牌。对于给定用户,可以将最后一次成功的认证日期存储在数据库中,并稍后与该选项一起使用以拒绝较旧的(重放的)令牌。
CWT(CBOR)支持
开发代码包括一个 cwt
cargo功能,它启用对CWT令牌的实验性解析和验证。
请注意,CWT 不支持自定义声明。所需的标识符 尚未标准化。
此外,现有的用于JSON和CBOR反序列化的Rust crate不安全。不受信任的方可以发送需要大量内存和CPU进行反序列化的序列化对象。已经为JSON添加了补丁,但使用当前的Rust工具,对于CBOR来说会很棘手。
作为一种缓解措施,我们强烈建议拒绝在应用程序上下文中会太大太多的令牌。可以通过 max_token_length
验证选项来完成此操作。
解决与boring
库的编译问题
作为与依赖项(boring
crate)的兼容性问题的临时解决方案,此库可以编译为仅使用Rust实现。
为此,请在您的Cargo配置中使用 default-features=false, features=["pure-rust"]
导入crate。
不要无条件的这样做。这只适用于非常特定的设置和目标,并且只在 boring
crate 的问题得到解决之前。在Cargo中配置此设置的方式也可能在未来版本中发生变化。
针对 musl
库的静态构建不需要这种解决方案。只需使用 cargo-zigbuild
构建您的项目即可。
在Web浏览器中的使用
支持 wasm32-freestanding
目标(在Rust中有时仍称为 wasm32-unknown-unknown
)。
然而,强烈建议使用原生JavaScript实现。JavaScript中有高质量的JWT实现,利用WebCrypto API,其性能和安全性比WebAssembly模块更优。
为什么还需要另一个JWT库
本包并非对JWT的支持。JWT 是一种糟糕的设计,是“但这是一种标准”并不一定意味着它是好的许多例子之一。
如果您控制令牌的创建和验证,我强烈推荐使用PASETO或Biscuit。
然而,JWT在业界仍被广泛使用,并且与流行的API通信仍然是绝对必要的。
本包的设计目标是
- 易于使用,即使是对于初学者来说也很简单
- 避免常见的JWT API陷阱
- 支持广泛使用的功能。我本想将算法选择限制在Ed25519,但为了连接到现有的API,需要其他方法,因此只提供它们(除了明显的原因之外,不提供
None
签名方法)。 - 最小化代码复杂性和外部依赖
- 自动执行常见任务以防止误用。签名验证和声明验证会自动执行,而不是依赖于应用程序。
- 仍然允许高级用户访问JWT令牌中包含的所有内容,如果他们确实需要的话
- 在WebAssembly环境中直接工作,以便可以在函数即服务平台上使用。
依赖关系
~5–12MB
~250K SLoC