可以正常使用get_weather_details获取前端指定的数据了
This commit is contained in:
41
src/auth.rs
41
src/auth.rs
@@ -1,7 +1,7 @@
|
||||
use chrono::Utc;
|
||||
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
|
||||
|
||||
use crate::models::Claims;
|
||||
use crate::models::{Claims, TempTokenClaims};
|
||||
use actix_web::{
|
||||
Error, HttpMessage, body::MessageBody, dev::ServiceRequest, dev::ServiceResponse,
|
||||
middleware::Next,
|
||||
@@ -79,3 +79,42 @@ pub fn verify_token(token: &str, secret: &str) -> Result<Claims, String> {
|
||||
|
||||
Ok(decoded.claims)
|
||||
}
|
||||
|
||||
// 生成临时token(包含openid和resource_id,短期有效)
|
||||
pub fn generate_temp_token(
|
||||
openid: &str,
|
||||
resource_id: i32,
|
||||
secret: &str,
|
||||
expire_minutes: i64, // 有效期(分钟)
|
||||
) -> Result<String, String> {
|
||||
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<TempTokenClaims, String> {
|
||||
let validation = Validation::new(Algorithm::HS256);
|
||||
let decoded = decode::<TempTokenClaims>(
|
||||
token,
|
||||
&DecodingKey::from_secret(secret.as_bytes()),
|
||||
&validation,
|
||||
)
|
||||
.map_err(|e| format!("临时token验证失败: {}", e))?;
|
||||
|
||||
Ok(decoded.claims)
|
||||
}
|
||||
|
||||
141
src/main.rs
141
src/main.rs
@@ -1,5 +1,6 @@
|
||||
use actix_web::middleware::from_fn;
|
||||
use actix_web::{App, HttpResponse, HttpServer, Responder, delete, get, post, web};
|
||||
use include_dir::{Dir, include_dir};
|
||||
use reqwest::Client;
|
||||
use sqlx::postgres::PgPool;
|
||||
mod auth;
|
||||
@@ -12,6 +13,10 @@ use models::{ErrorResponse, WeChatApiResponse, WeChatLoginRequest, WeatherData};
|
||||
|
||||
use crate::models::Claims;
|
||||
|
||||
// 编译时嵌入整个 static 目录
|
||||
static STATIC_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/static");
|
||||
static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
|
||||
|
||||
// 登录的API端点
|
||||
#[post("/api/login")]
|
||||
async fn login(
|
||||
@@ -200,29 +205,138 @@ async fn post_weather_data(
|
||||
}
|
||||
}
|
||||
|
||||
// 在main.rs中添加以下处理函数
|
||||
|
||||
// 获取天气数据详情
|
||||
#[get("/weather/details/{id}")]
|
||||
async fn get_weather_details(
|
||||
// main.rs
|
||||
#[post("/api/generate-temp-token/{resource_id}")]
|
||||
async fn generate_temp_token_handler(
|
||||
path: web::Path<i32>,
|
||||
pool: web::Data<PgPool>,
|
||||
claims: web::ReqData<Claims>,
|
||||
claims: web::ReqData<Claims>, // 依赖原有JWT验证,确保是合法用户
|
||||
) -> impl Responder {
|
||||
let weather_id = path.into_inner();
|
||||
println!("获取天气数据详情, ID: {}", weather_id);
|
||||
let resource_id = path.into_inner();
|
||||
let openid: &String = &claims.openid;
|
||||
|
||||
// 校验用户是否有权限访问该资源(防止生成其他用户的资源token)
|
||||
match db::get_weather_details(pool.get_ref(), resource_id).await {
|
||||
Ok(weather_data) => {
|
||||
if weather_data.openid != *openid {
|
||||
return HttpResponse::Forbidden().json(ErrorResponse {
|
||||
error: "无权限为该资源生成临时token".to_string(),
|
||||
errcode: Some(403),
|
||||
errmsg: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return HttpResponse::BadRequest().json(ErrorResponse {
|
||||
error: format!("资源不存在: {}", e),
|
||||
errcode: Some(404),
|
||||
errmsg: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 获取JWT密钥
|
||||
let jwt_secret = match std::env::var("JWT_SECRET") {
|
||||
Ok(secret) => secret,
|
||||
Err(_) => {
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||||
error: "服务器配置错误:缺少JWT_SECRET".to_string(),
|
||||
errcode: Some(500),
|
||||
errmsg: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 生成临时token(有效期10分钟)
|
||||
let temp_token = match auth::generate_temp_token(openid, resource_id, &jwt_secret, 10) {
|
||||
Ok(token) => token,
|
||||
Err(e) => {
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||||
error: format!("生成临时token失败: {}", e),
|
||||
errcode: Some(500),
|
||||
errmsg: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
HttpResponse::Ok().json(serde_json::json!({
|
||||
"temp_token": temp_token,
|
||||
"expire_minutes": 10,
|
||||
}))
|
||||
}
|
||||
|
||||
// 获取天气数据详情
|
||||
// 修改获取天气数据详情接口(移除路径中的id参数)
|
||||
#[get("/weather/details")]
|
||||
async fn get_weather_details(
|
||||
pool: web::Data<PgPool>,
|
||||
claims: Option<web::ReqData<Claims>>, // 原有JWT的claims变为可选
|
||||
query: web::Query<serde_json::Value>, // 用于获取URL参数temp_token和id
|
||||
) -> impl Responder {
|
||||
let jwt_secret = match std::env::var("JWT_SECRET") {
|
||||
Ok(secret) => secret,
|
||||
Err(_) => {
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||||
error: "服务器配置错误:缺少JWT_SECRET".to_string(),
|
||||
errcode: Some(500),
|
||||
errmsg: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 1. 优先检查临时token(URL参数)
|
||||
let (openid, weather_id) =
|
||||
if let Some(temp_token) = query.get("temp_token").and_then(|v| v.as_str()) {
|
||||
// 验证临时token
|
||||
let temp_claims = match auth::verify_temp_token(temp_token, &jwt_secret) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return HttpResponse::Unauthorized().json(ErrorResponse {
|
||||
error: format!("临时token无效: {}", e),
|
||||
errcode: Some(401),
|
||||
errmsg: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 从临时token中获取资源ID
|
||||
(temp_claims.openid, temp_claims.resource_id)
|
||||
}
|
||||
// 2. 若没有临时token,使用原有JWT验证(需要查询参数id)
|
||||
else if let Some(claims) = claims {
|
||||
// 从查询参数获取id
|
||||
let weather_id = match query.get("id").and_then(|v| v.as_i64()) {
|
||||
Some(id) => id as i32,
|
||||
None => {
|
||||
return HttpResponse::BadRequest().json(ErrorResponse {
|
||||
error: "缺少资源ID参数(id)".to_string(),
|
||||
errcode: Some(400),
|
||||
errmsg: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
(claims.openid.clone(), weather_id)
|
||||
}
|
||||
// 3. 两种验证方式都没有,返回未授权
|
||||
else {
|
||||
return HttpResponse::Unauthorized().json(ErrorResponse {
|
||||
error: "缺少token(需提供JWT或临时token)".to_string(),
|
||||
errcode: Some(401),
|
||||
errmsg: None,
|
||||
});
|
||||
};
|
||||
|
||||
// 后续逻辑:校验openid对资源的权限(复用原有逻辑)
|
||||
match db::get_weather_details(pool.get_ref(), weather_id).await {
|
||||
Ok(weather_data) => {
|
||||
// 检查数据是否属于当前用户
|
||||
if weather_data.openid != claims.openid {
|
||||
if weather_data.openid != openid {
|
||||
return HttpResponse::Forbidden().json(ErrorResponse {
|
||||
error: "无权限访问该数据".to_string(),
|
||||
errcode: Some(403),
|
||||
errmsg: None,
|
||||
});
|
||||
}
|
||||
|
||||
HttpResponse::Ok().json(serde_json::json!({
|
||||
"success": true,
|
||||
"data": weather_data
|
||||
@@ -362,14 +476,15 @@ fn create_server_config(
|
||||
.app_data(web::Data::new(http_client))
|
||||
// 公开接口(无需验证)
|
||||
.service(login)
|
||||
.service(get_weather_details) // 新增
|
||||
// 需要验证的接口(使用 from_fn 包装中间件)
|
||||
.service(
|
||||
web::scope("")
|
||||
.wrap(from_fn(jwt_middleware)) // 关键修改:用 from_fn 包装
|
||||
.service(post_weather_data)
|
||||
.service(get_weather_details) // 新增
|
||||
.service(get_weather_brief) // 新增
|
||||
.service(delete_weather), // 新增
|
||||
.service(generate_temp_token_handler)
|
||||
.service(delete_weather),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,15 @@ pub struct Claims {
|
||||
pub user_type: i32,
|
||||
}
|
||||
|
||||
// 临时访问用 token
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct TempTokenClaims {
|
||||
pub exp: i64, // 过期时间(时间戳)
|
||||
pub iat: i64, // 签发时间
|
||||
pub openid: String, // 用户身份标识
|
||||
pub resource_id: i32, // 允许访问的资源ID(如天气数据id)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WeChatLoginRequest {
|
||||
pub code: String,
|
||||
|
||||
Reference in New Issue
Block a user