#tower-middleware #authorization #tokens #parse #claim #bearer #request

tower-jwt

Tower 中间件,用于解析授权载体上的 JWT

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 次下载

Apache-2.0

25KB
387

Tower 中间件,用于从请求的授权载体解析 JWT 令牌,并将反序列化后的声明存储在请求扩展中。

这是基于 jsonwebtoken 包构建的,并支持该包支持的所有算法。

由于这是一个 Tower 中间件,因此可以在任何框架(如 Axum、Tonic 等)上使用。

使用 Hyper 的对称示例

此示例展示了如何使用从 jsonwebtoken 中重新导出的 DecodingKeyValidation 来解析和验证使用简单对称密钥设置的 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