feat: API Key 재발급 API 구현 (#46)
All checks were successful
SPMS_API/pipeline/head This commit looks good

Reviewed-on: https://git.ipstein.myds.me/SPMS/SPMS_API/pulls/47
This commit is contained in:
김선규 2026-02-09 15:16:21 +00:00
commit 3b4b1873a3
4 changed files with 58 additions and 0 deletions

View File

@ -63,4 +63,18 @@ public class ServiceController : ControllerBase
var result = await _serviceManagementService.ChangeStatusAsync(serviceCode, request); var result = await _serviceManagementService.ChangeStatusAsync(serviceCode, request);
return Ok(ApiResponse<ServiceResponseDto>.Success(result)); return Ok(ApiResponse<ServiceResponseDto>.Success(result));
} }
[HttpPost("{serviceCode}/apikey/refresh")]
[SwaggerOperation(
Summary = "API Key 재발급",
Description = "서비스의 API Key를 재발급합니다. 기존 키는 즉시 무효화되며, 새 키는 1회만 표시됩니다.")]
[SwaggerResponse(200, "재발급 성공", typeof(ApiResponse<ApiKeyRefreshResponseDto>))]
[SwaggerResponse(401, "인증되지 않은 요청")]
[SwaggerResponse(403, "권한 없음")]
[SwaggerResponse(404, "서비스를 찾을 수 없음")]
public async Task<IActionResult> RefreshApiKeyAsync([FromRoute] string serviceCode)
{
var result = await _serviceManagementService.RefreshApiKeyAsync(serviceCode);
return Ok(ApiResponse<ApiKeyRefreshResponseDto>.Success(result));
}
} }

View File

@ -0,0 +1,8 @@
namespace SPMS.Application.DTOs.Service;
public class ApiKeyRefreshResponseDto
{
public string ServiceCode { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty;
public DateTime ApiKeyCreatedAt { get; set; }
}

View File

@ -7,4 +7,5 @@ public interface IServiceManagementService
Task<ServiceListResponseDto> GetListAsync(ServiceListRequestDto request); Task<ServiceListResponseDto> GetListAsync(ServiceListRequestDto request);
Task<ServiceResponseDto> GetByServiceCodeAsync(string serviceCode); Task<ServiceResponseDto> GetByServiceCodeAsync(string serviceCode);
Task<ServiceResponseDto> ChangeStatusAsync(string serviceCode, ChangeServiceStatusRequestDto request); Task<ServiceResponseDto> ChangeStatusAsync(string serviceCode, ChangeServiceStatusRequestDto request);
Task<ApiKeyRefreshResponseDto> RefreshApiKeyAsync(string serviceCode);
} }

View File

@ -1,4 +1,5 @@
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Security.Cryptography;
using SPMS.Application.DTOs.Service; using SPMS.Application.DTOs.Service;
using SPMS.Application.Interfaces; using SPMS.Application.Interfaces;
using SPMS.Domain.Common; using SPMS.Domain.Common;
@ -110,6 +111,40 @@ public class ServiceManagementService : IServiceManagementService
return MapToDto(service); return MapToDto(service);
} }
public async Task<ApiKeyRefreshResponseDto> RefreshApiKeyAsync(string serviceCode)
{
var service = await _serviceRepository.GetByServiceCodeAsync(serviceCode);
if (service is null)
{
throw new SpmsException(
ErrorCodes.NotFound,
"서비스를 찾을 수 없습니다.",
404);
}
// 32~64자 랜덤 API Key 생성 (48자 = 36바이트 Base64)
var randomBytes = new byte[36];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomBytes);
}
var newApiKey = Convert.ToBase64String(randomBytes);
service.ApiKey = newApiKey;
service.ApiKeyCreatedAt = DateTime.UtcNow;
service.UpdatedAt = DateTime.UtcNow;
_serviceRepository.Update(service);
await _unitOfWork.SaveChangesAsync();
return new ApiKeyRefreshResponseDto
{
ServiceCode = service.ServiceCode,
ApiKey = newApiKey,
ApiKeyCreatedAt = service.ApiKeyCreatedAt
};
}
private static ServiceSummaryDto MapToSummaryDto(Service service) private static ServiceSummaryDto MapToSummaryDto(Service service)
{ {
return new ServiceSummaryDto return new ServiceSummaryDto