From f77657a0cef51359e5e03cb696556696d07807c6 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 20 Nov 2024 11:53:01 +0800 Subject: [PATCH] feat: update jwt encode and decode --- frameworks/Rust/axum/Cargo.lock | 164 +++++++++++++++++++++++- frameworks/Rust/axum/Cargo.toml | 2 + frameworks/Rust/axum/src/error.rs | 25 ++-- frameworks/Rust/axum/src/routes/user.rs | 17 ++- frameworks/Rust/axum/src/utils/jwt.rs | 74 ++++++++--- 5 files changed, 249 insertions(+), 33 deletions(-) diff --git a/frameworks/Rust/axum/Cargo.lock b/frameworks/Rust/axum/Cargo.lock index 645049a..08477f4 100644 --- a/frameworks/Rust/axum/Cargo.lock +++ b/frameworks/Rust/axum/Cargo.lock @@ -41,6 +41,21 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.93" @@ -94,9 +109,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core", @@ -147,6 +162,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-extra" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04" +dependencies = [ + "axum", + "axum-core", + "bytes", + "fastrand", + "futures-util", + "headers", + "http", + "http-body", + "http-body-util", + "mime", + "multer", + "pin-project-lite", + "serde", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -260,6 +299,26 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.14" @@ -360,6 +419,21 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + [[package]] name = "flate2" version = "1.0.34" @@ -454,6 +528,30 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + [[package]] name = "hermit-abi" version = "0.3.9" @@ -547,6 +645,29 @@ dependencies = [ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -833,6 +954,23 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -955,6 +1093,8 @@ dependencies = [ "anyhow", "argon2", "axum", + "axum-extra", + "chrono", "dotenvy", "jsonwebtoken", "rand", @@ -1231,6 +1371,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1785,6 +1936,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/frameworks/Rust/axum/Cargo.toml b/frameworks/Rust/axum/Cargo.toml index 105cd63..80d3dee 100644 --- a/frameworks/Rust/axum/Cargo.toml +++ b/frameworks/Rust/axum/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] # server axum = "0.7.7" +axum-extra = { version = "0.9.6", features = ["typed-header"] } tokio = { version = "1.41.1", features = ["full"] } tower = "0.5.1" tower-http = { version = "0.6.1", features = ["full"] } @@ -24,6 +25,7 @@ serde_json = { version = "1.0.132" } serde_repr = "0.1.19" jsonwebtoken = "9.3.0" validator = { version = "0.19.0", features = ["derive"] } +chrono = "0.4.38" [profile.release] lto = true diff --git a/frameworks/Rust/axum/src/error.rs b/frameworks/Rust/axum/src/error.rs index 3c3a9fe..93cbaff 100644 --- a/frameworks/Rust/axum/src/error.rs +++ b/frameworks/Rust/axum/src/error.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::{borrow::Cow, fmt::Display}; use axum::{ extract::rejection::{FormRejection, JsonRejection}, @@ -22,12 +22,13 @@ pub enum AppError { AxumFormRejection(#[from] FormRejection), #[error(transparent)] AxumJsonRejection(#[from] JsonRejection), + // jwt + #[error(transparent)] + Jwt(#[from] jsonwebtoken::errors::Error), // route // 路由通常错误 错误信息直接返回用户 - // #[error("{0}")] - // AuthorizeFailed(Cow<'static, str>), - // #[error("{0}")] - // UserConflict(Cow<'static, str>), + #[error("{0}")] + InvalidToken(Cow<'static, str>), } #[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)] @@ -35,7 +36,7 @@ pub enum AppError { pub enum ErrorCode { Normal = 200, InternalError = 1000, - //NotAuthorized = 1001, + // NotAuthorized = 1001, AuthorizeFailed = 1002, UserConflict = 1003, ParameterIncorrect = 1004, @@ -77,6 +78,7 @@ impl IntoResponse for AppError { let (status_code, code, err_message) = match self { AppError::Any(err) => log_internal_error(err), + AppError::Jwt(err) => log_internal_error(err), AppError::AxumFormRejection(_) | AppError::AxumJsonRejection(_) => ( StatusCode::BAD_REQUEST, ParameterIncorrect, @@ -85,11 +87,12 @@ impl IntoResponse for AppError { AppError::ValidationError(_) => { let message = format!("Input validation error: [{self}]").replace('\n', ", "); (StatusCode::BAD_REQUEST, ParameterIncorrect, message) - } // route - // AppError::AuthorizeFailed(err) => { - // (StatusCode::UNAUTHORIZED, AuthorizeFailed, err.to_string()) - // } - // AppError::UserConflict(err) => (StatusCode::CONFLICT, UserConflict, err.to_string()), + } + AppError::InvalidToken(_) => ( + StatusCode::BAD_REQUEST, + AuthorizeFailed, + "Invalid token".to_string(), + ), }; let body = Json(json!({ "code": code, diff --git a/frameworks/Rust/axum/src/routes/user.rs b/frameworks/Rust/axum/src/routes/user.rs index 79a1c49..fffc4cc 100644 --- a/frameworks/Rust/axum/src/routes/user.rs +++ b/frameworks/Rust/axum/src/routes/user.rs @@ -1,5 +1,9 @@ -use crate::utils::password::hash; +use crate::utils::{ + jwt::{self, Claims}, + password::hash, +}; use axum::{routing::post, Json, Router}; +use chrono::Utc; use serde::{Deserialize, Serialize}; use validator::Validate; @@ -37,11 +41,20 @@ pub async fn registry(Json(user_param): Json) -> RouteResult Self { + Self { + encoding: EncodingKey::from_secret(secret), + decoding: DecodingKey::from_secret(secret), + } + } +} + +pub static KEYS: LazyLock = LazyLock::new(|| { + let secret = Alphanumeric.sample_string(&mut rand::thread_rng(), 32); + Keys::new(secret.as_bytes()) +}); + #[derive(Serialize, Deserialize, Debug)] pub struct Claims { // aud: String, // Optional. Audience @@ -13,23 +42,33 @@ pub struct Claims { pub sub: String, // Optional. Subject (whom token refers to) } -pub fn encode_jwt(claims: &Claims, key: &[u8]) -> Result { - encode( - &Header::new(Algorithm::HS256), - &claims, - &EncodingKey::from_secret(key), - ) +#[async_trait] +impl FromRequestParts for Claims +where + S: Send + Sync, +{ + type Rejection = AppError; + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { + // Extract the token from the authorization header + let TypedHeader(Authorization(bearer)) = parts + .extract::>>() + .await + .map_err(|_| AppError::InvalidToken("Extract the token failed".into()))?; + // Decode the user data + let token_data = decode_jwt(bearer.token()) + .map_err(|_| AppError::InvalidToken("Deocde the token failed".into()))?; + + Ok(token_data.claims) + } } -pub fn decode_jwt( - token: &str, - key: &[u8], -) -> Result, jsonwebtoken::errors::Error> { - decode::( - token, - &DecodingKey::from_secret(key), - &Validation::default(), - ) +pub fn encode_jwt(claims: &Claims) -> Result { + encode(&Header::new(Algorithm::HS256), &claims, &KEYS.encoding) +} + +pub fn decode_jwt(token: &str) -> Result, jsonwebtoken::errors::Error> { + decode::(token, &KEYS.decoding, &Validation::default()) } #[cfg(test)] @@ -40,7 +79,6 @@ mod tests { #[test] fn jwt_works() { - let key = "xfy"; let now = SystemTime::now(); let seconds_since_epoch: usize = now .duration_since(UNIX_EPOCH) @@ -54,9 +92,9 @@ mod tests { iat: seconds_since_epoch, sub: sub.clone(), }; - let jwt = encode_jwt(&claims, key.as_ref()).unwrap(); + let jwt = encode_jwt(&claims).unwrap(); - let token_data = decode_jwt(&jwt, key.as_ref()).unwrap(); + let token_data = decode_jwt(&jwt).unwrap(); println!("{token_data:?}"); assert_eq!(token_data.header.alg, Algorithm::HS256);