2025-10-16 09:56:36 +08:00
|
|
|
|
using FutureMailAPI.Helpers;
|
|
|
|
|
|
using FutureMailAPI.DTOs;
|
2025-10-16 15:21:52 +08:00
|
|
|
|
using FutureMailAPI.Models;
|
|
|
|
|
|
using FutureMailAPI.Data;
|
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
|
|
using System.IdentityModel.Tokens.Jwt;
|
2025-10-16 09:56:36 +08:00
|
|
|
|
using System.Security.Claims;
|
2025-10-16 15:21:52 +08:00
|
|
|
|
using System.Security.Cryptography;
|
|
|
|
|
|
using System.Text;
|
2025-10-16 09:56:36 +08:00
|
|
|
|
|
|
|
|
|
|
namespace FutureMailAPI.Services
|
|
|
|
|
|
{
|
|
|
|
|
|
public interface IAuthService
|
|
|
|
|
|
{
|
|
|
|
|
|
Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto);
|
|
|
|
|
|
Task<ApiResponse<AuthResponseDto>> RegisterAsync(UserRegisterDto registerDto);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class AuthService : IAuthService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly IUserService _userService;
|
|
|
|
|
|
private readonly IPasswordHelper _passwordHelper;
|
2025-10-16 15:21:52 +08:00
|
|
|
|
private readonly FutureMailDbContext _context;
|
|
|
|
|
|
private readonly IConfiguration _configuration;
|
2025-10-16 09:56:36 +08:00
|
|
|
|
|
|
|
|
|
|
public AuthService(
|
|
|
|
|
|
IUserService userService,
|
|
|
|
|
|
IPasswordHelper passwordHelper,
|
2025-10-16 15:21:52 +08:00
|
|
|
|
FutureMailDbContext context,
|
|
|
|
|
|
IConfiguration configuration)
|
2025-10-16 09:56:36 +08:00
|
|
|
|
{
|
|
|
|
|
|
_userService = userService;
|
|
|
|
|
|
_passwordHelper = passwordHelper;
|
2025-10-16 15:21:52 +08:00
|
|
|
|
_context = context;
|
|
|
|
|
|
_configuration = configuration;
|
2025-10-16 09:56:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto)
|
|
|
|
|
|
{
|
2025-10-16 15:21:52 +08:00
|
|
|
|
// 获取用户信息
|
|
|
|
|
|
var userResult = await _userService.GetUserByUsernameOrEmailAsync(loginDto.UsernameOrEmail);
|
2025-10-16 09:56:36 +08:00
|
|
|
|
|
2025-10-16 15:21:52 +08:00
|
|
|
|
if (!userResult.Success || userResult.Data == null)
|
2025-10-16 09:56:36 +08:00
|
|
|
|
{
|
2025-10-16 15:21:52 +08:00
|
|
|
|
return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
|
|
|
|
|
|
}
|
2025-10-16 09:56:36 +08:00
|
|
|
|
|
2025-10-16 15:21:52 +08:00
|
|
|
|
var userDto = userResult.Data;
|
2025-10-16 09:56:36 +08:00
|
|
|
|
|
2025-10-16 15:21:52 +08:00
|
|
|
|
// 获取原始用户信息用于密码验证
|
|
|
|
|
|
var user = await _context.Users
|
|
|
|
|
|
.FirstOrDefaultAsync(u => u.Id == userDto.Id);
|
|
|
|
|
|
|
|
|
|
|
|
if (user == null)
|
2025-10-16 09:56:36 +08:00
|
|
|
|
{
|
2025-10-16 15:21:52 +08:00
|
|
|
|
return ApiResponse<AuthResponseDto>.ErrorResult("用户不存在");
|
2025-10-16 09:56:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 15:21:52 +08:00
|
|
|
|
// 验证密码
|
|
|
|
|
|
if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash, user.Salt))
|
2025-10-16 09:56:36 +08:00
|
|
|
|
{
|
2025-10-16 15:21:52 +08:00
|
|
|
|
return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
|
2025-10-16 09:56:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 15:21:52 +08:00
|
|
|
|
// 更新用户响应DTO
|
|
|
|
|
|
userDto.LastLoginAt = DateTime.UtcNow;
|
2025-10-16 09:56:36 +08:00
|
|
|
|
|
2025-10-16 15:21:52 +08:00
|
|
|
|
// 生成JWT令牌
|
|
|
|
|
|
var token = GenerateJwtToken(user);
|
|
|
|
|
|
var refreshToken = GenerateRefreshToken();
|
2025-10-16 09:56:36 +08:00
|
|
|
|
|
2025-10-16 15:21:52 +08:00
|
|
|
|
// 保存刷新令牌到用户表
|
|
|
|
|
|
user.RefreshToken = refreshToken;
|
|
|
|
|
|
user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(7); // 刷新令牌7天过期
|
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
|
|
|
|
|
|
|
// 创建认证响应DTO
|
2025-10-16 09:56:36 +08:00
|
|
|
|
var authResponse = new AuthResponseDto
|
|
|
|
|
|
{
|
2025-10-16 15:21:52 +08:00
|
|
|
|
User = userDto,
|
|
|
|
|
|
Token = token,
|
|
|
|
|
|
RefreshToken = refreshToken,
|
|
|
|
|
|
ExpiresIn = 3600 // 1小时,单位秒
|
2025-10-16 09:56:36 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "登录成功");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ApiResponse<AuthResponseDto>> RegisterAsync(UserRegisterDto registerDto)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查用户名是否已存在
|
|
|
|
|
|
var existingUserResult = await _userService.GetUserByUsernameAsync(registerDto.Username);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingUserResult.Success && existingUserResult.Data != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ApiResponse<AuthResponseDto>.ErrorResult("用户名已存在");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查邮箱是否已存在
|
|
|
|
|
|
var existingEmailResult = await _userService.GetUserByEmailAsync(registerDto.Email);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingEmailResult.Success && existingEmailResult.Data != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ApiResponse<AuthResponseDto>.ErrorResult("邮箱已被注册");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建用户
|
|
|
|
|
|
var createUserResult = await _userService.CreateUserAsync(registerDto);
|
|
|
|
|
|
|
|
|
|
|
|
if (!createUserResult.Success)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ApiResponse<AuthResponseDto>.ErrorResult(createUserResult.Message ?? "注册失败");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 15:21:52 +08:00
|
|
|
|
// 注册成功后,自动登录
|
2025-10-16 09:56:36 +08:00
|
|
|
|
var loginDto = new UserLoginDto
|
|
|
|
|
|
{
|
|
|
|
|
|
UsernameOrEmail = registerDto.Username,
|
|
|
|
|
|
Password = registerDto.Password
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return await LoginAsync(loginDto);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 15:21:52 +08:00
|
|
|
|
private string GenerateJwtToken(User user)
|
2025-10-16 09:56:36 +08:00
|
|
|
|
{
|
2025-10-16 15:21:52 +08:00
|
|
|
|
var jwtSettings = _configuration.GetSection("Jwt");
|
|
|
|
|
|
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
|
|
|
|
|
|
var tokenDescriptor = new SecurityTokenDescriptor
|
2025-10-16 09:56:36 +08:00
|
|
|
|
{
|
2025-10-16 15:21:52 +08:00
|
|
|
|
Subject = new ClaimsIdentity(new[]
|
|
|
|
|
|
{
|
|
|
|
|
|
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
|
|
|
|
|
new Claim(ClaimTypes.Name, user.Username),
|
|
|
|
|
|
new Claim(ClaimTypes.Email, user.Email)
|
|
|
|
|
|
}),
|
|
|
|
|
|
Expires = DateTime.UtcNow.AddHours(1),
|
|
|
|
|
|
Issuer = jwtSettings["Issuer"],
|
|
|
|
|
|
Audience = jwtSettings["Audience"],
|
|
|
|
|
|
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
2025-10-16 09:56:36 +08:00
|
|
|
|
};
|
2025-10-16 15:21:52 +08:00
|
|
|
|
|
|
|
|
|
|
var tokenHandler = new JwtSecurityTokenHandler();
|
|
|
|
|
|
var token = tokenHandler.CreateToken(tokenDescriptor);
|
|
|
|
|
|
return tokenHandler.WriteToken(token);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string GenerateRefreshToken()
|
|
|
|
|
|
{
|
|
|
|
|
|
var randomNumber = new byte[32];
|
|
|
|
|
|
using var rng = RandomNumberGenerator.Create();
|
|
|
|
|
|
rng.GetBytes(randomNumber);
|
|
|
|
|
|
return Convert.ToBase64String(randomNumber);
|
2025-10-16 09:56:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|