使用了jwt作为身份验证和分发
This commit is contained in:
50
Cargo.lock
generated
50
Cargo.lock
generated
@@ -786,8 +786,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1242,6 +1244,21 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "9.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"js-sys",
|
||||
"pem",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "language-tags"
|
||||
version = "0.3.2"
|
||||
@@ -1395,6 +1412,16 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.4"
|
||||
@@ -1536,6 +1563,16 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
@@ -1823,6 +1860,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"dotenvy",
|
||||
"error",
|
||||
"jsonwebtoken",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2023,6 +2061,18 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple_asn1"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
|
||||
@@ -8,6 +8,7 @@ actix-web = "4.11.0"
|
||||
chrono = {version = "0.4.41", features=["serde"]}
|
||||
dotenvy = "0.15.7"
|
||||
error = "0.1.9"
|
||||
jsonwebtoken = "9.3.1"
|
||||
reqwest = { version = "0.12.23", features=["json"]}
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.143"
|
||||
|
||||
81
src/auth.rs
Normal file
81
src/auth.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use chrono::Utc;
|
||||
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
|
||||
|
||||
use crate::models::Claims;
|
||||
use actix_web::{
|
||||
Error, HttpMessage, body::MessageBody, dev::ServiceRequest, dev::ServiceResponse,
|
||||
middleware::Next,
|
||||
}; // 新增:用于包装中间件函数
|
||||
|
||||
// 中间件函数:泛型 B 约束为 MessageBody,返回 Result<ServiceResponse<B>, Error>
|
||||
pub async fn jwt_middleware<B: MessageBody>(
|
||||
req: ServiceRequest,
|
||||
next: Next<B>,
|
||||
) -> Result<ServiceResponse<B>, Error> {
|
||||
// (保持原有逻辑不变)
|
||||
let auth_header = req
|
||||
.headers()
|
||||
.get("Authorization")
|
||||
.ok_or_else(|| actix_web::error::ErrorUnauthorized("缺少Authorization头"))?
|
||||
.to_str()
|
||||
.map_err(|_| actix_web::error::ErrorUnauthorized("Authorization格式无效"))?;
|
||||
|
||||
let token = auth_header.strip_prefix("Bearer ").ok_or_else(|| {
|
||||
actix_web::error::ErrorUnauthorized("Authorization格式应为 Bearer <token>")
|
||||
})?;
|
||||
|
||||
let jwt_secret = std::env::var("JWT_SECRET")
|
||||
.map_err(|_| actix_web::error::ErrorInternalServerError("服务器未配置JWT密钥"))?;
|
||||
|
||||
let claims = verify_token(token, &jwt_secret)
|
||||
.map_err(|e| actix_web::error::ErrorUnauthorized(format!("无效的token: {}", e)))?;
|
||||
|
||||
req.extensions_mut().insert(claims);
|
||||
next.call(req).await
|
||||
}
|
||||
|
||||
// 生成JWT的函数
|
||||
pub fn generate_token(
|
||||
user_id: i32,
|
||||
openid: &str,
|
||||
user_type: i32,
|
||||
secret: &str,
|
||||
) -> Result<String, String> {
|
||||
// 设置过期时间:当前时间 + 24小时(86400秒)
|
||||
let now = Utc::now();
|
||||
let exp = (now + chrono::Duration::hours(24)).timestamp();
|
||||
let iat = now.timestamp();
|
||||
|
||||
// 构建Claims
|
||||
let claims = Claims {
|
||||
exp,
|
||||
iat,
|
||||
user_id,
|
||||
openid: openid.to_string(),
|
||||
user_type,
|
||||
};
|
||||
|
||||
// 生成token
|
||||
encode(
|
||||
&Header::new(Algorithm::HS256), // 使用HS256算法
|
||||
&claims,
|
||||
&EncodingKey::from_secret(secret.as_bytes()), // 签名密钥
|
||||
)
|
||||
.map_err(|e| format!("生成JWT失败: {}", e))
|
||||
}
|
||||
|
||||
// 验证并解析JWT的函数
|
||||
pub fn verify_token(token: &str, secret: &str) -> Result<Claims, String> {
|
||||
// 验证配置(指定算法,默认会检查exp等字段)
|
||||
let validation = Validation::new(Algorithm::HS256);
|
||||
|
||||
// 解析token
|
||||
let decoded = decode::<Claims>(
|
||||
token,
|
||||
&DecodingKey::from_secret(secret.as_bytes()), // 与签发时相同的密钥
|
||||
&validation,
|
||||
)
|
||||
.map_err(|e| format!("JWT验证失败: {}", e))?;
|
||||
|
||||
Ok(decoded.claims)
|
||||
}
|
||||
141
src/main.rs
141
src/main.rs
@@ -1,59 +1,19 @@
|
||||
use actix_web::middleware::from_fn;
|
||||
use actix_web::{App, HttpResponse, HttpServer, Responder, Result, post, web};
|
||||
use db::create_pool;
|
||||
use reqwest::Client;
|
||||
use sqlx::postgres::PgPool;
|
||||
|
||||
mod auth;
|
||||
mod db;
|
||||
mod models;
|
||||
|
||||
use db::insert_weather_data;
|
||||
use models::{
|
||||
ErrorResponse, OpenIdRequest, OpenIdResponse, UserIdResponse, WeChatApiResponse,
|
||||
WeChatLoginRequest, WeatherData,
|
||||
};
|
||||
use auth::{generate_token, jwt_middleware};
|
||||
use db::{create_pool, insert_weather_data};
|
||||
use models::{ErrorResponse, WeChatApiResponse, WeChatLoginRequest, WeatherData};
|
||||
|
||||
// 新增:根据openid获取用户ID的端点
|
||||
#[post("/getmyid")]
|
||||
async fn get_user_id(pool: web::Data<PgPool>, request: web::Json<OpenIdRequest>) -> impl Responder {
|
||||
let openid = &request.openid;
|
||||
|
||||
// 查询用户ID
|
||||
let query = r#"
|
||||
SELECT id FROM users WHERE openid = $1
|
||||
"#;
|
||||
|
||||
let result: Result<Option<(i32,)>, sqlx::Error> = sqlx::query_as::<_, (i32,)>(query)
|
||||
.bind(openid)
|
||||
.fetch_optional(pool.get_ref())
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(Some(user_id)) => {
|
||||
println!("Found user ID {} for openid {}", user_id.0, openid);
|
||||
HttpResponse::Ok().json(UserIdResponse { user_id: user_id.0 })
|
||||
}
|
||||
Ok(None) => {
|
||||
println!("No user found with openid: {}", openid);
|
||||
HttpResponse::NotFound().json(ErrorResponse {
|
||||
error: format!("未找到openid为 {} 的用户", openid),
|
||||
errcode: Some(404), // 添加错误码
|
||||
errmsg: Some("用户不存在".to_string()), // 添加错误消息
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Database error: {}", e);
|
||||
HttpResponse::InternalServerError().json(ErrorResponse {
|
||||
error: format!("数据库查询错误: {}", e),
|
||||
errcode: Some(500), // 添加错误码
|
||||
errmsg: Some(e.to_string()), // 添加错误消息
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取openid的API端点
|
||||
#[post("/getopenid")]
|
||||
async fn get_openid(
|
||||
// 登录的API端点
|
||||
#[post("/api/login")]
|
||||
async fn login(
|
||||
pool: web::Data<PgPool>,
|
||||
req: web::Json<WeChatLoginRequest>,
|
||||
http_client: web::Data<Client>,
|
||||
@@ -121,6 +81,7 @@ async fn get_openid(
|
||||
errmsg: wechat_data.errmsg,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取openid
|
||||
let openid = match wechat_data.openid {
|
||||
Some(id) => id,
|
||||
@@ -133,36 +94,75 @@ async fn get_openid(
|
||||
}
|
||||
};
|
||||
|
||||
// 检查用户是否已存在,不存在则创建
|
||||
// 3. 检查用户是否已存在,不存在则创建(显式处理数据库错误)
|
||||
let query = r#"
|
||||
INSERT INTO users (openid)
|
||||
VALUES ($1)
|
||||
ON CONFLICT (openid) DO NOTHING
|
||||
RETURNING id
|
||||
"#;
|
||||
INSERT INTO users (openid, name, type)
|
||||
VALUES ($1, left($1, 8), 2)
|
||||
ON CONFLICT (openid) DO NOTHING
|
||||
RETURNING id
|
||||
"#;
|
||||
|
||||
let result = sqlx::query_as::<_, (i32,)>(query)
|
||||
let user_id = match sqlx::query_as::<_, (i32,)>(query)
|
||||
.bind(&openid)
|
||||
.fetch_optional(pool.get_ref())
|
||||
.await;
|
||||
match result {
|
||||
Ok(Some(user_id)) => {
|
||||
println!("用户已存在,ID: {}", user_id.0);
|
||||
.await
|
||||
{
|
||||
Ok(Some((id,))) => {
|
||||
// 已存在用户,直接获取ID
|
||||
println!("用户已存在,ID: {}", id);
|
||||
id
|
||||
}
|
||||
Ok(None) => {
|
||||
// 新用户创建成功,需再次查询ID(因ON CONFLICT DO NOTHING不返回值)
|
||||
println!("新用户已创建,openid: {}", openid);
|
||||
match sqlx::query_as::<_, (i32,)>("SELECT id FROM users WHERE openid = $1")
|
||||
.bind(&openid)
|
||||
.fetch_one(pool.get_ref())
|
||||
.await
|
||||
{
|
||||
Ok((id,)) => id,
|
||||
Err(e) => {
|
||||
eprintln!("查询新用户ID失败: {}", e);
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||||
error: "创建用户后查询ID失败".to_string(),
|
||||
errcode: Some(500),
|
||||
errmsg: Some(e.to_string()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("数据库操作失败: {}", e);
|
||||
// 即使数据库操作失败,我们仍然返回openid,因为微信登录已经成功
|
||||
// 数据库操作失败
|
||||
eprintln!("用户查询/创建失败: {}", e);
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||||
error: "用户信息处理失败".to_string(),
|
||||
errcode: Some(500),
|
||||
errmsg: Some(e.to_string()),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 返回openid给前端
|
||||
HttpResponse::Ok().json(OpenIdResponse { openid })
|
||||
// 4. 生成JWT token(显式处理所有可能的错误)
|
||||
// 4.1 获取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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 4.2 编码生成token
|
||||
let token = generate_token(user_id, &openid, 2, &jwt_secret);
|
||||
|
||||
// 5. 返回成功响应(包含token)
|
||||
HttpResponse::Ok().json(token)
|
||||
}
|
||||
|
||||
#[post("/weather-test")]
|
||||
#[post("/api/post-weather-data")]
|
||||
async fn post_weather_data(
|
||||
data: web::Json<WeatherData>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -215,9 +215,14 @@ fn create_server_config(
|
||||
App::new()
|
||||
.app_data(web::Data::new(pool))
|
||||
.app_data(web::Data::new(http_client))
|
||||
.service(get_user_id)
|
||||
.service(get_openid)
|
||||
.service(post_weather_data)
|
||||
// 公开接口(无需验证)
|
||||
.service(login)
|
||||
// 需要验证的接口(使用 from_fn 包装中间件)
|
||||
.service(
|
||||
web::scope("")
|
||||
.wrap(from_fn(jwt_middleware)) // 关键修改:用 from_fn 包装
|
||||
.service(post_weather_data),
|
||||
)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
|
||||
@@ -2,15 +2,19 @@ use chrono::NaiveDate;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
// 新增:API请求/响应结构体
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct OpenIdRequest {
|
||||
pub openid: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct UserIdResponse {
|
||||
// 定义JWT载荷结构体
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
// 标准字段:过期时间(必须)
|
||||
pub exp: i64, // 时间戳(秒)
|
||||
// 标准字段:签发时间
|
||||
pub iat: i64,
|
||||
// 自定义字段:用户ID(根据业务需求添加)
|
||||
pub user_id: i32,
|
||||
// 自定义字段:openid(可选)
|
||||
pub openid: String,
|
||||
// 自定义字段:user_ytpe
|
||||
pub user_type: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
||||
Reference in New Issue
Block a user