SPMS_API/SPMS.Application/Services/AuthService.cs
seonkyu.kim 9b9ca64b10 feat: 관리자 비밀번호 변경 API 구현 (#40)
- ChangePasswordRequestDto 추가
- IAuthService/AuthService에 ChangePasswordAsync 구현
- AuthController에 POST /v1/in/auth/password/change 엔드포인트 추가
- 현재 비밀번호 검증 및 BCrypt 해싱 적용

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-09 23:22:28 +09:00

177 lines
5.7 KiB
C#

using Microsoft.Extensions.Options;
using SPMS.Application.DTOs.Auth;
using SPMS.Application.Interfaces;
using SPMS.Application.Settings;
using SPMS.Domain.Common;
using SPMS.Domain.Exceptions;
using SPMS.Domain.Interfaces;
namespace SPMS.Application.Services;
public class AuthService : IAuthService
{
private readonly IAdminRepository _adminRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IJwtService _jwtService;
private readonly JwtSettings _jwtSettings;
public AuthService(
IAdminRepository adminRepository,
IUnitOfWork unitOfWork,
IJwtService jwtService,
IOptions<JwtSettings> jwtSettings)
{
_adminRepository = adminRepository;
_unitOfWork = unitOfWork;
_jwtService = jwtService;
_jwtSettings = jwtSettings.Value;
}
public async Task<LoginResponseDto> LoginAsync(LoginRequestDto request)
{
// 1. 이메일로 관리자 조회
var admin = await _adminRepository.GetByEmailAsync(request.Email);
if (admin is null)
{
throw new SpmsException(
ErrorCodes.LoginFailed,
"이메일 또는 비밀번호가 일치하지 않습니다.",
401);
}
// 2. 삭제된 계정 확인
if (admin.IsDeleted)
{
throw new SpmsException(
ErrorCodes.LoginFailed,
"이메일 또는 비밀번호가 일치하지 않습니다.",
401);
}
// 3. 비밀번호 검증 (BCrypt)
if (!BCrypt.Net.BCrypt.Verify(request.Password, admin.Password))
{
throw new SpmsException(
ErrorCodes.LoginFailed,
"이메일 또는 비밀번호가 일치하지 않습니다.",
401);
}
// 4. 토큰 생성
var accessToken = _jwtService.GenerateAccessToken(
admin.Id,
admin.Role.ToString());
var refreshToken = _jwtService.GenerateRefreshToken();
// 5. Refresh Token 및 최종 로그인 시간 저장
admin.RefreshToken = refreshToken;
admin.RefreshTokenExpiresAt = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiryDays);
admin.LastLoginAt = DateTime.UtcNow;
_adminRepository.Update(admin);
await _unitOfWork.SaveChangesAsync();
// 6. 응답 반환
return new LoginResponseDto
{
AccessToken = accessToken,
RefreshToken = refreshToken,
ExpiresIn = _jwtSettings.ExpiryMinutes * 60,
Admin = new AdminInfoDto
{
AdminCode = admin.AdminCode,
Email = admin.Email,
Name = admin.Name,
Role = admin.Role.ToString()
}
};
}
public async Task<TokenRefreshResponseDto> RefreshTokenAsync(TokenRefreshRequestDto request)
{
// 1. Refresh Token으로 관리자 조회
var admin = await _adminRepository.GetByRefreshTokenAsync(request.RefreshToken);
if (admin is null)
{
throw new SpmsException(
ErrorCodes.Unauthorized,
"유효하지 않은 Refresh Token입니다.",
401);
}
// 2. Refresh Token 만료 확인
if (admin.RefreshTokenExpiresAt < DateTime.UtcNow)
{
throw new SpmsException(
ErrorCodes.Unauthorized,
"Refresh Token이 만료되었습니다. 다시 로그인해주세요.",
401);
}
// 3. 새 토큰 생성
var newAccessToken = _jwtService.GenerateAccessToken(
admin.Id,
admin.Role.ToString());
var newRefreshToken = _jwtService.GenerateRefreshToken();
// 4. 새 Refresh Token 저장 (Rotation)
admin.RefreshToken = newRefreshToken;
admin.RefreshTokenExpiresAt = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiryDays);
_adminRepository.Update(admin);
await _unitOfWork.SaveChangesAsync();
// 5. 응답 반환
return new TokenRefreshResponseDto
{
AccessToken = newAccessToken,
RefreshToken = newRefreshToken,
ExpiresIn = _jwtSettings.ExpiryMinutes * 60
};
}
public async Task LogoutAsync(long adminId)
{
// 1. 관리자 조회
var admin = await _adminRepository.GetByIdAsync(adminId);
if (admin is null)
{
throw new SpmsException(
ErrorCodes.NotFound,
"관리자를 찾을 수 없습니다.",
404);
}
// 2. Refresh Token 무효화
admin.RefreshToken = null;
admin.RefreshTokenExpiresAt = null;
_adminRepository.Update(admin);
await _unitOfWork.SaveChangesAsync();
}
public async Task ChangePasswordAsync(long adminId, ChangePasswordRequestDto request)
{
// 1. 관리자 조회
var admin = await _adminRepository.GetByIdAsync(adminId);
if (admin is null)
{
throw new SpmsException(
ErrorCodes.NotFound,
"관리자를 찾을 수 없습니다.",
404);
}
// 2. 현재 비밀번호 검증
if (!BCrypt.Net.BCrypt.Verify(request.CurrentPassword, admin.Password))
{
throw new SpmsException(
ErrorCodes.PasswordValidationFailed,
"현재 비밀번호가 일치하지 않습니다.",
400);
}
// 3. 새 비밀번호 해싱 및 저장
admin.Password = BCrypt.Net.BCrypt.HashPassword(request.NewPassword);
_adminRepository.Update(admin);
await _unitOfWork.SaveChangesAsync();
}
}