feat: add redis

This commit is contained in:
2025-08-29 21:42:29 +08:00
parent af68d94efa
commit dc60a0a4bd
10 changed files with 875 additions and 52 deletions

View File

@ -1,5 +1,5 @@
use sea_orm::{EntityTrait, ColumnTrait, QueryFilter, ActiveModelTrait, Set};
use crate::{db::Db, models::{user, refresh_token}, utils::password, error::AppError};
use crate::{db::Db, models::{user, refresh_token}, utils::password, error::AppError, redis::TokenRedis};
use chrono::{Utc, Duration, FixedOffset};
use sha2::{Sha256, Digest};
use sea_orm::ActiveValue::NotSet;
@ -11,13 +11,38 @@ pub async fn login(db: &Db, username: String, password_plain: String) -> Result<
if u.status != 1 { return Err(AppError::Forbidden); }
let ok = password::verify_password(&password_plain, &u.password_hash).map_err(|_| AppError::Unauthorized)?;
if !ok { return Err(AppError::Unauthorized); }
let access_claims = crate::middlewares::jwt::new_access_claims(u.id, &u.username);
let refresh_claims = crate::middlewares::jwt::new_refresh_claims(u.id, &u.username);
let secret = std::env::var("JWT_SECRET").unwrap();
let access = crate::middlewares::jwt::encode_token(&access_claims, &secret)?;
let refresh = crate::middlewares::jwt::encode_token(&refresh_claims, &secret)?;
// persist refresh token hash
// 获取过期时间(秒)
let access_exp_secs = std::env::var("JWT_ACCESS_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1800);
let refresh_exp_secs = std::env::var("JWT_REFRESH_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1209600);
// 先删除用户的所有旧token防止多重登录
if let Err(e) = TokenRedis::revoke_all_tokens(u.id).await {
tracing::warn!("Failed to revoke old tokens for user {}: {}", u.id, e);
}
// 存储新token到Redis
if let Err(e) = TokenRedis::store_access_token(&access, u.id, access_exp_secs as u64).await {
tracing::error!("Failed to store access token to Redis for user {}: {}", u.id, e);
// 不返回错误降级到仅使用JWT模式
} else {
tracing::info!("Successfully stored access token to Redis for user {}", u.id);
}
if let Err(e) = TokenRedis::store_refresh_token(&refresh, u.id, refresh_exp_secs as u64).await {
tracing::error!("Failed to store refresh token to Redis for user {}: {}", u.id, e);
// 不返回错误降级到仅使用JWT模式
} else {
tracing::info!("Successfully stored refresh token to Redis for user {}", u.id);
}
// persist refresh token hash to database (backup)
let mut hasher = Sha256::new(); hasher.update(refresh.as_bytes());
let token_hash = format!("{:x}", hasher.finalize());
let exp_secs = std::env::var("JWT_REFRESH_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1209600);
@ -36,15 +61,26 @@ pub async fn login(db: &Db, username: String, password_plain: String) -> Result<
}
pub async fn logout(db: &Db, uid: i64) -> Result<(), AppError> {
// 从 Redis 中删除所有 token
let _ = TokenRedis::revoke_all_tokens(uid).await;
// 从数据库中删除 refresh token
let _ = refresh_token::Entity::delete_many().filter(refresh_token::Column::UserId.eq(uid)).exec(db).await?;
Ok(())
}
pub async fn rotate_refresh(db: &Db, uid: i64, old_refresh: String) -> Result<(String, String), AppError> {
// 验证Redis中的refresh token
let is_valid_redis = TokenRedis::validate_refresh_token(&old_refresh, uid).await.unwrap_or(false);
// 同时验证数据库中的token hash备用验证
let mut hasher = Sha256::new(); hasher.update(old_refresh.as_bytes());
let token_hash = format!("{:x}", hasher.finalize());
let existing = refresh_token::Entity::find().filter(refresh_token::Column::UserId.eq(uid)).filter(refresh_token::Column::TokenHash.eq(token_hash.clone())).one(db).await?;
if existing.is_none() { return Err(AppError::Unauthorized); }
if !is_valid_redis && existing.is_none() {
return Err(AppError::Unauthorized);
}
let u = user::Entity::find_by_id(uid).one(db).await?.ok_or(AppError::Unauthorized)?;
let access_claims = crate::middlewares::jwt::new_access_claims(u.id, &u.username);
@ -52,7 +88,16 @@ pub async fn rotate_refresh(db: &Db, uid: i64, old_refresh: String) -> Result<(S
let secret = std::env::var("JWT_SECRET").unwrap();
let access = crate::middlewares::jwt::encode_token(&access_claims, &secret)?;
let refresh = crate::middlewares::jwt::encode_token(&refresh_claims, &secret)?;
// 获取过期时间
let access_exp_secs = std::env::var("JWT_ACCESS_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1800);
let refresh_exp_secs = std::env::var("JWT_REFRESH_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1209600);
// 更新Redis中的token
TokenRedis::store_access_token(&access, u.id, access_exp_secs as u64).await?;
TokenRedis::store_refresh_token(&refresh, u.id, refresh_exp_secs as u64).await?;
// 更新数据库中的refresh token
let _ = refresh_token::Entity::delete_many().filter(refresh_token::Column::UserId.eq(uid)).filter(refresh_token::Column::TokenHash.eq(token_hash)).exec(db).await?;
let mut hasher2 = Sha256::new(); hasher2.update(refresh.as_bytes());
let token_hash2 = format!("{:x}", hasher2.finalize());