using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using BCrypt.Net; using Microsoft.IdentityModel.Tokens; using TaxBaik.Domain.Entities; using TaxBaik.Domain.Interfaces; namespace TaxBaik.Web.Services; public class AuthService { private readonly IAdminUserRepository _adminUserRepository; private readonly ILogger _logger; private readonly string _jwtSecretKey; private readonly int _tokenExpirationMinutes = 480; // 8시간 public AuthService(IAdminUserRepository adminUserRepository, ILogger logger, IConfiguration configuration) { _adminUserRepository = adminUserRepository; _logger = logger; _jwtSecretKey = configuration["Jwt:SecretKey"] ?? throw new InvalidOperationException("Missing 'Jwt:SecretKey' configuration."); } public async Task AuthenticateAndGenerateTokenAsync(string username, string password) { if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password)) { return null; } var user = await _adminUserRepository.GetByUsernameAsync(username); if (user == null) { _logger.LogWarning("로그인 시도: 존재하지 않는 사용자 '{Username}'", username); return null; } if (string.IsNullOrWhiteSpace(user.PasswordHash)) { _logger.LogError("로그인 실패: 사용자 '{Username}'의 PasswordHash가 비어 있습니다.", username); return null; } if (!BCrypt.Net.BCrypt.Verify(password, user.PasswordHash)) { _logger.LogWarning("로그인 시도: 잘못된 비밀번호 '{Username}'", username); return null; } _logger.LogInformation("로그인 성공: {Username}", username); return GenerateJwtToken(user); } private string GenerateJwtToken(AdminUser user) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSecretKey)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Username), new Claim("username", user.Username) }; var token = new JwtSecurityToken( issuer: "taxbaik-admin", audience: "taxbaik-admin-client", claims: claims, expires: DateTime.UtcNow.AddMinutes(_tokenExpirationMinutes), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } public ClaimsPrincipal? ValidateToken(string token) { try { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSecretKey)); var handler = new JwtSecurityTokenHandler(); var principal = handler.ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = key, ValidateIssuer = true, ValidIssuer = "taxbaik-admin", ValidateAudience = true, ValidAudience = "taxbaik-admin-client", ValidateLifetime = true, ClockSkew = TimeSpan.Zero }, out SecurityToken validatedToken); return principal; } catch { return null; } } }