feat: API Key 재발급 API 구현 (#46)

- ApiKeyRefreshResponseDto 추가
- IServiceManagementService.RefreshApiKeyAsync 인터페이스 추가
- ServiceManagementService.RefreshApiKeyAsync 구현 (32~64자 랜덤 키 생성)
- ServiceController.RefreshApiKeyAsync 엔드포인트 추가
This commit is contained in:
seonkyu.kim 2026-02-10 00:13:16 +09:00
parent 3d793c652b
commit 4cb54e4c41
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