【每周一库】- JWT的Rust实现
jsonwebtoken
Rust实现的JSON Web Token库,用于安全身份验证。
安装
将以下内容加入 Cargo.toml:
jsonwebtoken = "7"
serde = {version = "1.0", features = ["derive"] }
需要Rust 1.39及以上版本
算法
这个库目前支持以下算法:
- HS256
- HS384
- HS512
- RS256
- RS384
- RS512
- PS256
- PS384
- PS512
- ES256
- ES384
如何使用
引用与结构型:
use serde::{Serialize, Deserialize};
use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey};
/// 我们的声言结构型, 需要由`Serialize` 或 `Deserialize`派生
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
company: String,
exp: usize,
}
声言
声言中可被验证的字段。
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
aud: String, // 可选。听众
exp: usize, // 必须。(validate_exp 在验证中默认为真值)。截止时间 (UTC 时间戳)
iat: usize, // 可选。发布时间 (UTC 时间戳)
iss: String, // 可选。发布者
nbf: usize, // 可选。不早于 (UTC 时间戳)
sub: String, // 可选。标题 (令牌指向的人)
}
标头
默认算法是HS256,它使用共享机密。
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?;
自定义标头和更改算法
支持RFC中的所有参数,但默认的标头只有typ
和alg
这两个集。在你需要设置kid
参数或者更改算法时可以这样做:
let mut header = Header::new(Algorithm::HS512);
header.kid = Some("blabla".to_owned());
let token = encode(&header, &my_claims, &EncodingKey::from_secret("secret".as_ref()))?;
编码
// HS256
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?;
// RSA
let token = encode(&Header::new(Algorithm::RS256), &my_claims, &EncodingKey::from_rsa_pem(include_bytes!("privkey.pem"))?)?;
将一个JWT进行编码时需要以下3个参数:
- 一个标头:
Header
结构型 - 某些声言: 你定义的结构型
- 一个key或secret
当使用HS256,HS2384或HS512时,密钥始终是共享机密,如上例所示。使用RSA / EC时,密钥应始终是PEM或DER格式的私钥内容。
如果密钥是PEM格式,则最好以lazy_static
或类似的方式生成一次EncodingKey
,然后重复使用,以实现更好的性能。
解码
// `token` 是一个有两个参数的结构型: `标头` 和 `声言` (`声言` 为你自己定义的结构型)
let token = decode::<Claims>(&token, &DecodingKey::from_secret("secret".as_ref()), &Validation::default())?;
解码
会因以下原因产生错误:
- 令牌或它对应的签名是无效的
- 令牌是无效的base64字符串
- 至少有一个预定的声言验证失败
与编码一样,使用HS256,HS2384或HS512时,密钥始终像上面的示例一样是共享机密。使用RSA / EC时,密钥应始终是PEM或DER格式的公共密钥的内容。
在某些情况下,例如,如果你不知道所使用的算法或需要获取kid
,则可以选择仅解码标头:
let header = decode_header(&token)?;
这不会执行任何签名验证或验证令牌声明。
你还可以使用base64格式的RSA密钥的公钥组件对令牌进行解码。主要用例为JWK,其中公钥采用JSON格式,如下所示:
{
"kty":"RSA",
"e":"AQAB",
"kid":"6a7a119f-0876-4f7e-8d0f-bf3ea1391dd8",
"n":"yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ"
}
// `token` 是一个有两个参数的结构型: `标头` 和 `声言` (`声言` 为你自己定义的结构型)
let token = decode::<Claims>(&token, &DecodingKey::from_rsa_components(jwk["n"], jwk["e"]), &Validation::new(Algorithm::RS256))?;
如果密钥是PEM格式,则最好以lazy_static
或类似的方式生成一次DecodingKey
,并且复用,这样会优化性能。
将 SEC1 私钥转换为 PKCS8
jsonwebtoken
目前仅支持私有EC密钥的PKCS8格式。如果你的密钥顶部带有BEGIN EC PRIVATE KEY
,则为SEC1类型,可以将其转换为PKCS8,如下所示:
openssl pkcs8 -topk8 -nocrypt -in sec1.pem -out pkcs8.pem
验证
该库自动验证exp
声明,并验证nbf
(如果存在)。你还可以验证sub
,iss
和aud
,但是需要在Validation
结构型中设置期望值。
时钟偏差会让验证时间字段比较麻烦,你可以通过设置leeway
字段为iat
,exp
和nbf
验证添加一些余地。
最后需要注意的一点是,如果不使用HS256
,则需要设置此令牌允许的算法。
#[derive(Debug, Clone, PartialEq)]
struct Validation {
pub leeway: u64, // Default: 0
pub validate_exp: bool, // Default: true
pub validate_nbf: bool, // Default: false
pub aud: Option<HashSet<String>>, // Default: None
pub iss: Option<String>, // Default: None
pub sub: Option<String>, // Default: None
pub algorithms: Vec<Algorithm>, // Default: vec![Algorithm::HS256]
}
use jsonwebtoken::{Validation, Algorithm};
// 默认验证:唯一允许的算法是HS256
let validation = Validation::default();
// 快速设置验证的方法,只需更改算法即可
let validation = Validation::new(Algorithm::HS512);
// 为exp和nbf检查添加一些余地(以秒为单位)
let mut validation = Validation {leeway: 60, ..Default::default()};
// 检查发布者
let mut validation = Validation {iss: Some("issuer".to_string()), ..Default::default()};
// 设置听众
let mut validation = Validation::default();
validation.set_audience(&"Me"); // string
validation.set_audience(&["Me", "You"]); // array of strings
- Spring Cloud构建微服务架构:服务容错保护(Hystrix断路器)【Dalston版】
- 调整渐变下降的学习率
- 多线程之传统多线程
- ios 常用的正则表达式(手机号邮箱md5加密验证空字符串等)
- Spring Cloud构建微服务架构:Hystrix监控面板【Dalston版】
- 云原生应用的12要素
- Universal-Image-Loader源码分析,及常用的缓存策略
- ios textView跟随键盘的移动
- Android:屏保软件的开发
- CoordinatorLayout
- 从零开始的Spring Security Oauth2(二)
- 简化Swagger使用的自制Starter:spring-boot-starter-swagger,欢迎使用和吐槽
- demo3同通讯录展示的方式分组排序
- Android手势研究(textview及listview对比验证)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 01 . Memcached原理,主从复制,代理,管理后台
- JsonPath验证类既Groovy重载操作符实践
- Selenium等待:sleep、隐式、显式和Fluent
- 敏捷中的端到端测试
- java编程思想第四版第十章习题
- java编程思想第四版第十一章总结
- java编程思想第四版第十一章习题
- java编程思想第四版第十三章字符串 习题
- java编程思想第四版第十三章字符串 总结
- LoRa节点开发——代码详解修改LoRaWAN相关参数
- 01 . Redis简介及部署主从复制
- 07 . Kubernetes之Service
- LoRa节点开发——代码详解如何修改发射和接收信道(频率)
- 06 . Kubernetes之Pod控制器详细介绍及应用
- Python面试题