添加token生成
This commit is contained in:
parent
f090382d15
commit
2594ef6dd1
48
Cargo.lock
generated
48
Cargo.lock
generated
@ -1015,6 +1015,21 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonwebtoken"
|
||||||
|
version = "9.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"js-sys",
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simple_asn1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@ -1047,6 +1062,7 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
|
"jsonwebtoken",
|
||||||
"moka",
|
"moka",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
@ -1211,6 +1227,16 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint-dig"
|
name = "num-bigint-dig"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
@ -1374,6 +1400,16 @@ version = "1.0.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.0",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem-rfc7468"
|
name = "pem-rfc7468"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@ -1937,6 +1973,18 @@ dependencies = [
|
|||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_asn1"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
|
@ -39,3 +39,4 @@ futures-util = "0.3"
|
|||||||
reqwest = "0.12"
|
reqwest = "0.12"
|
||||||
futures-executor = "0.3"
|
futures-executor = "0.3"
|
||||||
error-stack = "0.4"
|
error-stack = "0.4"
|
||||||
|
jsonwebtoken = "9.3.0"
|
5
app.toml
5
app.toml
@ -11,3 +11,8 @@ level = "DEBUG"
|
|||||||
[database]
|
[database]
|
||||||
url = "postgres://lyj:1325479Lyj!@47.95.198.7:13207/demo_rs"
|
url = "postgres://lyj:1325479Lyj!@47.95.198.7:13207/demo_rs"
|
||||||
options = { min_conns = 10, max_conns = 20, conn_timeout = 30, idle_timeout = 300, max_lifetime = 60, sql_logging = true }
|
options = { min_conns = 10, max_conns = 20, conn_timeout = 30, idle_timeout = 300, max_lifetime = 60, sql_logging = true }
|
||||||
|
|
||||||
|
[jwt]
|
||||||
|
secret = "chuanyue"
|
||||||
|
expires = 1800
|
||||||
|
refresh_expires = 3600
|
@ -24,3 +24,4 @@ http-body-util = { workspace = true }
|
|||||||
moka = { workspace = true, features = ["future"] }
|
moka = { workspace = true, features = ["future"] }
|
||||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros" ] }
|
tokio = { workspace = true, features = ["rt-multi-thread", "macros" ] }
|
||||||
futures-util = { workspace = true }
|
futures-util = { workspace = true }
|
||||||
|
jsonwebtoken = { workspace = true }
|
@ -8,6 +8,7 @@ pub struct Config {
|
|||||||
pub server: Server,
|
pub server: Server,
|
||||||
pub logger: Logger,
|
pub logger: Logger,
|
||||||
pub database: Database,
|
pub database: Database,
|
||||||
|
pub jwt: Jwt
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
@ -39,6 +40,13 @@ pub struct Logger {
|
|||||||
pub level: String,
|
pub level: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Jwt {
|
||||||
|
pub secret: String,
|
||||||
|
pub expires: usize,
|
||||||
|
pub refresh_expires: usize,
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! config {
|
macro_rules! config {
|
||||||
() => {
|
() => {
|
||||||
|
@ -3,3 +3,4 @@ extern crate self as library;
|
|||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod resp;
|
pub mod resp;
|
||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
|
pub mod token;
|
98
library/src/token-backup
Normal file
98
library/src/token-backup
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// claims
|
||||||
|
use chrono::{Duration, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct Claims {
|
||||||
|
sub: i32, // 用户ID
|
||||||
|
exp: usize, // Access Token过期时间戳
|
||||||
|
r_exp: usize, // Refresh Token过期时间戳
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录API
|
||||||
|
async fn login(username: String, password: String) -> Result<(String, String), (StatusCode, String)> {
|
||||||
|
// 模拟数据库查询
|
||||||
|
let user = get_user_from_database(&username, &password)?;
|
||||||
|
// 如果用户存在且密码正确
|
||||||
|
let access_token_expiration = Utc::now() + Duration::minutes(15);
|
||||||
|
let refresh_token_expiration = Utc::now() + Duration::days(30);
|
||||||
|
|
||||||
|
let access_claims = Claims {
|
||||||
|
sub: user.id,
|
||||||
|
exp: access_token_expiration.timestamp() as usize,
|
||||||
|
r_exp: refresh_token_expiration.timestamp() as usize,
|
||||||
|
};
|
||||||
|
let refresh_claims = Claims {
|
||||||
|
sub: user.id,
|
||||||
|
exp: refresh_token_expiration.timestamp() as usize,
|
||||||
|
r_exp: refresh_token_expiration.timestamp() as usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
let access_token = jsonwebtoken::encode(&jsonwebtoken::Header::default(), &access_claims, JWT_SECRET)?;
|
||||||
|
let refresh_token = jsonwebtoken::encode(&jsonwebtoken::Header::default(), &refresh_claims, JWT_SECRET)?;
|
||||||
|
|
||||||
|
Ok((access_token, refresh_token))
|
||||||
|
}
|
||||||
|
|
||||||
|
//中间件实现Token校验
|
||||||
|
async fn authenticate_access_token<B>(req: Request<B>, next: Next<B>) -> Result<Response, (StatusCode, String)> {
|
||||||
|
let auth_header = req.headers().get(header::AUTHORIZATION);
|
||||||
|
let token = match auth_header {
|
||||||
|
Some(header_value) => {
|
||||||
|
let parts: Vec<&str> = header_value.to_str().unwrap_or("").split_whitespace().collect();
|
||||||
|
if parts.len() != 2 || parts[0] != "Bearer" {
|
||||||
|
return Err((StatusCode::BAD_REQUEST, "Invalid authorization header format".to_string()));
|
||||||
|
}
|
||||||
|
parts[1]
|
||||||
|
},
|
||||||
|
None => return Err((StatusCode::UNAUTHORIZED, "Missing authorization header".to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let validation = Validation::default();
|
||||||
|
match decode::<Claims>(token, &DecodingKey::from_secret(JWT_SECRET), &validation) {
|
||||||
|
Ok(decoded) => {
|
||||||
|
// 将Claims附加到请求扩展中,以便后续处理使用
|
||||||
|
req.extensions_mut().insert(decoded.claims);
|
||||||
|
Ok(next.run(req).await)
|
||||||
|
},
|
||||||
|
Err(_) => Err((StatusCode::UNAUTHORIZED, "Invalid token".to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//刷新Token的API
|
||||||
|
async fn refresh_token_handler(Json(payload): Json<Claims>) -> Result<String, (StatusCode, String)> {
|
||||||
|
let user_id = payload.sub;
|
||||||
|
// 在数据库中验证Refresh Token,确保它没有被使用过
|
||||||
|
// 这里简化为仅检查用户ID
|
||||||
|
if let Some(refresh_claims) = get_refresh_claims_from_database(user_id) {
|
||||||
|
// 生成新的Access Token
|
||||||
|
let access_token_expiration = Utc::now() + Duration::minutes(15);
|
||||||
|
let access_claims = Claims {
|
||||||
|
sub: user_id,
|
||||||
|
exp: access_token_expiration.timestamp() as usize,
|
||||||
|
r_exp: refresh_claims.r_exp,
|
||||||
|
};
|
||||||
|
let new_access_token = jsonwebtoken::encode(&jsonwebtoken::Header::default(), &access_claims, JWT_SECRET)?;
|
||||||
|
Ok(new_access_token)
|
||||||
|
} else {
|
||||||
|
Err((StatusCode::UNAUTHORIZED, "Invalid or expired refresh token".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 组合路由和中间件
|
||||||
|
use axum::{routing::post, Router};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/login", post(login))
|
||||||
|
.route("/refresh-token", post(refresh_token_handler))
|
||||||
|
// 示例路由,假设所有路由都需要认证,除了登录和刷新Token
|
||||||
|
.route("/protected-resource", post(|_| async { /* 处理逻辑 */ }))
|
||||||
|
.layer(tower::ServiceBuilder::new().layer_fn(authenticate_access_token));
|
||||||
|
|
||||||
|
println!("Server running on http://localhost:3000");
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||||
|
}
|
||||||
|
|
46
library/src/token.rs
Normal file
46
library/src/token.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::config;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Claim {
|
||||||
|
sub: i32, // 用户ID
|
||||||
|
exp: usize, // Token过期时间戳
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_token(sub: i32) -> String {
|
||||||
|
let claim = Claim {
|
||||||
|
sub,
|
||||||
|
exp: config!().jwt.expires,
|
||||||
|
};
|
||||||
|
generate(claim)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_refresh_token(sub: i32) -> String {
|
||||||
|
let claim = Claim {
|
||||||
|
sub,
|
||||||
|
exp: config!().jwt.refresh_expires,
|
||||||
|
};
|
||||||
|
generate(claim)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate(claim: Claim) -> String {
|
||||||
|
let token = jsonwebtoken::encode(
|
||||||
|
&jsonwebtoken::Header::default(),
|
||||||
|
&claim,
|
||||||
|
&jsonwebtoken::EncodingKey::from_secret(b"secret"),
|
||||||
|
);
|
||||||
|
token.unwrap_or_else(|e| {
|
||||||
|
tracing::error!(error =?e, "生成Token失败");
|
||||||
|
"".to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_token(token: &str) -> Result<Claim, jsonwebtoken::errors::Error> {
|
||||||
|
jsonwebtoken::decode::<Claim>(
|
||||||
|
token,
|
||||||
|
&jsonwebtoken::DecodingKey::from_secret(b"secret"),
|
||||||
|
&jsonwebtoken::Validation::default(),
|
||||||
|
)
|
||||||
|
.map(|data| data.claims)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user