[] 회원가입 시 회원 정보 저장 API & 토큰 추가 API 만드는 중

This commit is contained in:
김선규 2025-02-21 17:53:32 +09:00
parent cebf5a42cb
commit 55f40e56cf
7 changed files with 326 additions and 53 deletions

View File

@ -65,7 +65,8 @@ builder.Services.AddAuthentication(options =>
ValidateIssuerSigningKey = true, ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings.Issuer, ValidIssuer = jwtSettings.Issuer,
ValidAudience = jwtSettings.Audience, ValidAudience = jwtSettings.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)) IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)),
ClockSkew = TimeSpan.FromMinutes(jwtSettings.ClockSkewMinutes)
}; };
}); });
// JWT 설정부 끝 // JWT 설정부 끝
@ -96,6 +97,19 @@ builder.Services.AddCors(option =>
}); });
}); });
// 로그 설정 부분
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
if (builder.Environment.IsDevelopment()) {
builder.Logging.SetMinimumLevel(LogLevel.Trace);
}
else
{
builder.Logging.SetMinimumLevel(LogLevel.Warning);
}
// 로컬 테스트 위한 부분 // 로컬 테스트 위한 부분
builder.WebHost.UseUrls("http://0.0.0.0:5144"); builder.WebHost.UseUrls("http://0.0.0.0:5144");
///// ===== builder 설정 부 ===== ///// ///// ===== builder 설정 부 ===== /////

View File

@ -1,3 +1,4 @@
using AcaMate.Common.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using AcaMate.V1.Models; using AcaMate.V1.Models;
using Version = AcaMate.V1.Models.Version; using Version = AcaMate.V1.Models.Version;
@ -13,11 +14,18 @@ public class AppDbContext: DbContext
//MARK: Program //MARK: Program
public DbSet<Version> Version { get; set; } public DbSet<Version> Version { get; set; }
public DbSet<Academy> Academy { get; set; } public DbSet<Academy> Academy { get; set; }
public DbSet<RefreshToken> RefreshTokens { get; set; }
//MARK: USER //MARK: USER
public DbSet<Login> Login { get; set; } public DbSet<Login> Login { get; set; }
public DbSet<User_Academy> UserAcademy { get; set; } public DbSet<User_Academy> UserAcademy { get; set; }
public DbSet<User> User { get; set; } public DbSet<User> User { get; set; }
public DbSet<Permission> Permission { get; set; }
// public DbSet<Token> Token { get; set; }
public DbSet<Location> Location { get; set; }
public DbSet<Contact> Contact { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {

View File

@ -0,0 +1,72 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Collections.Generic;
using AcaMate.Common.Models;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Extensions.Options;
using System.Security.Cryptography;
namespace AcaMate.Common.Token;
public class JwtTokenService
{
private readonly JwtSettings _jwtSettings;
public JwtTokenService(IOptions<JwtSettings> jwtSettings)
{
_jwtSettings = jwtSettings.Value;
}
public string GenerateJwtToken(string uid, string role)
{
// 1. 클레임(Claim) 설정 - 필요에 따라 추가 정보도 포함
var claims = new List<Claim>
{
// 토큰 주체(sub) 생성을 위해 값으로 uid를 사용함 : 토큰이 대표하는 고유 식별자
new Claim(JwtRegisteredClaimNames.Sub, uid),
// Jti 는 토큰 식별자로 토큰의 고유 ID 이다.
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
// jwt 토큰이 가지는 권한
new Claim(ClaimTypes.Role, role)
// 추가 클레임 예: new Claim(ClaimTypes.Role, "Admin")
};
// 2. 비밀 키와 SigningCredentials 생성
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
// 3. 토큰 생성 (Issuer, Audience, 만료 시간, 클레임, 서명 정보 포함)
var token = new JwtSecurityToken(
issuer: _jwtSettings.Issuer,
audience: _jwtSettings.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(_jwtSettings.ExpiryMinutes),
signingCredentials: credentials
);
// 4. 토큰 객체를 문자열로 변환하여 반환
return new JwtSecurityTokenHandler().WriteToken(token);
}
public RefreshToken GenerateRefreshToken(string uid)
{
var randomNumber = new byte[32]; // 256비트
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
}
// return Convert.ToBase64String(randomNumber);
return new RefreshToken()
{
uid = uid,
token = Convert.ToBase64String(randomNumber),
create_Date = DateTime.UtcNow,
expire_date = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiryDays)
};
}
}

View File

@ -1,3 +1,6 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace AcaMate.Common.Models; namespace AcaMate.Common.Models;
public class JwtSettings public class JwtSettings
@ -6,4 +9,19 @@ public class JwtSettings
public string Issuer { get; set; } public string Issuer { get; set; }
public string Audience { get; set; } public string Audience { get; set; }
public int ExpiryMinutes { get; set; } public int ExpiryMinutes { get; set; }
public int ClockSkewMinutes { get; set; }
public int RefreshTokenExpiryDays { get; set; }
}
[Table(("refresh_token"))]
public class RefreshToken
{
[Key]
[Required(ErrorMessage = "필수 항목 누락")]
public string uid { get; set; }
public string token { get; set; }
public DateTime create_Date { get; set; }
public DateTime expire_date { get; set; }
// 이건 로그아웃시에 폐기 시킬예정이니 그떄 변경하는걸로 합시다.
public DateTime? revoke_Date { get; set; }
} }

View File

@ -1,33 +1,38 @@
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
using AcaMate.Common.Data; using AcaMate.Common.Data;
using AcaMate.Common.Models; using AcaMate.Common.Models;
using AcaMate.V1.Models; using AcaMate.V1.Models;
using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens; 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;
namespace AcaMate.V1.Controllers; namespace AcaMate.V1.Controllers;
[ApiController] [ApiController]
[Route("/api/v1/in/user")] [Route("/api/v1/in/user")]
[ApiExplorerSettings(GroupName = "사용자")] [ApiExplorerSettings(GroupName = "사용자")]
public class UserController: ControllerBase public class UserController : ControllerBase
{ {
private readonly AppDbContext _dbContext; private readonly AppDbContext _dbContext;
public UserController(AppDbContext dbContext) private readonly ILogger<UserController> _logger;
private readonly JwtTokenService _jwtTokenService;
public UserController(AppDbContext dbContext, ILogger<UserController> logger, JwtTokenService jwtTokenService)
{ {
_dbContext = dbContext; _dbContext = dbContext;
_logger = logger;
_jwtTokenService = jwtTokenService;
} }
[HttpGet()] [HttpGet]
[CustomOperation("회원 정보 조회", "회원 정보 조회", "사용자")] [CustomOperation("회원 정보 조회", "회원 정보 조회", "사용자")]
public IActionResult GetUserData(string uid) public IActionResult GetUserData(string uid)
{ {
if (string.IsNullOrEmpty(uid)) if (string.IsNullOrEmpty(uid)) return BadRequest(DefaultResponse.InvalidInputError);
{
return BadRequest(DefaultResponse.InvalidInputError);
}
try try
{ {
@ -41,13 +46,13 @@ public class UserController: ControllerBase
birth = u.birth, birth = u.birth,
device_id = u.device_id, device_id = u.device_id,
login_date = u.login_date, login_date = u.login_date,
type = u.type, type = u.type
}) })
.FirstOrDefault(); .FirstOrDefault();
var response = new APIResponseStatus<User> var response = new APIResponseStatus<User>
{ {
status = new Status() status = new Status
{ {
code = "000", code = "000",
message = "정상" message = "정상"
@ -56,7 +61,7 @@ public class UserController: ControllerBase
}; };
return Ok(response.JsonToString()); return Ok(response.JsonToString());
} }
catch(Exception ex) catch (Exception ex)
{ {
return StatusCode(500, DefaultResponse.UnknownError); return StatusCode(500, DefaultResponse.UnknownError);
} }
@ -66,22 +71,21 @@ public class UserController: ControllerBase
[CustomOperation("SNS 로그인", "로그인 후 회원이 있는지 확인", "사용자")] [CustomOperation("SNS 로그인", "로그인 후 회원이 있는지 확인", "사용자")]
public IActionResult Login(string acctype, string sns_id) public IActionResult Login(string acctype, string sns_id)
{ {
if (string.IsNullOrEmpty(acctype) && string.IsNullOrEmpty(sns_id)) if (string.IsNullOrEmpty(acctype) && string.IsNullOrEmpty(sns_id))
{
return BadRequest(DefaultResponse.InvalidInputError); return BadRequest(DefaultResponse.InvalidInputError);
}
try try
{ {
var login = _dbContext.Login.FirstOrDefault(l => l.sns_type == acctype && l.sns_id == sns_id); var login = _dbContext.Login.FirstOrDefault(l => l.sns_type == acctype && l.sns_id == sns_id);
string uid = ""; var uid = "";
List<string> bids = new List<string>(); List<string> bids = new List<string>();
if (login != null) if (login != null)
{ {
uid = login.uid; uid = login.uid;
var user = _dbContext.User.FirstOrDefault(user => user.uid == uid); var user = _dbContext.User.FirstOrDefault(user => user.uid == uid);
if (user != null) if (user != null)
{ {
@ -89,16 +93,22 @@ public class UserController: ControllerBase
_dbContext.SaveChanges(); _dbContext.SaveChanges();
} }
// 토큰 생성은 로그인 부분에서 하는거지
var accessToken = _jwtTokenService.GenerateJwtToken(uid, "Normal");
var refreshToken = _jwtTokenService.GenerateRefreshToken(uid);
_dbContext.RefreshTokens.Add(refreshToken);
_dbContext.SaveChanges();
var userAcademy = _dbContext.UserAcademy.Where(u => u.uid == uid).ToList(); var userAcademy = _dbContext.UserAcademy.Where(u => u.uid == uid).ToList();
foreach(User_Academy userData in userAcademy) foreach (var userData in userAcademy)
{ {
Console.WriteLine($"uid: {userData.uid} || bid: {userData.bid}"); _logger.LogInformation($"uid: {userData.uid} || bid: {userData.bid}");
bids.Add(userData.bid); bids.Add(userData.bid);
} }
var response = new APIResponseStatus<dynamic> var response = new APIResponseStatus<dynamic>
{ {
status = new Status() status = new Status
{ {
code = "000", code = "000",
message = "정상" message = "정상"
@ -109,7 +119,7 @@ public class UserController: ControllerBase
bid = bids bid = bids
} }
}; };
return Ok(response.JsonToString()); return Ok(response.JsonToString());
} }
else else
@ -117,7 +127,7 @@ public class UserController: ControllerBase
// 계정이 없다는 거 // 계정이 없다는 거
var response = new APIResponseStatus<dynamic> var response = new APIResponseStatus<dynamic>
{ {
status = new Status() status = new Status
{ {
code = "010", code = "010",
message = "정상" message = "정상"
@ -125,7 +135,7 @@ public class UserController: ControllerBase
data = new data = new
{ {
uid = "", uid = "",
bid = new string[]{} bid = new string[] { }
} }
}; };
return Ok(response.JsonToString()); return Ok(response.JsonToString());
@ -136,9 +146,9 @@ public class UserController: ControllerBase
return StatusCode(500, DefaultResponse.UnknownError); return StatusCode(500, DefaultResponse.UnknownError);
} }
} }
[HttpPost("academy")] [HttpPost("academy")]
[CustomOperation("학원 리스트 확인","등록된 학원 리스트 확인", "사용자")] [CustomOperation("학원 리스트 확인", "등록된 학원 리스트 확인", "사용자")]
public IActionResult ReadAcademyInfo([FromBody] RequestAcademy request) public IActionResult ReadAcademyInfo([FromBody] RequestAcademy request)
{ {
if (!request.bids.Any()) if (!request.bids.Any())
@ -156,10 +166,10 @@ public class UserController: ControllerBase
name = a.business_name name = a.business_name
}) })
.ToList(); .ToList();
var response = new APIResponseStatus<List<AcademyName>> var response = new APIResponseStatus<List<AcademyName>>
{ {
status = new Status() status = new Status
{ {
code = "000", code = "000",
message = "정상" message = "정상"
@ -171,17 +181,111 @@ public class UserController: ControllerBase
[HttpPost("register")] [HttpPost("register")]
[CustomOperation("회원 가입", "사용자 회원 가입", "사용자")] [CustomOperation("회원 가입", "사용자 회원 가입", "사용자")]
public IActionResult UserRegister([FromBody] User request) public async Task<IActionResult> UserRegister([FromBody] UserAll request)
{ {
if (request.uid.IsNullOrEmpty()) if (!ModelState.IsValid) return BadRequest(DefaultResponse.InvalidInputError);
var atIndext = request.email.IndexOf('@');
var localPart_email = request.email.Substring(0, atIndext);
var uid = $"AM{localPart_email}{DateTime.Now:yyyyMMdd}";
var user = new User
{ {
var error = DefaultResponse.InvalidInputError; uid = uid,
return Ok(error); name = request.name,
birth = request.birth,
type = request.type,
device_id = request.device_id,
auto_login_yn = request.auto_login_yn,
login_date = request.login_date
};
var login = new Login
{
uid = uid,
sns_id = request.sns_id,
sns_type = request.sns_type
};
var permission = new Permission
{
uid = uid,
location_yn = request.location_yn,
camera_yn = request.camera_yn,
photo_yn = request.photo_yn,
push_yn = request.push_yn,
market_app_yn = request.market_app_yn,
market_sms_yn = request.market_sms_yn,
market_email_yn = request.market_email_yn
};
var contact = new Contact
{
uid = uid,
email = request.email,
phone = request.phone,
address = request.address
};
if (await SaveData<User, string>(user, u => u.uid))
{
await SaveData<Login, string>(login, l => l.sns_id);
await SaveData<Permission, string>(permission, p => p.uid);
await SaveData<Contact, string>(contact, c => c.uid);
} }
else
// TO-DO: jwt 토큰 만들어서 여기서 보내는 작업을 해야 함
return Ok($"회원가입 : {uid}");
}
private async Task<bool> SaveData<T, K> (T entity, Expression<Func<T, K>> key) where T : class
{
try
{ {
return Ok("회원가입"); 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<Func<T, bool>>(equalsExpr, parameter);
var dbSet = _dbContext.Set<T>();
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;
} }
} }
} }

View File

@ -8,10 +8,15 @@ namespace AcaMate.V1.Models;
public class Login public class Login
{ {
[Key] [Key]
[Required(ErrorMessage = "필수 항목 누락")]
[MaxLength(100)] [MaxLength(100)]
public string sns_id {get; set;} public string sns_id {get; set;}
[Required(ErrorMessage = "필수 항목 누락")]
[MaxLength(70)] [MaxLength(70)]
public string uid {get; set;} public string uid {get; set;}
[Required(ErrorMessage = "필수 항목 누락")]
[MaxLength(4)] [MaxLength(4)]
public string sns_type {get; set;} public string sns_type {get; set;}
} }
@ -20,9 +25,16 @@ public class Login
public class User_Academy public class User_Academy
{ {
[Key] [Key]
[Required(ErrorMessage = "필수 항목 누락")]
public string uid { get; set; } public string uid { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public string bid { get; set; } public string bid { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public DateTime register_date { get; set; } public DateTime register_date { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public bool status { get; set; } public bool status { get; set; }
} }
@ -30,12 +42,22 @@ public class User_Academy
public class User public class User
{ {
[Key] [Key]
[Required(ErrorMessage = "필수 항목 누락")]
public string uid { get; set; } public string uid { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public string name { get; set; } public string name { get; set; }
public DateTime birth { get; set; } public DateTime? birth { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public string type { get; set; } public string type { get; set; }
public string device_id { get; set; }
public int auto_login_yn { get; set; } public string? device_id { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public bool auto_login_yn { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public DateTime login_date { get; set; } public DateTime login_date { get; set; }
} }
@ -44,6 +66,8 @@ public class User
public class Permission public class Permission
{ {
[Key] [Key]
[Required(ErrorMessage = "필수 항목 누락")]
public string uid { get; set; } public string uid { get; set; }
public bool location_yn {get; set;} public bool location_yn {get; set;}
@ -59,10 +83,20 @@ public class Permission
public class Token public class Token
{ {
[Key] [Key]
[Required(ErrorMessage = "필수 항목 누락")]
public string uid { get; set; } public string uid { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public string refresh_token { get; set; } public string refresh_token { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public DateTime create_date { get; set; } public DateTime create_date { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public DateTime expires_date { get; set; } public DateTime expires_date { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public DateTime revoke_date { get; set; } public DateTime revoke_date { get; set; }
} }
@ -70,8 +104,14 @@ public class Token
public class Location public class Location
{ {
[Key] [Key]
[Required(ErrorMessage = "필수 항목 누락")]
public string uid { get; set; } public string uid { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public string lat { get; set; } public string lat { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public string lng { get; set; } public string lng { get; set; }
} }
@ -79,25 +119,40 @@ public class Location
public class Contact public class Contact
{ {
[Key] [Key]
[Required(ErrorMessage = "필수 항목 누락")]
public string uid { get; set; } public string uid { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public string email { get; set; } public string email { get; set; }
public string phone { get; set; } public string? phone { get; set; }
public string? address { get; set; }
} }
// -- -- -- -- -- DB 테이블 -- -- -- -- -- // // -- -- -- -- -- DB 테이블 -- -- -- -- -- //
public class UserAll public class UserAll
{ {
public string uid { get; set; } // [Required(ErrorMessage = "필수 항목 누락")]
public string name { get; set; } // public string uid { get; set; }
public DateTime birth { get; set; }
public string type { get; set; }
public string device_id { get; set; }
public int auto_login_yn { get; set; }
public DateTime login_date { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public string name { get; set; }
public DateTime? birth { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public string type { get; set; }
public string? device_id { get; set; }
public bool auto_login_yn { get; set; }
public DateTime login_date { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public string email { get; set; } public string email { get; set; }
public string phone { get; set; } public string? phone { get; set; }
public string? address { get; set; }
public bool location_yn {get; set;} public bool location_yn {get; set;}
public bool camera_yn {get; set;} public bool camera_yn {get; set;}
@ -107,7 +162,9 @@ public class UserAll
public bool market_sms_yn {get; set;} public bool market_sms_yn {get; set;}
public bool market_email_yn {get; set;} public bool market_email_yn {get; set;}
[Required(ErrorMessage = "필수 항목 누락")]
public string sns_id {get; set;} public string sns_id {get; set;}
[Required(ErrorMessage = "필수 항목 누락")]
public string sns_type {get; set;} public string sns_type {get; set;}
public string sns_email {get; set;}
} }

View File

@ -1,7 +1,7 @@
{ {
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Warning",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },