improvement: 로그아웃 연동 완료 (#253) #254

Merged
seonkyu.kim merged 1 commits from improvement/#253-logout-integration into develop 2026-02-26 01:25:00 +00:00
4 changed files with 25 additions and 6 deletions
Showing only changes of commit 71102e38ac - Show all commits

View File

@ -82,8 +82,9 @@ public class AuthController : ControllerBase
[Authorize] [Authorize]
[SwaggerOperation( [SwaggerOperation(
Summary = "로그아웃", Summary = "로그아웃",
Description = "현재 로그인된 관리자의 토큰을 무효화합니다. Refresh Token은 DB에서 삭제되고, Access Token은 Redis 블랙리스트에 등록되어 즉시 사용 불가합니다.")] Description = "현재 로그인된 관리자의 토큰을 무효화합니다. Refresh Token은 DB에서 삭제되고, Access Token은 Redis 블랙리스트에 등록되어 즉시 사용 불가합니다. " +
[SwaggerResponse(200, "로그아웃 성공")] "설정/마이페이지/프로필 등 모든 화면에서 이 단일 API를 사용합니다. 응답의 redirect_to로 이동합니다.")]
[SwaggerResponse(200, "로그아웃 성공", typeof(ApiResponse<LogoutResponseDto>))]
[SwaggerResponse(401, "인증되지 않은 요청")] [SwaggerResponse(401, "인증되지 않은 요청")]
public async Task<IActionResult> LogoutAsync() public async Task<IActionResult> LogoutAsync()
{ {
@ -94,8 +95,8 @@ public class AuthController : ControllerBase
var accessToken = HttpContext.Request.Headers["Authorization"] var accessToken = HttpContext.Request.Headers["Authorization"]
.ToString().Replace("Bearer ", ""); .ToString().Replace("Bearer ", "");
await _authService.LogoutAsync(adminId, accessToken); var result = await _authService.LogoutAsync(adminId, accessToken);
return Ok(ApiResponse.Success()); return Ok(ApiResponse<LogoutResponseDto>.Success(result));
} }
[HttpPost("email/verify")] [HttpPost("email/verify")]

View File

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace SPMS.Application.DTOs.Auth;
public class LogoutResponseDto
{
[JsonPropertyName("logged_out")]
public bool LoggedOut { get; set; }
[JsonPropertyName("redirect_to")]
public string RedirectTo { get; set; } = "/login";
}

View File

@ -8,7 +8,7 @@ public interface IAuthService
Task<SignupResponseDto> SignupAsync(SignupRequestDto request); Task<SignupResponseDto> SignupAsync(SignupRequestDto request);
Task<LoginResponseDto> LoginAsync(LoginRequestDto request); Task<LoginResponseDto> LoginAsync(LoginRequestDto request);
Task<TokenRefreshResponseDto> RefreshTokenAsync(TokenRefreshRequestDto request); Task<TokenRefreshResponseDto> RefreshTokenAsync(TokenRefreshRequestDto request);
Task LogoutAsync(long adminId, string accessToken); Task<LogoutResponseDto> LogoutAsync(long adminId, string accessToken);
Task<ChangePasswordResponseDto> ChangePasswordAsync(long adminId, ChangePasswordRequestDto request); Task<ChangePasswordResponseDto> ChangePasswordAsync(long adminId, ChangePasswordRequestDto request);
Task<EmailCheckResponseDto> CheckEmailAsync(EmailCheckRequestDto request); Task<EmailCheckResponseDto> CheckEmailAsync(EmailCheckRequestDto request);
Task<EmailVerifyResponseDto> VerifyEmailAsync(EmailVerifyRequestDto request); Task<EmailVerifyResponseDto> VerifyEmailAsync(EmailVerifyRequestDto request);

View File

@ -287,7 +287,7 @@ public class AuthService : IAuthService
}; };
} }
public async Task LogoutAsync(long adminId, string accessToken) public async Task<LogoutResponseDto> LogoutAsync(long adminId, string accessToken)
{ {
// 1. 관리자 조회 // 1. 관리자 조회
var admin = await _adminRepository.GetByIdAsync(adminId); var admin = await _adminRepository.GetByIdAsync(adminId);
@ -313,6 +313,12 @@ public class AuthService : IAuthService
if (remaining > TimeSpan.Zero) if (remaining > TimeSpan.Zero)
await _tokenStore.StoreAsync($"blacklist:{jti}", "revoked", remaining); await _tokenStore.StoreAsync($"blacklist:{jti}", "revoked", remaining);
} }
return new LogoutResponseDto
{
LoggedOut = true,
RedirectTo = "/login"
};
} }
public async Task<ChangePasswordResponseDto> ChangePasswordAsync(long adminId, ChangePasswordRequestDto request) public async Task<ChangePasswordResponseDto> ChangePasswordAsync(long adminId, ChangePasswordRequestDto request)