feat(auth): 添加 Refresh Token 双 Token 机制
- 添加 /api/refresh-token 接口支持 Token 续期 - 登录接口返回 access_token 和 refresh_token - 新增 refresh_tokens 表存储 refresh_token - 部署脚本添加数据库备份和迁移功能 - deploy.sh 添加 4 项 API 测试 - 更新 AGENTS.md 文档
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
use actix_web::{web, HttpResponse, Responder, post};
|
||||
use chrono::{Utc, Duration};
|
||||
use reqwest::Client;
|
||||
use sqlx::postgres::PgPool;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::auth::generate_token;
|
||||
use crate::auth::{generate_token, generate_refresh_token, verify_refresh_token};
|
||||
use crate::db;
|
||||
use crate::error::ErrorResponse;
|
||||
use crate::models::{
|
||||
AppState, ErrorResponse, TokenResponse, WeChatApiResponse, WeChatLoginRequest,
|
||||
AppState, LoginResponse, RefreshTokenRequest, TokenRefreshResponse,
|
||||
WeChatApiResponse, WeChatLoginRequest,
|
||||
};
|
||||
|
||||
#[post("/api/login")]
|
||||
@@ -28,11 +32,7 @@ pub async fn login(
|
||||
}
|
||||
Err(e) => {
|
||||
error!("微信API请求失败: {}", e);
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||||
error: "请求微信服务失败".to_string(),
|
||||
errcode: None,
|
||||
errmsg: None,
|
||||
});
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse::<()>::error("请求微信服务失败"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -43,11 +43,7 @@ pub async fn login(
|
||||
}
|
||||
Err(e) => {
|
||||
error!("微信API响应解析失败: {}", e);
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||||
error: "解析微信响应失败".to_string(),
|
||||
errcode: None,
|
||||
errmsg: None,
|
||||
});
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse::<()>::error("解析微信响应失败"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -56,11 +52,11 @@ pub async fn login(
|
||||
"微信登录验证失败 - 错误码: {}, 错误信息: {:?}",
|
||||
errcode, wechat_data.errmsg
|
||||
);
|
||||
return HttpResponse::BadRequest().json(ErrorResponse {
|
||||
error: "微信登录失败".to_string(),
|
||||
errcode: Some(errcode),
|
||||
errmsg: wechat_data.errmsg,
|
||||
});
|
||||
let errmsg = wechat_data.errmsg.unwrap_or_default();
|
||||
return HttpResponse::BadRequest().json(ErrorResponse::<()>::error(format!(
|
||||
"微信登录失败: {} {}",
|
||||
errcode, errmsg
|
||||
)));
|
||||
}
|
||||
|
||||
let openid = match wechat_data.openid {
|
||||
@@ -71,11 +67,7 @@ pub async fn login(
|
||||
}
|
||||
None => {
|
||||
error!("微信API响应中未包含openid");
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||||
error: "微信API未返回openid".to_string(),
|
||||
errcode: None,
|
||||
errmsg: None,
|
||||
});
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse::<()>::error("微信API未返回openid"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -97,11 +89,7 @@ pub async fn login(
|
||||
}
|
||||
Err(e) => {
|
||||
error!("用户信息处理失败: {}", e);
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||||
error: "用户信息处理失败".to_string(),
|
||||
errcode: Some(500),
|
||||
errmsg: None,
|
||||
});
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse::<()>::error("用户信息处理失败"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -112,14 +100,93 @@ pub async fn login(
|
||||
}
|
||||
Err(e) => {
|
||||
error!("JWT生成失败: {}", e);
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||||
error: "生成身份令牌失败".to_string(),
|
||||
errcode: Some(500),
|
||||
errmsg: None,
|
||||
});
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse::<()>::error("生成身份令牌失败"));
|
||||
}
|
||||
};
|
||||
|
||||
let token_response = TokenResponse::new(token);
|
||||
HttpResponse::Ok().json(token_response)
|
||||
let refresh_token_str = match generate_refresh_token(user_id, &app_state.jwt_secret) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
error!("Refresh token 生成失败: {}", e);
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse::<()>::error("生成刷新令牌失败"));
|
||||
}
|
||||
};
|
||||
|
||||
let refresh_expires_at = Utc::now() + Duration::days(7);
|
||||
if let Err(e) = db::create_refresh_token(pool.get_ref(), user_id, &refresh_token_str, refresh_expires_at).await {
|
||||
error!("保存 refresh_token 失败: {}", e);
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse::<()>::error("保存刷新令牌失败"));
|
||||
}
|
||||
|
||||
let login_response = LoginResponse::new(token, refresh_token_str);
|
||||
HttpResponse::Ok().json(login_response)
|
||||
}
|
||||
|
||||
#[post("/api/refresh-token")]
|
||||
pub async fn refresh_token(
|
||||
pool: web::Data<PgPool>,
|
||||
req: web::Json<RefreshTokenRequest>,
|
||||
app_state: web::Data<AppState>,
|
||||
) -> impl Responder {
|
||||
let refresh_token = &req.refresh_token;
|
||||
|
||||
let refresh_token_record = match db::verify_refresh_token(pool.get_ref(), refresh_token).await {
|
||||
Ok(record) => record,
|
||||
Err(e) => {
|
||||
warn!("Refresh token 验证失败: {}", e);
|
||||
return HttpResponse::Unauthorized().json(ErrorResponse::<()>::error("Refresh token 无效或已过期"));
|
||||
}
|
||||
};
|
||||
|
||||
let user_id = refresh_token_record.user_id;
|
||||
|
||||
let user_query = r#"SELECT openid FROM users WHERE id = $1"#;
|
||||
let openid: String = match sqlx::query_as::<_, (String,)>(user_query)
|
||||
.bind(user_id)
|
||||
.fetch_optional(pool.get_ref())
|
||||
.await
|
||||
{
|
||||
Ok(Some((openid,))) => openid,
|
||||
Ok(None) => {
|
||||
error!("用户不存在: {}", user_id);
|
||||
return HttpResponse::Unauthorized().json(ErrorResponse::<()>::error("用户不存在"));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("查询用户失败: {}", e);
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse::<()>::error("查询用户信息失败"));
|
||||
}
|
||||
};
|
||||
|
||||
let new_access_token = match generate_token(user_id, &openid, 2, &app_state.jwt_secret) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
error!("生成新 JWT 失败: {}", e);
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse::<()>::error("生成令牌失败"));
|
||||
}
|
||||
};
|
||||
|
||||
let new_refresh_token = match generate_refresh_token(user_id, &app_state.jwt_secret) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
error!("生成新 refresh_token 失败: {}", e);
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse::<()>::error("生成刷新令牌失败"));
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = db::delete_specific_refresh_token(pool.get_ref(), refresh_token).await {
|
||||
warn!("删除旧 refresh_token 失败(继续): {}", e);
|
||||
}
|
||||
|
||||
let new_expires_at = Utc::now() + Duration::days(7);
|
||||
if let Err(e) = db::create_refresh_token(pool.get_ref(), user_id, &new_refresh_token, new_expires_at).await {
|
||||
error!("保存新 refresh_token 失败: {}", e);
|
||||
return HttpResponse::InternalServerError().json(ErrorResponse::<()>::error("保存刷新令牌失败"));
|
||||
}
|
||||
|
||||
let response = TokenRefreshResponse {
|
||||
success: true,
|
||||
token: new_access_token,
|
||||
refresh_token: new_refresh_token,
|
||||
};
|
||||
HttpResponse::Ok().json(response)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user