Merge pull request 'main' (#49) from seonkyu.kim/AcaMate_API:main into debug
All checks were successful
Back/pipeline/head This commit looks good

Reviewed-on: https://git.ipstein.myds.me/AcaMate/AcaMate_API/pulls/49
This commit is contained in:
김선규 2025-06-17 07:02:56 +00:00
commit c5f105ce8a
15 changed files with 2100 additions and 111 deletions

26
Document/Rule.md Normal file
View File

@ -0,0 +1,26 @@
# AcaMate API 문서
## 개요
## 프로젝트 구조
### 각 폴더 간 관계
#### 역할
- Controller: API 요청을 처리하고 응답을 반환하는 역할
- Service: 비즈니스 로직을 처리하는 역할
- Repository: 데이터베이스와의 상호작용을 처리하는 역할
- Model: 데이터 구조를 정의하는 역할
#### 폴더 관계
- Controller 는 Service 를 참조하고, Service 는 Repository 를 참조한다.
- Controller 는 Service 와 1:N 관계를 가진다.
- Service 는 Repository 와 1:N 관계를 가진다.
- Controller에서 Repository를 직접 참조하지 않는다.
- Repository 와 Service 는 모두 Interface 를 통해 의존성을 주입받는다.
- Common 폴더는 모든 계층에서 공통적으로 사용되는 유틸리티나 헬퍼 클래스를 포함한다.
### 오류 코드
- 0xx : 성공
- 1xx : 입력 오류
- 2xx : 출력 오류
- 3xx : 통신 오류
- 999 : 알 수 없는 오류

View File

@ -26,7 +26,7 @@ using Back.Program.Services.V1.Interfaces;
Boolean isLocal = false; Boolean isLocal = false;
// 로컬 테스트 할 때는 이거 키고 아니면 끄기 // 로컬 테스트 할 때는 이거 키고 아니면 끄기
// isLocal = true; isLocal = true;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@ -117,7 +117,9 @@ builder.Services.AddSingleton<IPushQueue, InMemoryPushQueue>();
builder.Services.AddHostedService<PushBackgroundService>(); builder.Services.AddHostedService<PushBackgroundService>();
// PUSH 설정부 끝 // PUSH 설정부 끝
builder.Services.AddControllers(); builder.Services.AddControllers();
// 세션 설정 // 세션 설정
// IN-MEMORY 캐시 // IN-MEMORY 캐시
builder.Services.AddDistributedMemoryCache(); builder.Services.AddDistributedMemoryCache();
@ -140,6 +142,8 @@ builder.Services.AddScoped<IAppRepository, AppRepository>();
// builder.Services.AddScoped<IPushService, PushService>(); // builder.Services.AddScoped<IPushService, PushService>();
builder.Services.AddScoped<IPushRepository, PushRepository>(); builder.Services.AddScoped<IPushRepository, PushRepository>();
builder.Services.AddScoped<SessionManager>();
builder.Services.AddScoped<DedicateWeb>();
// builder.Services.AddScoped<UserService>(); // // builder.Services.AddScoped<UserService>(); //
// builder.Services.AddScoped<UserController>(); // builder.Services.AddScoped<UserController>();

View File

@ -0,0 +1,76 @@
using System.Security.Claims;
using System.Text.Json;
using Back.Program.Common.Data;
using Back.Program.Common.Model;
using Back.Program.Services.V1.Interfaces;
namespace Back.Program.Common.Auth;
public class DedicateWeb(
ILogger<DedicateWeb> _logger,
SessionManager _sessionManager,
IRepositoryService _repositoryService,
JwtTokenService _jwtTokenService,
IAppService _appService)
{
public async Task<(string code, string result)> GetAuthToken()
{
var summary = "GetAuthToken";
try
{
// 1. 세션에서 토큰 가져오기
var (result, token) = await _sessionManager.GetString("token");
_logger.LogInformation($"세션에서 토큰 가져오기 결과: {result}, 토큰: {token}");
if (!result || string.IsNullOrEmpty(token))
{
_logger.LogWarning($"세션에 토큰이 없습니다");
return ("200", "세션에 토큰 없음");
}
// 2. 토큰 검증
var validToken = await _jwtTokenService.ValidateToken(token);
_logger.LogInformation($"토큰 검증 결과: {validToken != null}");
if (validToken == null)
{
// 3. 토큰이 유효하지 않으면 리프레시 토큰으로 새 토큰 발급 시도
var (refreshResult, refreshToken) = await _sessionManager.GetString("refresh");
_logger.LogInformation($"리프레시 토큰 가져오기 결과: {refreshResult}, 토큰: {refreshToken}");
if (!refreshResult || string.IsNullOrEmpty(refreshToken))
{
_logger.LogWarning($"리프레시 토큰이 없습니다");
return ("200", "리프레시 토큰 없음");
}
// 4. 리프레시 토큰으로 새 토큰 발급
var retryResult = await _appService.RetryAccess(summary, refreshToken);
_logger.LogInformation($"토큰 재발급 결과: {retryResult.status.code}");
if (retryResult.status.code == "000")
{
// 5. 새 토큰을 세션에 저장
var data = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(retryResult.data));
var newToken = data.GetProperty("access").GetString();
await _sessionManager.SetString("token", newToken);
_logger.LogInformation($"[{summary}] 새 토큰 세션 저장 완료");
return ("000", newToken);
}
else
{
_logger.LogWarning($"[{summary}] 토큰 갱신 실패: {retryResult.status.message}");
return ("102", "토큰 갱신 실패");
}
}
return ("000", token);
}
catch (Exception ex)
{
_logger.LogError($"[{summary}] 세션 데이터 조회 중 오류: {ex.Message}");
_logger.LogError($"[{summary}] 스택 트레이스: {ex.StackTrace}");
return ("100", "세션 데이터 조회 중 오류");
}
}
}

View File

@ -22,6 +22,8 @@ namespace Back.Program.Common.Auth
_logger = logger; _logger = logger;
} }
// JWT 토큰 생성
public string GenerateJwtToken(string jwtKey)//, string role) public string GenerateJwtToken(string jwtKey)//, string role)
{ {
// 1. 클레임(Claim) 설정 - 필요에 따라 추가 정보도 포함 // 1. 클레임(Claim) 설정 - 필요에 따라 추가 정보도 포함
@ -53,6 +55,7 @@ namespace Back.Program.Common.Auth
return new JwtSecurityTokenHandler().WriteToken(token); return new JwtSecurityTokenHandler().WriteToken(token);
} }
// 리프레시 토큰 생성
public RefreshToken GenerateRefreshToken(string uid) public RefreshToken GenerateRefreshToken(string uid)
{ {
var randomNumber = new byte[32]; // 256비트 var randomNumber = new byte[32]; // 256비트

View File

@ -0,0 +1,53 @@
namespace Back.Program.Common.Data;
public class SessionManager
{
private readonly IHttpContextAccessor _http;
public SessionManager(IHttpContextAccessor http)
{
_http = http;
}
public Task<bool> SetString(string key, string value)
{
try
{
_http.HttpContext.Session.SetString(key, value);
return Task.FromResult(true);
}
catch
{
return Task.FromResult(false);
}
}
public Task<(bool result, string data)> GetString(string key)
{
try
{
var value = _http.HttpContext.Session.GetString(key);
return Task.FromResult((true, value ?? string.Empty));
}
catch
{
return Task.FromResult((false, ""));
}
}
public Task<bool> Remove(string key)
{
try
{
_http.HttpContext.Session.Remove(key);
return Task.FromResult(true);
}
catch
{
return Task.FromResult(false);
}
}
}

View File

@ -25,9 +25,10 @@ namespace Back.Program.Controllers.V1
private readonly JwtTokenService _jwtTokenService; private readonly JwtTokenService _jwtTokenService;
private readonly IAppService _appService; private readonly IAppService _appService;
private readonly IAppRepository _appRepository; private readonly IAppRepository _appRepository;
private readonly ISessionService _sessionService; private readonly SessionManager _sessionManager;
public AppController(AppDbContext dbContext, ILogger<AppController> logger, IRepositoryService repositoryService, JwtTokenService jwtTokenService,IAppService appService, IAppRepository appRepository, ISessionService sessionService) public AppController(AppDbContext dbContext, ILogger<AppController> logger, IRepositoryService repositoryService,
JwtTokenService jwtTokenService, IAppService appService, IAppRepository appRepository, SessionManager sessionManager)
{ {
_dbContext = dbContext; _dbContext = dbContext;
_logger = logger; _logger = logger;
@ -35,7 +36,7 @@ namespace Back.Program.Controllers.V1
_jwtTokenService = jwtTokenService; _jwtTokenService = jwtTokenService;
_appService = appService; _appService = appService;
_appRepository = appRepository; _appRepository = appRepository;
_sessionService = sessionService; _sessionManager = sessionManager;
} }
@ -54,7 +55,7 @@ namespace Back.Program.Controllers.V1
} }
[HttpGet("version")] [HttpGet("version")]
[CustomOperation("앱 버전 확인","앱 버전을 확인해서 업데이트 여부 판단", "시스템")] [CustomOperation("앱 버전 확인", "앱 버전을 확인해서 업데이트 여부 판단", "시스템")]
public async Task<IActionResult> GetVersionData(string type) public async Task<IActionResult> GetVersionData(string type)
{ {
if (string.IsNullOrEmpty(type)) return BadRequest(APIResponse.InvalidInputError()); if (string.IsNullOrEmpty(type)) return BadRequest(APIResponse.InvalidInputError());
@ -82,7 +83,7 @@ namespace Back.Program.Controllers.V1
return BadRequest(APIResponse.InvalidInputError()); return BadRequest(APIResponse.InvalidInputError());
} }
var (success, value) = await _sessionService.GetString(key); var (success, value) = await _sessionManager.GetString(key);
if (!success) if (!success)
{ {
return BadRequest(APIResponse.InvalidInputError()); return BadRequest(APIResponse.InvalidInputError());
@ -106,7 +107,7 @@ namespace Back.Program.Controllers.V1
foreach(var request in requests) foreach(var request in requests)
{ {
Console.WriteLine($"세션 저장 시도 - key: {request.key}, value: {request.value}"); Console.WriteLine($"세션 저장 시도 - key: {request.key}, value: {request.value}");
var success = await _sessionService.SetString(request.key, request.value); var success = await _sessionManager.SetString(request.key, request.value);
if (!success) if (!success)
{ {
Console.WriteLine($"세션 저장 실패 - key: {request.key}"); Console.WriteLine($"세션 저장 실패 - key: {request.key}");

View File

@ -1,4 +1,5 @@
using System.Text.Json; using System.Text.Json;
using Back.Program.Common.Data;
using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -10,6 +11,8 @@ using Back.Program.Models.APIResponses;
namespace Back.Program.Controllers; namespace Back.Program.Controllers;
// TO-DO: 여기 controller, service, repository 분리 필요
[ApiController] [ApiController]
[Route("/api/v1/out/user")] [Route("/api/v1/out/user")]
[ApiExplorerSettings(GroupName = "외부 동작(사용자)")] [ApiExplorerSettings(GroupName = "외부 동작(사용자)")]
@ -19,21 +22,27 @@ public class OutController: ControllerBase
private readonly IRepositoryService _repositoryService; private readonly IRepositoryService _repositoryService;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IKakaoService _kakaoService; private readonly IKakaoService _kakaoService;
private readonly ISessionService _sessionService; private readonly SessionManager _sessionManager;
public OutController(ILogger<OutController> logger, public OutController(ILogger<OutController> logger,
IRepositoryService repositoryService, IUserService userService, IKakaoService kakaoService, ISessionService sessionService) IRepositoryService repositoryService, IUserService userService, IKakaoService kakaoService, SessionManager sessionManager)
{ {
_logger = logger; _logger = logger;
_repositoryService = repositoryService; _repositoryService = repositoryService;
_userService = userService; _userService = userService;
_kakaoService = kakaoService; _kakaoService = kakaoService;
_sessionService = sessionService; _sessionManager = sessionManager;
} }
[HttpGet("kakao/auth")] [HttpGet("kakao/auth")]
[CustomOperation("카카오 로그인", "카카오 로그인 동작", "사용자")] [CustomOperation("카카오 로그인", "카카오 로그인 동작", "사용자")]
public async Task<IActionResult> KakaoLogin([FromQuery] string? scope) public async Task<IActionResult> KakaoLogin([FromQuery] string? scope, [FromQuery] string? redirectPath)
{ {
if (!string.IsNullOrEmpty(redirectPath))
{
await _sessionManager.SetString("redirectPath", redirectPath);
}
var url = await _kakaoService.GetAuthorizationUrl(scope ?? ""); var url = await _kakaoService.GetAuthorizationUrl(scope ?? "");
Console.WriteLine($"카카오 로그인 API: {url}"); Console.WriteLine($"카카오 로그인 API: {url}");
return Ok(new { url }); return Ok(new { url });
@ -42,54 +51,98 @@ public class OutController: ControllerBase
[HttpGet("kakao/redirect")] [HttpGet("kakao/redirect")]
public async Task<IActionResult> RedirectFromKakao([FromQuery] string code) public async Task<IActionResult> RedirectFromKakao([FromQuery] string code)
{ {
_logger.LogInformation("카카오 리다이렉트 시작");
var (success, response) = await _kakaoService.Redirect(code); var (success, response) = await _kakaoService.Redirect(code);
Console.WriteLine($"리다이렉트 : {response}"); _logger.LogInformation($"리다이렉트 결과: {success}, 응답: {response}");
if (success) if (success)
{ {
// HttpContext.Session.SetString("AccessToken", response);
var (idSuccess, idResponse) = await _kakaoService.UserMe(response); var (idSuccess, idResponse) = await _kakaoService.UserMe(response);
_logger.LogInformation($"사용자 정보 조회 결과: {idSuccess}, 응답: {idResponse}");
if (idSuccess) if (idSuccess)
{ {
var json = JsonDocument.Parse(idResponse); var json = JsonDocument.Parse(idResponse);
if (json.RootElement.TryGetProperty("id", out var idElement)) if (json.RootElement.TryGetProperty("id", out var idElement))
{ {
var snsId = idElement.ToString(); var snsId = idElement.ToString();
Console.WriteLine($"ID = {snsId}"); _logger.LogInformation($"카카오 ID: {snsId}");
var loginResult = await _userService.Login("SNS Login", "ST01", snsId); var loginResult = await _userService.Login("SNS Login", "ST01", snsId);
Console.WriteLine($"login = {loginResult.JsonToString()}"); _logger.LogInformation($"로그인 결과: {loginResult.JsonToString()}");
if (loginResult.status.code == "000") if (loginResult.status.code == "000")
{ {
var data = JsonSerializer.Deserialize<LoginAPIResponse>(JsonSerializer.Serialize(loginResult.data));
_logger.LogInformation($"로그인 데이터: {JsonSerializer.Serialize(data)}");
var data = loginResult.data as LoginAPIResponse ?? new LoginAPIResponse();
if (data != null) if (data != null)
{ {
string token = data.token; string token = data.token;
string refresh = data.refresh; string refresh = data.refresh;
if (await _sessionService.SetString("token", token) && _logger.LogInformation($"토큰 저장 시도 - token: {token}, refresh: {refresh}");
await _sessionService.SetString("refresh", refresh))
if (await _sessionManager.SetString("token", token) &&
await _sessionManager.SetString("refresh", refresh))
{ {
return Redirect("/about"); _logger.LogInformation("세션 저장 성공");
var (hasPath, redirectPath) = await _sessionManager.GetString("redirectPath");
await _sessionManager.Remove("redirectPath"); // 사용 후 세션에서 제거
var redirectUrl = hasPath && !string.IsNullOrEmpty(redirectPath)
? $"{redirectPath}?auth=true"
: "/about?auth=true";
_logger.LogInformation($"리다이렉트 URL: {redirectUrl}");
return Redirect(redirectUrl);
} }
else
{
_logger.LogError("세션 저장 실패");
}
}
else
{
_logger.LogError("로그인 데이터가 null입니다");
} }
} }
else if (loginResult.status.code == "001") else if (loginResult.status.code == "001")
{ {
if (await _sessionService.SetString("snsId", snsId)) _logger.LogInformation("회원가입 필요");
if (await _sessionManager.SetString("snsId", snsId))
{ {
return Redirect("/auth/register"); return Redirect("/auth/register");
} }
} }
else else
{ {
_logger.LogError($"로그인 실패: {loginResult.status.message}");
return BadRequest(new { error = "로그인 실패", message = loginResult.status.message }); return BadRequest(new { error = "로그인 실패", message = loginResult.status.message });
} }
// return Ok(new { id="cc" }); }
else
{
_logger.LogError("카카오 ID를 찾을 수 없습니다");
} }
} }
Console.WriteLine($"ID_res = {idResponse}"); else
{
_logger.LogError($"사용자 정보 조회 실패: {idResponse}");
}
}
else
{
_logger.LogError($"카카오 리다이렉트 실패: {response}");
} }
return BadRequest(); return BadRequest();
} }
// // 로그아웃 API 예시 (이미 있다면 해당 위치에 추가)
// [HttpGet("logout")]
// public IActionResult Logout()
// {
// // 세션/쿠키 등 로그아웃 처리
// Response.Cookies.Delete("IsLogin");
// // 기타 로그아웃 처리 로직...
// return Redirect("/");
// }
} }

View File

@ -0,0 +1,37 @@
using Back.Program.Common.Data;
using Back.Program.Services.V1.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace Back.Program.Controllers.V1;
/// <summary>
/// USER는 사용자가 자신의 데이터를 보거나 만들거나 하는 등 직접 사용하는 경우에 사용
/// </summary>
[ApiController]
[Route("/api/v1/in/user")]
[ApiExplorerSettings(GroupName = "")]
public class SessionController : ControllerBase
{
private readonly ILogger<SessionController> _logger;
private readonly IRepositoryService _repositoryService;
private readonly SessionManager _sessionManager;
private readonly ISessionService _sessionService;
private SessionController(ILogger<SessionController> logger,
IRepositoryService repositoryService, SessionManager sessionManager, ISessionService sessionService)
{
_logger = logger;
_repositoryService = repositoryService;
_sessionManager = sessionManager;
_sessionService = sessionService;
}
[HttpGet("session/user")]
[CustomOperation("세션 정보 확인", "세션 정보 확인", "사용자")]
public async Task<IActionResult> GetSessionData()
{
string summary = _repositoryService.ReadSummary(typeof(UserController), "GetSessionData");
var result = await _sessionService.GetSessionData(summary);
return Ok(result);
}
}

View File

@ -17,19 +17,16 @@ namespace Back.Program.Controllers.V1
[ApiController] [ApiController]
[Route("/api/v1/in/user")] [Route("/api/v1/in/user")]
[ApiExplorerSettings(GroupName = "사용자")] [ApiExplorerSettings(GroupName = "사용자")]
public class UserController : ControllerBase public class UserController(
ILogger<UserController> logger,
SessionManager sessionManager,
DedicateWeb dedicateWeb,
IRepositoryService repositoryService,
IUserService userService)
: ControllerBase
{ {
private readonly ILogger<UserController> _logger; private readonly ILogger<UserController> _logger = logger;
private readonly IRepositoryService _repositoryService; private readonly SessionManager _sessionManager = sessionManager;
private readonly IUserService _userService;
public UserController(ILogger<UserController> logger,
IRepositoryService repositoryService, IUserService userService, IKakaoService kakaoService)
{
_logger = logger;
_repositoryService = repositoryService;
_userService = userService;
}
[HttpGet] [HttpGet]
[CustomOperation("회원 정보 조회", "회원 정보 조회 (자기자신)", "사용자")] [CustomOperation("회원 정보 조회", "회원 정보 조회 (자기자신)", "사용자")]
@ -37,9 +34,15 @@ namespace Back.Program.Controllers.V1
{ {
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = _repositoryService.ReadSummary(typeof(UserController), "GetUserData"); string summary = repositoryService.ReadSummary(typeof(UserController), "GetUserData");
if (token == "VO00")
{
var (code, WebAuthResult) = await dedicateWeb.GetAuthToken();
if (code != "000") return Ok(APIResponse.Send(code, $"{WebAuthResult}", new { }));
token = WebAuthResult;
}
var result = await _userService.GetUser(summary, token); var result = await userService.GetUser(summary, token);
return Ok(result); return Ok(result);
} }
@ -52,8 +55,8 @@ namespace Back.Program.Controllers.V1
if (string.IsNullOrEmpty(accType) && string.IsNullOrEmpty(snsId)) return BadRequest(APIResponse.InvalidInputError()); if (string.IsNullOrEmpty(accType) && string.IsNullOrEmpty(snsId)) return BadRequest(APIResponse.InvalidInputError());
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = _repositoryService.ReadSummary(typeof(UserController), "Login"); string summary = repositoryService.ReadSummary(typeof(UserController), "Login");
var result = await _userService.Login(summary, accType, snsId); var result = await userService.Login(summary, accType, snsId);
return Ok(result); return Ok(result);
} }
@ -63,9 +66,9 @@ namespace Back.Program.Controllers.V1
public async Task<IActionResult> UserRegister([FromBody] UserAll request) public async Task<IActionResult> UserRegister([FromBody] UserAll request)
{ {
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = _repositoryService.ReadSummary(typeof(UserController), "UserRegister"); string summary = repositoryService.ReadSummary(typeof(UserController), "UserRegister");
var result = await _userService.Register(summary, request); var result = await userService.Register(summary, request);
return Ok(result); return Ok(result);
} }
@ -75,9 +78,9 @@ namespace Back.Program.Controllers.V1
{ {
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = _repositoryService.ReadSummary(typeof(UserController), "Logout"); string summary = repositoryService.ReadSummary(typeof(UserController), "Logout");
var result = await _userService.Logout(summary, token); var result = await userService.Logout(summary, token);
return Ok(result); return Ok(result);
} }
@ -88,8 +91,8 @@ namespace Back.Program.Controllers.V1
{ {
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = _repositoryService.ReadSummary(typeof(UserController), "Cancel"); string summary = repositoryService.ReadSummary(typeof(UserController), "Cancel");
var result = await _userService.Cancel(summary, token); var result = await userService.Cancel(summary, token);
return Ok(result); return Ok(result);
} }
@ -100,17 +103,12 @@ namespace Back.Program.Controllers.V1
{ {
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = _repositoryService.ReadSummary(typeof(UserController), "ReadAcademyInfo"); string summary = repositoryService.ReadSummary(typeof(UserController), "ReadAcademyInfo");
var result = await _userService.GetAcademy(summary, token); var result = await userService.GetAcademy(summary, token);
return Ok(result); return Ok(result);
} }
} }
} }

View File

@ -158,11 +158,11 @@ public class AppService: IAppService
{ {
var refreshToken = await _appRepository.FindRefreshToken(refresh); var refreshToken = await _appRepository.FindRefreshToken(refresh);
if (refreshToken == null) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 문제"); if (refreshToken == null) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 문제");
if (refreshToken.revoke_Date < DateTime.Now) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 만료"); if (refreshToken.revoke_Date != null) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 폐기");
if (refreshToken.expire_date < DateTime.Now) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 폐기"); if (refreshToken.expire_date < DateTime.Now) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 만료");
string access = _jwtTokenService.GenerateJwtToken(refreshToken.uid); string access = _jwtTokenService.GenerateJwtToken(refreshToken.uid);
return APIResponse.Send<object>("000", $"[{summary}], 토큰 생성 완료", new { accsee = access }); return APIResponse.Send<object>("000", $"[{summary}], 토큰 생성 완료", new { access = access });
} }
} }

View File

@ -1,8 +1,9 @@
using Back.Program.Common.Model;
using Back.Program.Models.Entities;
namespace Back.Program.Services.V1.Interfaces; namespace Back.Program.Services.V1.Interfaces;
public interface ISessionService public interface ISessionService
{ {
Task<bool> SetString(string key, string value); Task<APIResponseStatus<object>> GetSessionData(string summary);
Task<(bool result, string data)> GetString(string key);
Task<bool> Remove(string key);
} }

View File

@ -11,6 +11,5 @@ namespace Back.Program.Services.V1.Interfaces
Task<APIResponseStatus<object>> Logout(string summary, string token); Task<APIResponseStatus<object>> Logout(string summary, string token);
Task<APIResponseStatus<object>> Cancel(string summary, string token); Task<APIResponseStatus<object>> Cancel(string summary, string token);
Task<APIResponseStatus<object>> GetAcademy(string summary, string token); Task<APIResponseStatus<object>> GetAcademy(string summary, string token);
} }
} }

View File

@ -1,50 +1,111 @@
using System.Security.Claims;
using System.Text.Json;
using Back.Program.Common.Auth;
using Back.Program.Common.Data;
using Back.Program.Common.Model;
using Back.Program.Repositories.V1.Interfaces;
using Back.Program.Services.V1.Interfaces; using Back.Program.Services.V1.Interfaces;
namespace Back.Program.Services.V1; namespace Back.Program.Services.V1;
public class SessionService: ISessionService public class SessionService: ISessionService
{ {
private readonly IHttpContextAccessor _http; private readonly ILogger<ISessionService> _logger;
public SessionService(IHttpContextAccessor http) private readonly IUserRepository _userRepository;
private readonly JwtTokenService _jwtTokenService;
private readonly IRepositoryService _repositoryService;
private readonly ILogRepository _logRepository;
private readonly SessionManager _sessionManager;
private readonly IAppService _appService;
public SessionService(ILogger<ISessionService> logger, IUserRepository userRepository,
JwtTokenService jwtTokenService,
IRepositoryService repositoryService, ILogRepository logRepository,
SessionManager sessionManager, IAppService appService)
{ {
_http = http; _logger = logger;
_userRepository = userRepository;
_jwtTokenService = jwtTokenService;
_repositoryService = repositoryService;
_logRepository = logRepository;
_sessionManager = sessionManager;
_appService = appService;
} }
public Task<bool> SetString(string key, string value) public async Task<APIResponseStatus<object>> GetSessionData(string summary)
{ {
try try
{ {
_http.HttpContext.Session.SetString(key, value); _logger.LogInformation($"[{summary}] 세션 데이터 조회 시작");
return Task.FromResult(true);
} // 1. 세션에서 토큰 가져오기
catch var (result, token) = await _sessionManager.GetString("token");
_logger.LogInformation($"[{summary}] 세션에서 토큰 가져오기 결과: {result}, 토큰: {token}");
if (!result || string.IsNullOrEmpty(token))
{ {
return Task.FromResult(false); _logger.LogWarning($"[{summary}] 세션에 토큰이 없습니다");
return APIResponse.Send<object>("200", "세션에 토큰이 없습니다", new { });
} }
// 2. 토큰 검증
var validToken = await _jwtTokenService.ValidateToken(token);
_logger.LogInformation($"[{summary}] 토큰 검증 결과: {validToken != null}");
if (validToken == null)
{
// 3. 토큰이 유효하지 않으면 리프레시 토큰으로 새 토큰 발급 시도
var (refreshResult, refreshToken) = await _sessionManager.GetString("refresh");
_logger.LogInformation($"[{summary}] 리프레시 토큰 가져오기 결과: {refreshResult}, 토큰: {refreshToken}");
if (!refreshResult || string.IsNullOrEmpty(refreshToken))
{
_logger.LogWarning($"[{summary}] 리프레시 토큰이 없습니다");
return APIResponse.Send<object>("201", "리프레시 토큰이 없습니다", new { });
} }
public Task<(bool result, string data)> GetString(string key)
// 4. 리프레시 토큰으로 새 토큰 발급
var retryResult = await _appService.RetryAccess(summary, refreshToken);
_logger.LogInformation($"[{summary}] 토큰 재발급 결과: {retryResult.status.code}");
if (retryResult.status.code == "000")
{ {
try // 5. 새 토큰을 세션에 저장
{ var data = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(retryResult.data));
var value = _http.HttpContext.Session.GetString(key); var newToken = data.GetProperty("access").GetString();
return Task.FromResult((true, value ?? string.Empty)); await _sessionManager.SetString("token", newToken);
_logger.LogInformation($"[{summary}] 새 토큰 세션 저장 완료");
// 6. 새 토큰으로 사용자 정보 조회
validToken = await _jwtTokenService.ValidateToken(newToken);
} }
catch else
{ {
return Task.FromResult((false, "")); _logger.LogWarning($"[{summary}] 토큰 갱신 실패: {retryResult.status.message}");
} return APIResponse.Send<object>("202", "토큰 갱신 실패", new { });
}
public Task<bool> Remove(string key)
{
try
{
_http.HttpContext.Session.Remove(key);
return Task.FromResult(true);
}
catch
{
return Task.FromResult(false);
} }
} }
// 7. 최종적으로 유효한 토큰으로 사용자 정보 조회
var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty;
_logger.LogInformation($"[{summary}] 사용자 ID: {uid}");
var user = await _userRepository.FindUser(uid);
_logger.LogInformation($"[{summary}] 사용자 정보 조회 결과: {user != null}");
if (user == null)
{
_logger.LogWarning($"[{summary}] 사용자 정보를 찾을 수 없습니다");
return APIResponse.Send<object>("203", "사용자 정보를 찾을 수 없습니다", new { });
}
_logger.LogInformation($"[{summary}] 세션 데이터 조회 성공: {user.name}");
return APIResponse.Send<object>("000", $"[{summary}], 정상", user);
}
catch (Exception ex)
{
_logger.LogError($"[{summary}] 세션 데이터 조회 중 오류: {ex.Message}");
_logger.LogError($"[{summary}] 스택 트레이스: {ex.StackTrace}");
return APIResponse.InternalSeverError($"[{summary}], 세션 데이터 조회 실패");
}
}
} }

View File

@ -4,6 +4,8 @@ using Back.Program.Common.Model;
using Back.Program.Models.Entities; using Back.Program.Models.Entities;
using Back.Program.Repositories.V1.Interfaces; using Back.Program.Repositories.V1.Interfaces;
using Back.Program.Services.V1.Interfaces; using Back.Program.Services.V1.Interfaces;
using System.Text.Json;
using Back.Program.Common.Data;
namespace Back.Program.Services.V1 namespace Back.Program.Services.V1
{ {
@ -14,18 +16,24 @@ namespace Back.Program.Services.V1
private readonly JwtTokenService _jwtTokenService; private readonly JwtTokenService _jwtTokenService;
private readonly IRepositoryService _repositoryService; private readonly IRepositoryService _repositoryService;
private readonly ILogRepository _logRepository; private readonly ILogRepository _logRepository;
private readonly IAppService _appService;
private readonly SessionManager _sessionManager;
public UserService(ILogger<IUserService> logger, IUserRepository userRepository, public UserService(ILogger<IUserService> logger, IUserRepository userRepository,
JwtTokenService jwtTokenService, JwtTokenService jwtTokenService,
IRepositoryService repositoryService, ILogRepository logRepository) IRepositoryService repositoryService, ILogRepository logRepository,
IAppService appService, SessionManager sessionManager)
{ {
_logger = logger; _logger = logger;
_userRepository = userRepository; _userRepository = userRepository;
_jwtTokenService = jwtTokenService; _jwtTokenService = jwtTokenService;
_repositoryService = repositoryService; _repositoryService = repositoryService;
_logRepository = logRepository; _logRepository = logRepository;
_appService = appService;
_sessionManager = sessionManager;
} }
public async Task<APIResponseStatus<object>> GetUser(string summary, string token) public async Task<APIResponseStatus<object>> GetUser(string summary, string token)
{ {
var validToken = await _jwtTokenService.ValidateToken(token); var validToken = await _jwtTokenService.ValidateToken(token);
@ -46,7 +54,7 @@ namespace Back.Program.Services.V1
var user = await _userRepository.FindUser(login.uid); var user = await _userRepository.FindUser(login.uid);
if (user == null) if (user == null)
return APIResponse.Send<object>("002", $"[{summary}], 회원 정보 오류", new {}); return APIResponse.Send<object>("002", $"[{summary}], 회원 정보 오류", new { });
user.login_date = DateTime.Now; user.login_date = DateTime.Now;
var token = _jwtTokenService.GenerateJwtToken(user.uid); var token = _jwtTokenService.GenerateJwtToken(user.uid);
@ -170,7 +178,7 @@ namespace Back.Program.Services.V1
refresh.revoke_Date = DateTime.Now; refresh.revoke_Date = DateTime.Now;
if (await _repositoryService.SaveData<RefreshToken>(refresh)) if (await _repositoryService.SaveData<RefreshToken>(refresh))
{ {
return APIResponse.Send<object>("000", $"[{summary}], 로그아웃 정상", new {}); return APIResponse.Send<object>("000", $"[{summary}], 로그아웃 정상", new { });
} }
} }
@ -184,7 +192,7 @@ namespace Back.Program.Services.V1
var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty;
var user = await _userRepository.FindUser(uid); var user = await _userRepository.FindUser(uid);
if (user == null) return APIResponse.Send<object>("001", $"[{summary}], 회원 정보 확인 오류", new {}); if (user == null) return APIResponse.Send<object>("001", $"[{summary}], 회원 정보 확인 오류", new { });
if (await _repositoryService.DeleteData<User>(user)) if (await _repositoryService.DeleteData<User>(user))
{ {
@ -200,7 +208,7 @@ namespace Back.Program.Services.V1
_logger.LogInformation($"[{summary}]: 성공 - 로그 저장 실패"); _logger.LogInformation($"[{summary}]: 성공 - 로그 저장 실패");
} }
return APIResponse.Send<object>("000", $"[{summary}], 정상", new {}); return APIResponse.Send<object>("000", $"[{summary}], 정상", new { });
} }
else else
{ {
@ -227,7 +235,7 @@ namespace Back.Program.Services.V1
var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty;
var user = await _userRepository.FindUser(uid); var user = await _userRepository.FindUser(uid);
if (user == null) return APIResponse.Send<object>("001", $"[{summary}], 회원 정보 확인 오류", new {}); if (user == null) return APIResponse.Send<object>("001", $"[{summary}], 회원 정보 확인 오류", new { });
var academyList = await _userRepository.FindAcademies(uid); var academyList = await _userRepository.FindAcademies(uid);

1669
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff