From 3ac1062d3c37005bddac311ac89622d4102052d7 Mon Sep 17 00:00:00 2001 From: Seonkyu_Kim Date: Mon, 24 Feb 2025 17:54:38 +0900 Subject: [PATCH] =?UTF-8?q?[=E2=9C=A8]=20JWT=20=EC=83=9D=EC=84=B1=20+=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=A4=91=20&=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85,=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20API=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Program.cs | 2 + Program/Common/JWTToken/JwtTokenService.cs | 46 ++++++++++++++- Program/Common/Model/JwtSettings.cs | 24 +++++++- Program/V1/Controllers/UserController.cs | 68 +++++++++++++++++++++- Program/V1/Models/User.cs | 20 ------- 5 files changed, 133 insertions(+), 27 deletions(-) diff --git a/Program.cs b/Program.cs index ddddd9f..bc65410 100644 --- a/Program.cs +++ b/Program.cs @@ -72,7 +72,9 @@ builder.Services.AddAuthentication(options => // JWT 설정부 끝 builder.Services.AddControllers(); + // 여기다가 API 있는 컨트롤러들 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 0995a5d..a99a2ad 100644 --- a/Program/Common/JWTToken/JwtTokenService.cs +++ b/Program/Common/JWTToken/JwtTokenService.cs @@ -6,6 +6,9 @@ using System.Collections.Generic; using AcaMate.Common.Models; using Microsoft.IdentityModel.Tokens; using Microsoft.Extensions.Options; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc; + using System.Security.Cryptography; @@ -30,8 +33,9 @@ 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 생성 @@ -43,7 +47,7 @@ public class JwtTokenService issuer: _jwtSettings.Issuer, audience: _jwtSettings.Audience, claims: claims, - expires: DateTime.UtcNow.AddMinutes(_jwtSettings.ExpiryMinutes), + expires: DateTime.Now.AddMinutes(_jwtSettings.ExpiryMinutes), signingCredentials: credentials ); @@ -63,10 +67,46 @@ public class JwtTokenService return new RefreshToken() { uid = uid, - token = Convert.ToBase64String(randomNumber), + refresh_token = Convert.ToBase64String(randomNumber), create_Date = DateTime.UtcNow, expire_date = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiryDays) }; } + + + public ClaimsPrincipal ValidateToken(string token) + { + if (string.IsNullOrWhiteSpace(token)) return null; + var tokenHandler = new JwtSecurityTokenHandler(); + + try + { + var key = Encoding.UTF8.GetBytes(_jwtSettings.SecretKey); + var validationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = true, + ValidIssuer = _jwtSettings.Issuer, + ValidateAudience = true, + ValidAudience = _jwtSettings.Audience, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromMinutes(_jwtSettings.ClockSkewMinutes) + }; + var principal = tokenHandler.ValidateToken(token, validationParameters, out var securityToken); + return principal; + } + catch (Exception e) + { + Console.WriteLine($"검증 실패 {e}"); + return null; + } + } + + + + + + } \ No newline at end of file diff --git a/Program/Common/Model/JwtSettings.cs b/Program/Common/Model/JwtSettings.cs index 8f949fe..a7b57cb 100644 --- a/Program/Common/Model/JwtSettings.cs +++ b/Program/Common/Model/JwtSettings.cs @@ -12,16 +12,34 @@ public class JwtSettings public int ClockSkewMinutes { get; set; } public int RefreshTokenExpiryDays { get; set; } } -[Table(("refresh_token"))] + + +[Table("refresh_token")] public class RefreshToken { [Key] [Required(ErrorMessage = "필수 항목 누락")] public string uid { get; set; } - public string token { get; set; } + public string refresh_token { get; set; } public DateTime create_Date { get; set; } public DateTime expire_date { get; set; } // 이건 로그아웃시에 폐기 시킬예정이니 그떄 변경하는걸로 합시다. public DateTime? revoke_Date { get; set; } -} \ No newline at end of file +} +/* +""" +토큰 동작 관련 +다시 물어보자 토큰 로직 관련해서 일단은 로그인을 예로 들면 +1. 로그인을 진행한다. +2. 회원이 DB에 정상적으로 존재한다. +3. 엑세스 토큰과 리프레시 토큰을 생성한다. +4. 엑세스 토큰은 클라이언트로 리프래시 토큰은 서버에 저장하고 클라이언트로도 보낸다. +5. (상황1) 시간이 경과해 엑세스 토큰의 시간이 경과했다. +6. (상황2) 회원 정보에 대한 접근이 필요한 동작을 수행한다. +7. 엑세스 토큰과 리프레시 토큰을 서버로 전송한다. +8. 엑세스 토큰이 만료가 되었음이 확인이 되면 리프레시 토큰으로 새 엑세스를 만들기 위해 리프레시 토큰을 확인한다. +9. 리프레시 토큰이 만료가 되지 않았다면 리프레시 토큰을 토대로 엑세스 토큰을 생성한다. +10. 생성된 엑세스 토큰을 가지고 상황2의 동작을 수행하고 엑세스 토큰을 반환한다. +""" +*/ \ No newline at end of file diff --git a/Program/V1/Controllers/UserController.cs b/Program/V1/Controllers/UserController.cs index df702c7..e40ae7c 100644 --- a/Program/V1/Controllers/UserController.cs +++ b/Program/V1/Controllers/UserController.cs @@ -1,3 +1,6 @@ +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; @@ -9,6 +12,7 @@ using AcaMate.Common.Token; using Microsoft.Extensions.Logging; using Microsoft.EntityFrameworkCore.Query; using MySqlConnector; +using System.Linq; namespace AcaMate.V1.Controllers; @@ -235,8 +239,49 @@ public class UserController : ControllerBase } // TO-DO: jwt 토큰 만들어서 여기서 보내는 작업을 해야 함 + var token = _jwtTokenService.GenerateJwtToken(uid, "admin"); + var refreshToken = _jwtTokenService.GenerateRefreshToken(uid); + await 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"); + } + /* */ - return Ok($"회원가입 : {uid}"); + var result = new APIResponseStatus() + { + status = new Status() + { + code = "000", + message = "정상" + }, + data = new + { + uid = uid, + accessToken = token, + refreshToken = refreshToken.refresh_token + } + }; + + return Ok(result.JsonToString()); } private async Task SaveData (T entity, Expression> key) where T : class { @@ -287,5 +332,26 @@ public class UserController : ControllerBase } } + [HttpGet("logout")] + [CustomOperation("로그아웃", "사용자 로그아웃", "사용자")] + 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"); + } + + return Ok("로그아웃"); + } + } \ No newline at end of file diff --git a/Program/V1/Models/User.cs b/Program/V1/Models/User.cs index 4aecbef..2eab154 100644 --- a/Program/V1/Models/User.cs +++ b/Program/V1/Models/User.cs @@ -79,26 +79,6 @@ public class Permission public bool market_email_yn {get; set;} } -[Table("token")] -public class Token -{ - [Key] - - [Required(ErrorMessage = "필수 항목 누락")] - public string uid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public string refresh_token { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public DateTime create_date { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public DateTime expires_date { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public DateTime revoke_date { get; set; } -} [Table("location")] public class Location