first commit
This commit is contained in:
232
src/services/auth_service.rs
Normal file
232
src/services/auth_service.rs
Normal file
@@ -0,0 +1,232 @@
|
||||
use anyhow::Result;
|
||||
use argon2::{
|
||||
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
||||
Argon2,
|
||||
};
|
||||
use rand::Rng;
|
||||
|
||||
use crate::utils::jwt::TokenService;
|
||||
use crate::domain::dto::auth::{RegisterRequest, LoginRequest, DeleteUserRequest};
|
||||
use crate::domain::entities::users;
|
||||
use crate::config::auth::AuthConfig;
|
||||
use crate::infra::redis::{redis_client::RedisClient, redis_key::{BusinessType, RedisKey}};
|
||||
use crate::repositories::user_repository::UserRepository;
|
||||
|
||||
pub struct AuthService {
|
||||
user_repo: UserRepository,
|
||||
redis_client: RedisClient,
|
||||
auth_config: AuthConfig,
|
||||
}
|
||||
|
||||
impl AuthService {
|
||||
pub fn new(user_repo: UserRepository, redis_client: RedisClient, auth_config: AuthConfig) -> Self {
|
||||
Self { user_repo, redis_client, auth_config }
|
||||
}
|
||||
|
||||
/// 哈希密码
|
||||
pub fn hash_password(&self, password: &str) -> Result<String> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let argon2 = Argon2::default();
|
||||
let password_hash = argon2
|
||||
.hash_password(password.as_bytes(), &salt)
|
||||
.map_err(|e| anyhow::anyhow!("密码哈希失败: {}", e))?
|
||||
.to_string();
|
||||
Ok(password_hash)
|
||||
}
|
||||
|
||||
/// 生成用户 ID
|
||||
pub fn generate_user_id(&self) -> String {
|
||||
let mut rng = rand::thread_rng();
|
||||
rng.gen_range(1_000_000_000i64..10_000_000_000i64)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// 生成唯一的用户 ID
|
||||
pub async fn generate_unique_user_id(&self) -> Result<String> {
|
||||
let mut attempts = 0;
|
||||
const MAX_ATTEMPTS: u32 = 10;
|
||||
|
||||
loop {
|
||||
let candidate_id = self.generate_user_id();
|
||||
|
||||
let existing = self.user_repo.count_by_id(&candidate_id).await?;
|
||||
if existing == 0 {
|
||||
return Ok(candidate_id);
|
||||
}
|
||||
|
||||
attempts += 1;
|
||||
if attempts >= MAX_ATTEMPTS {
|
||||
return Err(anyhow::anyhow!("生成唯一用户 ID 失败"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存 refresh_token 到 Redis
|
||||
async fn save_refresh_token(&self, user_id: &str, refresh_token: &str, expiration_days: i64) -> Result<()> {
|
||||
let key = RedisKey::new(BusinessType::Auth)
|
||||
.add_identifier("refresh_token")
|
||||
.add_identifier(user_id);
|
||||
|
||||
let expiration_seconds = expiration_days * 24 * 3600;
|
||||
|
||||
self.redis_client
|
||||
.set_ex(&key.build(), refresh_token, expiration_seconds as u64)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Redis 保存失败: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取并删除 refresh_token
|
||||
async fn get_and_delete_refresh_token(&self, user_id: &str) -> Result<String> {
|
||||
let key = RedisKey::new(BusinessType::Auth)
|
||||
.add_identifier("refresh_token")
|
||||
.add_identifier(user_id);
|
||||
|
||||
let token: Option<String> = self.redis_client
|
||||
.get(&key.build())
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Redis 查询失败: {}", e))?;
|
||||
|
||||
if token.is_some() {
|
||||
self.redis_client
|
||||
.delete_key(&key)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Redis 删除失败: {}", e))?;
|
||||
}
|
||||
|
||||
token.ok_or_else(|| anyhow::anyhow!("刷新令牌无效或已过期"))
|
||||
}
|
||||
|
||||
/// 删除用户的 refresh_token
|
||||
pub async fn delete_refresh_token(&self, user_id: &str) -> Result<()> {
|
||||
let key = RedisKey::new(BusinessType::Auth)
|
||||
.add_identifier("refresh_token")
|
||||
.add_identifier(user_id);
|
||||
|
||||
self.redis_client
|
||||
.delete_key(&key)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Redis 删除失败: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 注册用户
|
||||
pub async fn register(
|
||||
&self,
|
||||
request: RegisterRequest,
|
||||
) -> Result<(users::Model, String, String)> {
|
||||
// 1. 检查邮箱是否已存在
|
||||
let existing = self.user_repo.count_by_email(&request.email).await?;
|
||||
|
||||
if existing > 0 {
|
||||
return Err(anyhow::anyhow!("邮箱已注册"));
|
||||
}
|
||||
|
||||
// 2. 哈希密码
|
||||
let password_hash = self.hash_password(&request.password)?;
|
||||
|
||||
// 3. 生成用户 ID
|
||||
let user_id = self.generate_unique_user_id().await?;
|
||||
|
||||
// 4. 插入数据库并获取包含真实 created_at 的用户对象
|
||||
let user = self.user_repo.insert(user_id.clone(), request.email, password_hash).await?;
|
||||
|
||||
// 5. 生成 token
|
||||
let (access_token, refresh_token) = TokenService::generate_token_pair(
|
||||
&user_id,
|
||||
self.auth_config.access_token_expiration_minutes,
|
||||
self.auth_config.refresh_token_expiration_days,
|
||||
&self.auth_config.jwt_secret,
|
||||
)?;
|
||||
|
||||
// 6. 保存 refresh_token
|
||||
self.save_refresh_token(&user_id, &refresh_token, self.auth_config.refresh_token_expiration_days as i64).await?;
|
||||
|
||||
Ok((user, access_token, refresh_token))
|
||||
}
|
||||
|
||||
/// 登录
|
||||
pub async fn login(
|
||||
&self,
|
||||
request: LoginRequest,
|
||||
) -> Result<(users::Model, String, String)> {
|
||||
// 1. 查询用户
|
||||
let user = self.user_repo.find_by_email(&request.email).await?
|
||||
.ok_or_else(|| anyhow::anyhow!("邮箱或密码错误"))?;
|
||||
|
||||
// 2. 验证密码
|
||||
let password_hash = self.user_repo.get_password_hash(&request.email).await?
|
||||
.ok_or_else(|| anyhow::anyhow!("邮箱或密码错误"))?;
|
||||
|
||||
let parsed_hash = PasswordHash::new(&password_hash)
|
||||
.map_err(|e| anyhow::anyhow!("解析密码哈希失败: {}", e))?;
|
||||
let argon2 = Argon2::default();
|
||||
|
||||
argon2
|
||||
.verify_password(request.password.as_bytes(), &parsed_hash)
|
||||
.map_err(|_| anyhow::anyhow!("邮箱或密码错误"))?;
|
||||
|
||||
// 3. 生成 token
|
||||
let (access_token, refresh_token) = TokenService::generate_token_pair(
|
||||
&user.id,
|
||||
self.auth_config.access_token_expiration_minutes,
|
||||
self.auth_config.refresh_token_expiration_days,
|
||||
&self.auth_config.jwt_secret,
|
||||
)?;
|
||||
|
||||
// 4. 保存 refresh_token
|
||||
self.save_refresh_token(&user.id, &refresh_token, self.auth_config.refresh_token_expiration_days as i64).await?;
|
||||
|
||||
Ok((user, access_token, refresh_token))
|
||||
}
|
||||
|
||||
/// 使用 refresh_token 刷新 access_token
|
||||
pub async fn refresh_access_token(
|
||||
&self,
|
||||
refresh_token: &str,
|
||||
) -> Result<(String, String)> {
|
||||
// 1. 从 refresh_token 中解码出 user_id
|
||||
let user_id = TokenService::decode_user_id(refresh_token, &self.auth_config.jwt_secret)?;
|
||||
|
||||
// 2. 从 Redis 获取存储的 token 并删除
|
||||
let stored_token = self.get_and_delete_refresh_token(&user_id).await?;
|
||||
|
||||
// 3. 验证 token 是否匹配
|
||||
if stored_token != refresh_token {
|
||||
return Err(anyhow::anyhow!("刷新令牌无效"));
|
||||
}
|
||||
|
||||
// 4. 生成新的 token 对
|
||||
let (new_access_token, new_refresh_token) = TokenService::generate_token_pair(
|
||||
&user_id,
|
||||
self.auth_config.access_token_expiration_minutes,
|
||||
self.auth_config.refresh_token_expiration_days,
|
||||
&self.auth_config.jwt_secret,
|
||||
)?;
|
||||
|
||||
// 5. 保存新的 refresh_token
|
||||
self.save_refresh_token(&user_id, &new_refresh_token, self.auth_config.refresh_token_expiration_days as i64).await?;
|
||||
|
||||
Ok((new_access_token, new_refresh_token))
|
||||
}
|
||||
|
||||
/// 删除用户
|
||||
pub async fn delete_user(&self, request: DeleteUserRequest) -> Result<()> {
|
||||
let password_hash = self.user_repo.get_password_hash(&request.user_id).await?
|
||||
.ok_or_else(|| anyhow::anyhow!("用户不存在"))?;
|
||||
|
||||
let parsed_hash = PasswordHash::new(&password_hash)
|
||||
.map_err(|e| anyhow::anyhow!("解析密码哈希失败: {}", e))?;
|
||||
let argon2 = Argon2::default();
|
||||
|
||||
argon2
|
||||
.verify_password(request.password.as_bytes(), &parsed_hash)
|
||||
.map_err(|_| anyhow::anyhow!("密码错误"))?;
|
||||
|
||||
self.user_repo.delete_by_id(&request.user_id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user