4 个版本
0.2.1 | 2024年3月25日 |
---|---|
0.2.0 | 2024年3月23日 |
0.1.1 | 2024年3月23日 |
0.1.0 | 2024年3月20日 |
176 在 身份验证 中
每月 21 次下载
25KB
387 行
Tower 中间件,用于从请求的授权载体解析 JWT 令牌,并将反序列化后的声明存储在请求扩展中。
这是基于 jsonwebtoken 包构建的,并支持该包支持的所有算法。
由于这是一个 Tower 中间件,因此可以在任何框架(如 Axum、Tonic 等)上使用。
使用 Hyper 的对称示例
此示例展示了如何使用从 jsonwebtoken
中重新导出的 DecodingKey
和 Validation
来解析和验证使用简单对称密钥设置的 JWT。它还展示了如何自定义默认验证器以及如何提取声明中的注册和公共字段。
use chrono::{DateTime, Utc};
use http::{header::AUTHORIZATION, Request, Response, StatusCode};
use http_body_util::Empty;
use serde::Deserialize;
use std::convert::Infallible;
use tower::{Service, ServiceBuilder, ServiceExt};
use tower_jwt::{DecodingKey, JwtLayer, RequestClaim, Validation};
// Setup your claim with the fields you want to extract
#[derive(Clone, Deserialize, Debug)]
struct Claim {
/// Subject (whom the token refers to)
pub sub: String,
/// Name of the claim owner
pub name: String,
#[serde(with = "chrono::serde::ts_seconds")]
/// Issued at (timestamp)
pub iat: DateTime<Utc>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn handle(req: Request<Empty<()>>) -> Result<Response<Empty<()>>, Infallible> {
let claim = req.extensions().get::<RequestClaim<Claim>>();
if let Some(claim) = claim {
// Use the claim here...
assert_eq!(claim.claim.sub, "1234567890");
assert_eq!(claim.claim.name, "John Doe");
assert_eq!(
claim.claim.iat,
DateTime::parse_from_rfc3339("2018-01-18T01:30:00Z").unwrap()
);
Ok(Response::new(Empty::new()))
} else {
// Claim was not set so this request is unauthorized
Ok(Response::builder()
.status(StatusCode::UNAUTHORIZED)
.body(Empty::new())
.unwrap())
}
}
let mut validation = Validation::default();
validation.validate_exp = false;
validation.required_spec_claims.clear();
// Make a new JWT layer which will validate the tokens on requests
let jwt_layer = JwtLayer::<Claim>::new(
validation,
DecodingKey::from_secret("symmetric secret".as_bytes()),
);
let mut service = ServiceBuilder::new().layer(jwt_layer).service_fn(handle);
// Call the service without a claim
let request = Request::builder().uri("/").body(Empty::new())?;
let status = service.ready().await?.call(request).await?.status();
assert_eq!(
status,
StatusCode::UNAUTHORIZED,
"request did not have a token while endpoint expected one"
);
// Call the service with a claim
let request = Request::builder()
.uri("/")
.header(AUTHORIZATION, "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDAwfQ.CHiQ0VbodaR55aiN_0JJB7nWJBO__rt_7ur1WO-jZxg")
.body(Empty::new())?;
let status = service.ready().await?.call(request).await?.status();
assert_eq!(
status,
StatusCode::OK,
"request should extract the token correctly"
);
Ok(())
}
使用 Axum 的非对称示例
use axum::{routing::get, Extension, Router};
use http::{Request, StatusCode};
use http_body_util::Empty;
use ring::{
rand,
signature::{self, Ed25519KeyPair, KeyPair},
};
use serde::Deserialize;
use tower::ServiceExt;
use tower_jwt::{DecodingKey, JwtLayer, RequestClaim, Validation};
// Setup your claim with the fields you want to extract
#[derive(Deserialize, Clone)]
pub struct Claim {
/// Subject (whom token refers to).
pub sub: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Make a asymmetric key pair
// This will mostly be done outside of the code using like `openssl` to generate the key pair
let doc = signature::Ed25519KeyPair::generate_pkcs8(&rand::SystemRandom::new()).unwrap();
// let encoding_key = EncodingKey::from_ed_der(doc.as_ref());
let pair = Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap();
let public_key = pair.public_key().as_ref().to_vec();
let decoding_key = DecodingKey::from_ed_der(&public_key);
let mut validation = Validation::new(jsonwebtoken::Algorithm::EdDSA);
// Only allow tokens from the test-issuer
validation.set_issuer(&["test-issuer"]);
let router = Router::new()
.route(
"/",
get(|claim: Option<Extension<RequestClaim<Claim>>>| async move {
if let Some(Extension(claim)) = claim {
(StatusCode::OK, format!("Hello, {}", claim.claim.sub))
} else {
(StatusCode::UNAUTHORIZED, "Not authorized".to_string())
}
}),
)
.layer(JwtLayer::<Claim, _>::new(validation, move || {
let decoding_key = decoding_key.clone();
async {
// In practice a network call will happen here to get the public key
decoding_key
}
}));
// Call the service without a claim
let response = router
.clone()
.oneshot(Request::builder().uri("/").body(Empty::new()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
Ok(())
}
依赖项
~3–13MB
~170K SLoC