diff --git a/Program.cs b/Program.cs index bc65410..2f460d4 100644 --- a/Program.cs +++ b/Program.cs @@ -75,6 +75,7 @@ builder.Services.AddControllers(); // 여기다가 API 있는 컨트롤러들 AddScoped 하면 되는건가? builder.Services.AddScoped(); +builder.Services.AddScoped(); // builder.Services.AddScoped(); // // builder.Services.AddScoped(); diff --git a/Program/Common/JWTToken/JwtTokenService.cs b/Program/Common/JWTToken/JwtTokenService.cs index a99a2ad..250fc0e 100644 --- a/Program/Common/JWTToken/JwtTokenService.cs +++ b/Program/Common/JWTToken/JwtTokenService.cs @@ -17,13 +17,16 @@ namespace AcaMate.Common.Token; public class JwtTokenService { private readonly JwtSettings _jwtSettings; + + private readonly ILogger _logger; - public JwtTokenService(IOptions jwtSettings) + public JwtTokenService(IOptions jwtSettings, ILogger logger) { _jwtSettings = jwtSettings.Value; + _logger = logger; } - public string GenerateJwtToken(string uid, string role) + public string GenerateJwtToken(string uid)//, string role) { // 1. 클레임(Claim) 설정 - 필요에 따라 추가 정보도 포함 var claims = new List @@ -33,9 +36,8 @@ public class JwtTokenService // Jti 는 토큰 식별자로 토큰의 고유 ID 이다. new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // jwt 토큰이 가지는 권한 - new Claim(ClaimTypes.Role, role), + // new Claim(ClaimTypes.Role, role), // 추가 클레임 예: new Claim(ClaimTypes.Role, "Admin") - new Claim(ClaimTypes.NameIdentifier, uid) }; // 2. 비밀 키와 SigningCredentials 생성 @@ -50,7 +52,7 @@ public class JwtTokenService expires: DateTime.Now.AddMinutes(_jwtSettings.ExpiryMinutes), signingCredentials: credentials ); - + // 4. 토큰 객체를 문자열로 변환하여 반환 return new JwtSecurityTokenHandler().WriteToken(token); } @@ -102,6 +104,8 @@ public class JwtTokenService Console.WriteLine($"검증 실패 {e}"); return null; } + + } diff --git a/Program/Common/Model/JwtSettings.cs b/Program/Common/Model/JwtSettings.cs index a7b57cb..5836c3e 100644 --- a/Program/Common/Model/JwtSettings.cs +++ b/Program/Common/Model/JwtSettings.cs @@ -27,6 +27,13 @@ public class RefreshToken // 이건 로그아웃시에 폐기 시킬예정이니 그떄 변경하는걸로 합시다. public DateTime? revoke_Date { get; set; } } + +public class ValidateToken +{ + public string token { get; set; } + public string refresh { get; set; } + public string uid { get; set; } +} /* """ 토큰 동작 관련 diff --git a/Program/V1/Controllers/UserController.cs b/Program/V1/Controllers/UserController.cs index e40ae7c..00b165d 100644 --- a/Program/V1/Controllers/UserController.cs +++ b/Program/V1/Controllers/UserController.cs @@ -1,19 +1,21 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using Microsoft.IdentityModel.Tokens; -using AcaMate.Common.Data; -using AcaMate.Common.Models; -using AcaMate.V1.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Linq.Expressions; -using AcaMate.V1.Services; -using AcaMate.Common.Token; using Microsoft.Extensions.Logging; using Microsoft.EntityFrameworkCore.Query; using MySqlConnector; using System.Linq; +using AcaMate.Common.Data; +using AcaMate.Common.Token; +using AcaMate.Common.Models; + +using AcaMate.V1.Models; +using AcaMate.V1.Services; + namespace AcaMate.V1.Controllers; [ApiController] @@ -24,12 +26,14 @@ public class UserController : ControllerBase private readonly AppDbContext _dbContext; private readonly ILogger _logger; private readonly JwtTokenService _jwtTokenService; + private readonly IRepositoryService _repositoryService; - public UserController(AppDbContext dbContext, ILogger logger, JwtTokenService jwtTokenService) + public UserController(AppDbContext dbContext, ILogger logger, JwtTokenService jwtTokenService, IRepositoryService repositoryService) { _dbContext = dbContext; _logger = logger; _jwtTokenService = jwtTokenService; + _repositoryService = repositoryService; } [HttpGet] @@ -73,114 +77,136 @@ public class UserController : ControllerBase [HttpGet("login")] [CustomOperation("SNS 로그인", "로그인 후 회원이 있는지 확인", "사용자")] - public IActionResult Login(string acctype, string sns_id) + public async Task Login(string acctype, string sns_id) { + // API 동작 파라미터 입력 값 확인 if (string.IsNullOrEmpty(acctype) && string.IsNullOrEmpty(sns_id)) return BadRequest(DefaultResponse.InvalidInputError); try { - var login = _dbContext.Login.FirstOrDefault(l => l.sns_type == acctype && l.sns_id == sns_id); - var uid = ""; - - List bids = new List(); + var login = await _dbContext.Login + .FirstOrDefaultAsync(l => l.sns_type == acctype && l.sns_id == sns_id); if (login != null) { - uid = login.uid; - + // 로그인 정보가 존재 하는 상황 + var uid = login.uid; - var user = _dbContext.User.FirstOrDefault(user => user.uid == uid); + var user = await _dbContext.User + .FirstOrDefaultAsync(u => u.uid == uid); + if (user != null) { + // 정상적으로 User 테이블에도 있는것이 확인 됨 user.login_date = DateTime.Now; - _dbContext.SaveChanges(); - } - - // 토큰 생성은 로그인 부분에서 하는거지 - var accessToken = _jwtTokenService.GenerateJwtToken(uid, "Normal"); - var refreshToken = _jwtTokenService.GenerateRefreshToken(uid); - _dbContext.RefreshTokens.Add(refreshToken); - _dbContext.SaveChanges(); + await _dbContext.SaveChangesAsync(); + + // 토큰 생성은 로그인이 이제 되고 나서 한다. + var accessToken = _jwtTokenService.GenerateJwtToken(uid);//, "Normal"); + var refreshToken = _jwtTokenService.GenerateRefreshToken(uid); + _logger.LogInformation($"{uid}: {accessToken}, {refreshToken}"); + + await _repositoryService.SaveData(refreshToken, rt => rt.uid); + - var userAcademy = _dbContext.UserAcademy.Where(u => u.uid == uid).ToList(); - foreach (var userData in userAcademy) - { - _logger.LogInformation($"uid: {userData.uid} || bid: {userData.bid}"); - bids.Add(userData.bid); - } - - var response = new APIResponseStatus - { - status = new Status + var response = new APIResponseStatus { - code = "000", - message = "정상" - }, - data = new - { - uid = $"{uid}", - bid = bids - } - }; - - return Ok(response.JsonToString()); + status = new Status + { + code = "000", + message = "정상" + }, + data = new + { + token = accessToken, + refresh = refreshToken.refresh_token + } + }; + return Ok(response.JsonToString()); + } } - else + // case 1: Login 테이블에 값이 없다 == 로그인이 처음 + // case 2: User 테이블에 값이 없다 == 이건 문제가 있는 상황 -> 해결은 회원가입 재 진행 시도 + // Login에는 있는데 User 테이블에 없다? 말이 안되긴 하는데... + return Ok(new APIResponseStatus { - // 계정이 없다는 거 - var response = new APIResponseStatus + status = new Status { - status = new Status - { - code = "010", - message = "정상" - }, - data = new - { - uid = "", - bid = new string[] { } - } - }; - return Ok(response.JsonToString()); - } + code = "010", + message = "로그인 정보 없음 > 회원 가입 진행" + }, + data = new + { + token = "", + refresh = "" + // bidList = new string[] { } + } + }.JsonToString()); } catch (Exception ex) { + _logger.LogInformation($"[로그인] 에러 발생 : {ex}"); return StatusCode(500, DefaultResponse.UnknownError); } } - [HttpPost("academy")] - [CustomOperation("학원 리스트 확인", "등록된 학원 리스트 확인", "사용자")] - public IActionResult ReadAcademyInfo([FromBody] RequestAcademy request) + [HttpGet("academy")] + [CustomOperation("학원 리스트 확인", "사용자가 등록된 학원 리스트 확인", "사용자")] + public async Task ReadAcademyInfo(string token, string refresh) { - if (!request.bids.Any()) + _logger.LogInformation($"토큰 : {token}, {refresh}"); + + if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(refresh)) { var error = DefaultResponse.InvalidInputError; return Ok(error); } - var academies = _dbContext - .Academy - .Where(a => request.bids.Contains(a.bid)) - .Select(a => new AcademyName - { - bid = a.bid, - name = a.business_name - }) - .ToList(); - - var response = new APIResponseStatus> + try { - status = new Status + var validateToken = await _repositoryService.ValidateToken(token, refresh); + var uid = validateToken.uid; + + var userAcademy = await _dbContext.UserAcademy + .Where(ua => ua.uid == uid) + .Select(ua => ua.bid) + .ToListAsync(); + + + var academies = await _dbContext.Academy + .Where(a => userAcademy.Contains(a.bid)) + .Select(a => new AcademyName + { + bid = a.bid, + name = a.business_name + }) + .ToListAsync(); + + var response = new APIResponseStatus> { - code = "000", - message = "정상" - }, - data = academies - }; - return Ok(response); + status = new Status + { + code = "000", + message = "정상" + }, + data = academies + }; + + + return Ok(response); + } + catch (SecurityTokenException tokenEx) + { + _logger.LogInformation($"[로그인][오류] 토큰 검증 : {tokenEx}"); + return StatusCode(500, DefaultResponse.InvalidInputError); + } + catch (Exception ex) + { + _logger.LogInformation($"[로그인][오류] 에러 발생 : {ex}"); + return StatusCode(500, DefaultResponse.UnknownError); + } + } [HttpPost("register")] @@ -231,41 +257,18 @@ public class UserController : ControllerBase }; - if (await SaveData(user, u => u.uid)) + if (await _repositoryService.SaveData(user, u => u.uid)) { - await SaveData(login, l => l.sns_id); - await SaveData(permission, p => p.uid); - await SaveData(contact, c => c.uid); + await _repositoryService.SaveData(login, l => l.sns_id); + await _repositoryService.SaveData(permission, p => p.uid); + await _repositoryService.SaveData(contact, c => c.uid); } // TO-DO: jwt 토큰 만들어서 여기서 보내는 작업을 해야 함 - var token = _jwtTokenService.GenerateJwtToken(uid, "admin"); + var token = _jwtTokenService.GenerateJwtToken(uid);//, "admin"); var refreshToken = _jwtTokenService.GenerateRefreshToken(uid); - await SaveData(refreshToken, rt => rt.uid); + await _repositoryService.SaveData(refreshToken, rt => rt.uid); - - /* */ - var principalToken = _jwtTokenService.ValidateToken(token); - if (principalToken != null) - { - var jti = principalToken.FindFirst(JwtRegisteredClaimNames.Jti)?.Value; - var sub = principalToken.FindFirst(JwtRegisteredClaimNames.Sub)?.Value; - var id = principalToken.FindFirst(ClaimTypes.NameIdentifier)?.Value; - var id2 = principalToken.FindFirst(JwtRegisteredClaimNames.Sub)?.Value; - // var check = principalToken.FindFirst("sub")?.Value; - _logger.LogInformation($"토큰? - {jti}"); - _logger.LogInformation($"토큰? - {sub}"); - _logger.LogInformation($"토큰? - {id}"); - // TODO: 도대체 토큰 이거 sub확인이 왜 안되는걸까.? - - } - else - { - - _logger.LogInformation("dd"); - } - /* */ - var result = new APIResponseStatus() { status = new Status() @@ -275,7 +278,6 @@ public class UserController : ControllerBase }, data = new { - uid = uid, accessToken = token, refreshToken = refreshToken.refresh_token } @@ -283,75 +285,27 @@ public class UserController : ControllerBase return Ok(result.JsonToString()); } - private async Task SaveData (T entity, Expression> key) where T : class - { - try - { - var value = key.Compile()(entity); - - // x 라 함은 Expression 으로 생성되는 트리에서 T 타입으의 매개변수를 지칭함 - var parameter = Expression.Parameter(typeof(T), "x"); - - var invokedExpr = Expression.Invoke(key, parameter); - var constantExpr = Expression.Constant(value, key.Body.Type); - var equalsExpr = Expression.Equal(invokedExpr, constantExpr); - - var predicate = Expression.Lambda>(equalsExpr, parameter); - - var dbSet = _dbContext.Set(); - var entityData = await dbSet.FirstOrDefaultAsync(predicate); - - if (entityData != null) - { - _logger.LogInformation($"[{typeof(T)}] 해당 PK 존재 [{value}]: 계속"); - var entry = _dbContext.Entry(entityData); - entry.CurrentValues.SetValues(entity); - if (entry.Properties.Any(p => p.IsModified)) - { - _logger.LogInformation($"[{typeof(T)}] 변경사항 존재: 계속"); - } - else - { - _logger.LogInformation($"[{typeof(T)}] 변경사항 없음: 종료"); - return true; - } - } - else - { - dbSet.Add(entity); - } - - await _dbContext.SaveChangesAsync(); - _logger.LogInformation($"[{typeof(T)}] DB 저장 완료: 종료"); - return true; - } - catch (Exception ex) - { - _logger.LogInformation($"[{typeof(T)}] 알 수 없는 오류: 종료\n{ex}"); - return false; - } - } [HttpGet("logout")] [CustomOperation("로그아웃", "사용자 로그아웃", "사용자")] - public async Task LogOut(string token, string refresh)//([FromBody] UserAll request) + public async Task LogOut(string token, string refresh) //([FromBody] UserAll request) { - var principalToken = _jwtTokenService.ValidateToken(token); - if (principalToken != null) - { - // var uid = principalToken.FindFirst(JwtRegisteredClaimNames.Jti)?.Value; - var uid = principalToken.FindFirst("sub")?.Value; - _logger.LogInformation($"토큰? - {uid}"); - - } - else - { - - _logger.LogInformation("dd"); - } + /* */ + // var value = await ValidateToken(token, refresh); + // _logger.LogInformation(value.uid); + // _logger.LogInformation(value.refresh); + // _logger.LogInformation(value.token); + /* */ + return Ok("로그아웃"); } + + + + + + } \ No newline at end of file diff --git a/Program/V1/Services/RepositoryService.cs b/Program/V1/Services/RepositoryService.cs new file mode 100644 index 0000000..f53fb60 --- /dev/null +++ b/Program/V1/Services/RepositoryService.cs @@ -0,0 +1,136 @@ +using AcaMate.Common.Data; +using AcaMate.Common.Token; +using AcaMate.Common.Models; + + +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Linq.Expressions; +using System.Security.Claims; +using Microsoft.IdentityModel.Tokens; + +namespace AcaMate.V1.Services; + +public interface IRepositoryService +{ + Task SaveData(T entity, Expression> key) where T : class; + Task ValidateToken(string token, string refresh); +} + +public class RepositoryService: IRepositoryService +{ + private readonly AppDbContext _dbContext; + private readonly ILogger _logger; + private readonly JwtTokenService _jwtTokenService; + + public RepositoryService(AppDbContext dbContext, ILogger logger, JwtTokenService jwtTokenService) + { + _dbContext = dbContext; + _logger = logger; + _jwtTokenService = jwtTokenService; + } + + public async Task SaveData (T entity, Expression> key) where T : class + { + try + { + var value = key.Compile()(entity); + + // x 라 함은 Expression 으로 생성되는 트리에서 T 타입으의 매개변수를 지칭함 + var parameter = Expression.Parameter(typeof(T), "x"); + + var invokedExpr = Expression.Invoke(key, parameter); + var constantExpr = Expression.Constant(value, key.Body.Type); + var equalsExpr = Expression.Equal(invokedExpr, constantExpr); + + var predicate = Expression.Lambda>(equalsExpr, parameter); + + var dbSet = _dbContext.Set(); + + var entityData = await dbSet.FirstOrDefaultAsync(predicate); + + if (entityData != null) + { + _logger.LogInformation($"[{typeof(T)}] 해당 PK 존재 [{value}]: 계속"); + var entry = _dbContext.Entry(entityData); + entry.CurrentValues.SetValues(entity); + if (entry.Properties.Any(p => p.IsModified)) + { + _logger.LogInformation($"[{typeof(T)}] 변경사항 존재: 계속"); + } + else + { + _logger.LogInformation($"[{typeof(T)}] 변경사항 없음: 종료"); + return true; + } + } + else + { + _logger.LogInformation($"[{typeof(T)}] 처음등록"); + dbSet.Add(entity); + } + + await _dbContext.SaveChangesAsync(); + _logger.LogInformation($"[{typeof(T)}] DB 저장 완료: 종료"); + return true; + } + catch (Exception ex) + { + _logger.LogInformation($"[{typeof(T)}] 알 수 없는 오류: 종료 {ex}"); + return false; + } + } + + //토큰 태울때는 인코딩 된 걸로 태워야지 원본꺼 태우면 데이터에 손상옵니다. + public async Task ValidateToken(string token, string refresh) + { + var principalToken = _jwtTokenService.ValidateToken(token); + if (principalToken != null) + { + var uid = principalToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + _logger.LogInformation($"토큰 변환 - {uid}"); + return new ValidateToken + { + token = token, + refresh = refresh, + uid = uid + }; + } + else + { + _logger.LogInformation("엑세스 토큰 만료"); + var refreshToken = await _dbContext.RefreshTokens + .FirstOrDefaultAsync(t => t.refresh_token == refresh); + if (refreshToken == null) + { + throw new SecurityTokenException("리프레시 토큰도 잘못되었음"); + } + var uid = refreshToken.uid; + + if (refreshToken.expire_date > DateTime.Now) + { + _logger.LogInformation($"리프레시 : {uid}"); + var access = _jwtTokenService.GenerateJwtToken(uid); + return new ValidateToken + { + token = access, + refresh = refreshToken.refresh_token, + uid = uid + }; + } + else + { + refreshToken = _jwtTokenService.GenerateRefreshToken(uid); + _logger.LogInformation("리프레시 토큰 만료"); + await SaveData(refreshToken, rt => rt.uid); + return new ValidateToken + { + token = token, + refresh = refreshToken.refresh_token, + uid = uid + }; + } + } + } + +} \ No newline at end of file