use chrono::{Utc, Duration}; use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode}; use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64}; use crate::error::AppError; use crate::models::{Claims, TempTokenClaims}; use actix_web::{ Error, HttpMessage, body::MessageBody, dev::ServiceRequest, dev::ServiceResponse, middleware::Next, }; // 常量 const ACCESS_TOKEN_EXPIRE_HOURS: i64 = 24; const REFRESH_TOKEN_EXPIRE_DAYS: i64 = 7; pub async fn jwt_middleware( req: ServiceRequest, next: Next, ) -> Result, Error> { let auth_header = req .headers() .get("Authorization") .ok_or_else(|| AppError::Unauthorized("请先登录".to_string()))? .to_str() .map_err(|_| AppError::Unauthorized("Authorization格式无效".to_string()))?; let token = auth_header.strip_prefix("Bearer ").ok_or_else(|| { AppError::Unauthorized("请使用有效的登录凭证".to_string()) })?; let jwt_secret = std::env::var("JWT_SECRET") .map_err(|_| AppError::Internal("服务器配置错误".to_string()))?; let claims = verify_token(token, &jwt_secret) .map_err(|e| AppError::Unauthorized(format!("登录已过期,请重新登录: {}", e)))?; req.extensions_mut().insert(claims); next.call(req).await } // 生成 access_token pub fn generate_token( user_id: i32, openid: &str, user_type: i32, secret: &str, ) -> Result { let now = Utc::now(); let exp = (now + Duration::hours(ACCESS_TOKEN_EXPIRE_HOURS)).timestamp(); let iat = now.timestamp(); let claims = Claims { exp, iat, user_id, openid: openid.to_string(), user_type, }; encode( &Header::new(Algorithm::HS256), &claims, &EncodingKey::from_secret(secret.as_bytes()), ) .map_err(|e| format!("生成JWT失败: {}", e)) } // 生成 refresh_token(简单 base64 编码的随机字符串) pub fn generate_refresh_token(user_id: i32, secret: &str) -> Result { let now = Utc::now(); let exp = (now + Duration::days(REFRESH_TOKEN_EXPIRE_DAYS)).timestamp(); let payload = format!("{}:{}:{}", user_id, exp, secret); let token = BASE64.encode(payload.as_bytes()); Ok(token) } // 解析 refresh_token,返回 (user_id, expires_at) pub fn verify_refresh_token(token: &str, secret: &str) -> Result<(i32, i64), String> { let decoded = BASE64.decode(token) .map_err(|e| format!("Refresh token 格式错误: {}", e))?; let payload = String::from_utf8(decoded) .map_err(|_| "Refresh token 解析失败".to_string())?; let parts: Vec<&str> = payload.split(':').collect(); if parts.len() != 3 { return Err("Refresh token 结构错误".to_string()); } let user_id: i32 = parts[0].parse() .map_err(|_| "Refresh token user_id 解析失败".to_string())?; let exp: i64 = parts[1].parse() .map_err(|_| "Refresh token exp 解析失败".to_string())?; let now = Utc::now().timestamp(); if now > exp { return Err("Refresh token 已过期".to_string()); } Ok((user_id, exp)) } // 验证并解析JWT的函数 pub fn verify_token(token: &str, secret: &str) -> Result { // 验证配置(指定算法,默认会检查exp等字段) let validation = Validation::new(Algorithm::HS256); // 解析token let decoded = decode::( token, &DecodingKey::from_secret(secret.as_bytes()), // 与签发时相同的密钥 &validation, ) .map_err(|e| format!("JWT验证失败: {}", e))?; Ok(decoded.claims) } // 生成临时token(包含openid和resource_id,短期有效) pub fn generate_temp_token( openid: &str, resource_id: i32, secret: &str, expire_minutes: i64, // 有效期(分钟) ) -> Result { let now = Utc::now(); let exp = (now + chrono::Duration::minutes(expire_minutes)).timestamp(); let iat = now.timestamp(); let claims = TempTokenClaims { exp, iat, openid: openid.to_string(), resource_id, }; encode( &Header::new(Algorithm::HS256), &claims, &EncodingKey::from_secret(secret.as_bytes()), ) .map_err(|e| format!("生成临时token失败: {}", e)) } // 验证临时token并返回载荷 pub fn verify_temp_token(token: &str, secret: &str) -> Result { let validation = Validation::new(Algorithm::HS256); let decoded = decode::( token, &DecodingKey::from_secret(secret.as_bytes()), &validation, ) .map_err(|e| format!("临时token验证失败: {}", e))?; Ok(decoded.claims) }