diff --git a/.gitignore b/.gitignore
index 2400df5..0502adf 100755
--- a/.gitignore
+++ b/.gitignore
@@ -11,7 +11,6 @@
-
# 기본 파일 및 폴더 제외
*.log
*.env
@@ -55,6 +54,11 @@ obj/
# Blazor 관련
**/wwwroot/_framework/
+./wwwroot
+**/wwwroot
+**/publish
+./publish
+
# Docker 관련
docker-compose.override.yml
@@ -62,4 +66,68 @@ Dockerfile
# 기타 캐시 파일
**/*.cache
-**/*.tmp
\ No newline at end of file
+**/*.tmp# 특정 환경에 따라 추가
+ /private/
+ /publish/
+ /bin/
+ /obj/
+
+ ./private/
+ ./privacy/
+ ./publish/
+ ./bin/
+
+
+
+
+ # 기본 파일 및 폴더 제외
+ *.log
+ *.env
+ *.bak
+ *.tmp
+ *.swp
+
+ # macOS 관련 파일 제외
+ ._
+ ._*
+ .DS_Store
+ .AppleDouble
+ .LSOverride
+ .Spotlight-V100
+ .Trashes
+
+ # Windows 관련
+ Thumbs.db
+ ehthumbs.db
+ desktop.ini
+
+ # Visual Studio 관련
+ .vscode/
+ .vs/
+ *.suo
+ *.user
+ *.userosscache
+ *.sln.docstates
+
+ # Rider 관련
+ .idea/
+ *.sln.iml
+
+ # .NET 관련
+ bin/
+ obj/
+ *.pdb
+ *.dll
+ *.exe
+ *.nuget/
+
+ # Blazor 관련
+ **/wwwroot/_framework/
+
+ # Docker 관련
+ docker-compose.override.yml
+ Dockerfile
+
+ # 기타 캐시 파일
+ **/*.cache
+ **/*.tmp
\ No newline at end of file
diff --git a/Back.csproj b/Back.csproj
index fc3d551..b289ad8 100644
--- a/Back.csproj
+++ b/Back.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/Diary/25.03.md b/Diary/25.03.md
index d961a35..efeb7a4 100644
--- a/Diary/25.03.md
+++ b/Diary/25.03.md
@@ -55,5 +55,23 @@
### 3. USER API 만들기
1. [X] 회원 탈퇴 [./user/cancel]
-2. [보류] 회원 정보 변경 [./user/set]
+2. [ ] [보류] 회원 정보 변경 [./user/set]
- 근데 회원 정보를 변경하는게 뭐뭐를 변경해야 하는지 아직 정해진게 없어서 이건 일단 보류
+
+---
+## 19일(수)
+- 모바일(iOS) 앱 작업을 진행하다가 문제를 발견해서 해결중
+### 1. Header 및 접근 보안 이슈
+1. [X] 헤더 접근 방식 반영하기
+2. [ ] 헤더 접근 위한 고유한 값 생성하기
+ - 로그 저장 안되는 문제 발생
+
+---
+## 20일(수)
+### 1. Header 및 접근 보안 이슈
+1. [X] 헤더 접근 위한 고유값 생성 후 로그 저장에서 발생하는 이슈 확인
+ - dbSet에 등록을 안했던 문제였음
+2. [ ] 이거 헤더 필터 제대로 안걸림
+
+### 2. iOS 앱 과 연결
+1. [ ] 앱에서 데이터 받아 사용자 정보 관리 정상적으로 작동되는지 확인
\ No newline at end of file
diff --git a/Diary/25.04.md b/Diary/25.04.md
new file mode 100644
index 0000000..4f769e8
--- /dev/null
+++ b/Diary/25.04.md
@@ -0,0 +1,63 @@
+# 2025년 4월 To-do
+## 3일 (목)
+### 1. 전체적인 구조 재 정립
+1. [ ] Controller, Model, Repository, Service, DTO 확립
+2. [ ] 확립된 구조에 맞게 폴더 구조 변경 및 네이밍 정의
+3. [ ] 변경된 구조에 맞게 코드 리팩토링
+4. [ ] 응답이나 예외에 맞는 일관되게 코드 통일화
+
+## 리팩토링
+### 리팩토링의 필요성
+1. 현재 C,R,S 등 의 폴더를 만들어는 뒀으나 해당 구조에 맞게 작업이 올바르게 되지 않음
+2. 제대로 구분되지 않다보니 하나의 Controller 에서 다양한 역할과 책임을 맡고 있음
+3. 그러다보니 명확한 확장과 구조의 파악이 어려움
+
+### 목표
+- 책임과 역할에 맞게 명확한 구분을 한다.
+#### 원칙
+1. Common, Controller, Model, Repository, Service, DTO 등 역할별 책임에 맞게 계층 분리
+2. 도메인 중심으로 각 단위별로 묶기
+
+### 구조 정의
+```
+ /Controllers
+ └─ /V1
+ └─ /Interfaces
+ └─ I{Domain}.cs
+ └─ {Domain}Contreoller.cs
+
+ /Services
+ └─ /V1
+ └─ /Interfaces
+ └─ I{Domain}Service.cs
+ └─ {Domain}Service.cs
+
+ /Repositories
+ └─ /V1
+ └─ /Interfaces
+ └─ I{Domain}Repository.cs
+ └─ {Domain}Repository.cs
+
+ /Models
+ └─ /Entities
+ └─ {Domain}.cs
+ └─ /DTOs
+ └─ /V1
+ └─ {Domain}Dto.cs
+
+ /Common
+ └─ /{공통기능}
+ └─ {공통기능관련}.cs
+
+ SwaggerConfigure.cs
+ Program.cs
+```
+
+---
+
+## 14일 (월)
+### 1. 회원가입 이후 동작할 기능 구현하기
+1. [ ] 학원 목록 관련 기능 추가
+
+### 2. Blazor를 활용한 Admin 페이지 생성해보기
+
diff --git a/Program.cs b/Program.cs
index c2501c8..c80148c 100644
--- a/Program.cs
+++ b/Program.cs
@@ -1,21 +1,28 @@
using Pomelo.EntityFrameworkCore;
using System.Text;
-using AcaMate.Common.Chat;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
+using Back;
+using Back.Program.Common.Auth;
+using Back.Program.Common.Auth.Interface;
+using Back.Program.Common.Chat;
+using Back.Program.Common.Data;
+using Back.Program.Common.Middleware;
+using Back.Program.Common.Model;
+using Back.Program.Models.Entities;
+using Back.Program.Repositories.V1;
+using Back.Program.Repositories.V1.Interfaces;
+using Back.Program.Services.V1;
+using Back.Program.Services.V1.Interfaces;
-using AcaMate.Common.Models;
-using AcaMate.V1.Services;
-using AcaMate.Common.Data;
-using AcaMate.V1.Controllers;
-using AcaMate.V1.Models;
var builder = WebApplication.CreateBuilder(args);
@@ -23,6 +30,8 @@ var builder = WebApplication.CreateBuilder(args);
// DB 설정부 시작
builder.Configuration.AddJsonFile("private/dbSetting.json", optional: true, reloadOnChange: true);
+
+builder.Services.AddHttpContextAccessor();
// var connectionString = builder.Configuration.GetConnectionString("MariaDbConnection");
// builder.Services.AddDbContext(options => options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
builder.Services.AddDbContext(optionsAction: (serviceProvider, options) =>
@@ -37,7 +46,7 @@ builder.Services.AddDbContext(optionsAction: (serviceProvider, opt
options.UseMySql(baseConnectionString, ServerVersion.AutoDetect(baseConnectionString));
});
-builder.Services.AddHttpContextAccessor();
+
// DB 설정부 끝
@@ -83,7 +92,7 @@ builder.Services.AddAuthentication(options =>
builder.Services.Configure(builder.Configuration.GetSection("PushFileSetting"));
// HttpClientFactory를 이용한 ApnsPushService 등록 (핸들러에 인증서 추가)
-builder.Services.AddHttpClient(client =>
+builder.Services.AddHttpClient(client =>
{
var settings = builder.Configuration.GetSection("PushFileSetting").Get();
client.BaseAddress = new Uri(settings.uri);
@@ -112,8 +121,19 @@ builder.Services.AddHostedService();
builder.Services.AddControllers();
// 여기다가 API 있는 컨트롤러들 AddScoped 하면 되는건가?
-builder.Services.AddScoped();
-builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+
+// builder.Services.AddScoped();
+builder.Services.AddScoped();
// builder.Services.AddScoped(); //
// builder.Services.AddScoped();
@@ -150,13 +170,13 @@ else
}
-
// 로컬 테스트 위한 부분 (올릴때는 꺼두기)
// builder.WebHost.UseUrls("http://0.0.0.0:5144");
///// ===== builder 설정 부 ===== /////
var app = builder.Build();
+string staticRoot;
if (app.Environment.IsDevelopment())
{
@@ -164,26 +184,50 @@ if (app.Environment.IsDevelopment())
// app.UseSwaggerUI();
app.UseCustomSwaggerUI();
app.UseDeveloperExceptionPage(); // 좀더 자세한 예외 정보 제공
+ staticRoot = Path.Combine(Directory.GetCurrentDirectory(), "publish", "debug", "wwwroot");
}
else
{
app.UseExceptionHandler("/error");
app.UseHsts();
+ staticRoot = Path.Combine(Directory.GetCurrentDirectory(), "publish", "release", "wwwroot");
}
// 로컬 테스트 위한 부분 (올릴떄는 켜두기)
app.UseHttpsRedirection();
-app.UseRouting();
-// app.MapControllers();
+//예외처리 미들웨어 부분
+app.UseMiddleware();
+// 헤더 미들웨어 부분
+app.UseMiddleware(
+ (object)new string[] { "iOS_AM_Connect_Key", "And_AM_Connect_Key", "Web_AM_Connect_Key" }
+ );
+// app.UseBlazorFrameworkFiles();
+// app.UseStaticFiles();
+
+app.UseStaticFiles(new StaticFileOptions
+{
+ FileProvider = new PhysicalFileProvider(staticRoot),
+ RequestPath = ""
+});
+
+
+
+
+app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthorization();
app.UseWebSockets();
+
app.UseEndpoints(end =>
{
- end.MapControllers();
+ ControllerEndpointRouteBuilderExtensions.MapControllers(end);
+
+ // 프론트 테스트 위한 부분
+ end.MapFallbackToFile("index.html");
+
end.MapHub("/chatHub");
});
diff --git a/Program/Common/Auth/APIHeaderMiddleware.cs b/Program/Common/Auth/APIHeaderMiddleware.cs
new file mode 100644
index 0000000..a4a26fb
--- /dev/null
+++ b/Program/Common/Auth/APIHeaderMiddleware.cs
@@ -0,0 +1,69 @@
+using Back.Program.Common.Auth.Interface;
+using Back.Program.Common.Data;
+using Microsoft.EntityFrameworkCore;
+
+namespace Back.Program.Common.Auth
+{
+ ///
+ public class APIHeaderMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly string[] _headerNames;
+ // private readonly IHeaderConfig _headerConfig;
+
+ public APIHeaderMiddleware(RequestDelegate next, string[] headerNames) //, IHeaderConfig headerConfig)
+ {
+ _next = next;
+ _headerNames = headerNames;
+ }
+
+ public async Task Invoke(HttpContext context)
+ {
+ if (context.Request.Path.Equals("/api/v1/in/app", StringComparison.OrdinalIgnoreCase))
+ {
+ await _next(context);
+ return;
+ }
+
+ // 정적 파일 요청은 미들웨어 건너뜀
+ var path = context.Request.Path.Value;
+ if (path != null && (path.StartsWith("/api")))
+ {
+ // Scoped 사용해서 값 가져오는 곳임
+ var headerConfig = context.RequestServices.GetRequiredService();
+
+ bool valid = false;
+
+ foreach (var header in _headerNames)
+ {
+ /// context.Request.Headers.TryGetValue(header, out var headerValue)
+ /// header 를 찾는데 header
+ if (context.Request.Headers.TryGetValue(header, out var headerValue) &&
+ !string.IsNullOrWhiteSpace(headerValue))
+ {
+ var keyName = await headerConfig.GetExpectedHeaderValueAsync(headerValue);
+ if (keyName != string.Empty)
+ {
+ valid = true;
+ break;
+ }
+ }
+ }
+
+ if (!valid)
+ {
+ context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+ await context.Response.WriteAsync($"Invalid header value");
+ return;
+ }
+
+ await _next(context);
+ }
+
+ {
+ await _next(context);
+ return;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Program/Common/Auth/HeaderConfigRepository.cs b/Program/Common/Auth/HeaderConfigRepository.cs
new file mode 100644
index 0000000..91a045b
--- /dev/null
+++ b/Program/Common/Auth/HeaderConfigRepository.cs
@@ -0,0 +1,27 @@
+using Back.Program.Common.Auth.Interface;
+using Back.Program.Common.Data;
+using Microsoft.EntityFrameworkCore;
+
+namespace Back.Program.Common.Auth
+{
+ ///
+ /// DB에서 헤더 키값 찾아서 그 밸류 값 빼오기 위해서 사용
+ ///
+ public class HeaderConfigRepository : IHeaderConfig
+ {
+ private readonly AppDbContext _dbContext;
+
+ public HeaderConfigRepository(AppDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task GetExpectedHeaderValueAsync(string headerValue)
+ {
+ var config = await _dbContext.APIHeader
+ .FirstOrDefaultAsync(h => h.h_value == headerValue);
+ return config?.h_key ?? string.Empty;
+ }
+
+ }
+}
diff --git a/Program/Common/Auth/Interface/IHeaderConfig.cs b/Program/Common/Auth/Interface/IHeaderConfig.cs
new file mode 100644
index 0000000..04e5587
--- /dev/null
+++ b/Program/Common/Auth/Interface/IHeaderConfig.cs
@@ -0,0 +1,7 @@
+namespace Back.Program.Common.Auth.Interface
+{
+ public interface IHeaderConfig
+ {
+ Task GetExpectedHeaderValueAsync(string headerName);
+ }
+}
diff --git a/Program/Common/Auth/JwtTokenService.cs b/Program/Common/Auth/JwtTokenService.cs
new file mode 100644
index 0000000..caa0e27
--- /dev/null
+++ b/Program/Common/Auth/JwtTokenService.cs
@@ -0,0 +1,107 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Security.Cryptography;
+using System.Text;
+using Back.Program.Common.Model;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+
+namespace Back.Program.Common.Auth
+{
+ ///
+ /// 사용자의 정보를 바탕으로 JWT 생성
+ ///
+ public class JwtTokenService
+ {
+ private readonly JwtSettings _jwtSettings;
+ private readonly ILogger _logger;
+
+ public JwtTokenService(IOptions jwtSettings, ILogger logger)
+ {
+ _jwtSettings = jwtSettings.Value;
+ _logger = logger;
+ }
+
+ public string GenerateJwtToken(string jwtKey)//, string role)
+ {
+ // 1. 클레임(Claim) 설정 - 필요에 따라 추가 정보도 포함
+ var claims = new List
+ {
+ // 토큰 주체(sub) 생성을 위해 값으로 uid를 사용함 : 토큰이 대표하는 고유 식별자
+ new Claim(JwtRegisteredClaimNames.Sub, jwtKey),
+ // 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.Now.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 new RefreshToken()
+ {
+ uid = uid,
+ refresh_token = Convert.ToBase64String(randomNumber),
+ create_Date = DateTime.UtcNow,
+ expire_date = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiryDays)
+
+ };
+ }
+
+
+ ///
+ /// 여기는 엑세스 토큰의 확인을 위한 jwt 서비스 내의 인증 메서드
+ ///
+ public async Task 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 ex)
+ {
+ _logger.LogError($"엑세스 토큰 오류: {ex.Message}");
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Program/Common/Chat/ChatHub.cs b/Program/Common/Chat/ChatHub.cs
index 9841c96..138f25c 100644
--- a/Program/Common/Chat/ChatHub.cs
+++ b/Program/Common/Chat/ChatHub.cs
@@ -1,16 +1,25 @@
+using Back.Program.Services.V1.Interfaces;
using Microsoft.AspNetCore.SignalR;
-using System.Threading.Tasks;
-namespace AcaMate.Common.Chat;
+namespace Back.Program.Common.Chat;
public class ChatHub : Hub
{
+ private readonly ILogger _logger;
+ private readonly IChatService _chatService;
+
+ public ChatHub(ILogger logger, IChatService chatService)
+ {
+ _logger = logger;
+ _chatService = chatService;
+ }
+
+
// 클라이언트에서 메시지를 보내면 모든 사용자에게 전송
public async Task SendMessage(string user, string message)
{
Console.WriteLine($"Message received: {user}: {message}");
await Clients.All.SendAsync("ReceiveMessage", user, message);
-
}
// 특정 사용자에게 메시지를 보냄
@@ -34,4 +43,15 @@ public class ChatHub : Hub
Console.WriteLine("OnDisconnectedAsync");
await base.OnDisconnectedAsync(exception);
}
+
+
+ public async Task JoinRoom(string cid)
+ {
+ await Groups.AddToGroupAsync(Context.ConnectionId, cid);
+ }
+
+ public async Task JoinGroup(string cid, string groupName)
+ {
+ await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
+ }
}
\ No newline at end of file
diff --git a/Program/Common/Data/AppDbContext.cs b/Program/Common/Data/AppDbContext.cs
index dd2a32a..92b653f 100644
--- a/Program/Common/Data/AppDbContext.cs
+++ b/Program/Common/Data/AppDbContext.cs
@@ -1,52 +1,60 @@
-using AcaMate.Common.Models;
+using Back.Program.Common.Model;
+using Back.Program.Models.Entities;
using Microsoft.EntityFrameworkCore;
-using AcaMate.V1.Models;
-using Version = AcaMate.V1.Models.Version;
+using Version = Back.Program.Models.Entities.Version;
-namespace AcaMate.Common.Data;
-//database=AcaMate;
-public class AppDbContext: DbContext
+namespace Back.Program.Common.Data
{
- public AppDbContext(DbContextOptions options) : base(options)
+ //database=AcaMate;
+ public class AppDbContext: DbContext
{
- }
+ public AppDbContext(DbContextOptions options) : base(options)
+ {
+ }
- //MARK: Program
- public DbSet Version { get; set; }
- public DbSet Academy { get; set; }
- public DbSet RefreshTokens { get; set; }
+ //MARK: API
+ public DbSet APIHeader { get; set; }
- //MARK: USER
- public DbSet Login { get; set; }
- public DbSet UserAcademy { get; set; }
- public DbSet User { get; set; }
- public DbSet Permission { get; set; }
- // public DbSet Token { get; set; }
- public DbSet Location { get; set; }
- public DbSet Contact { get; set; }
+ //MARK: Program
+ public DbSet Version { get; set; }
+ public DbSet Academy { get; set; }
+ public DbSet RefreshToken { get; set; }
- //MARK: PUSH
- public DbSet DBPayload { get; set; }
- public DbSet PushCabinet { get; set; }
+ //MARK: USER
+ public DbSet Login { get; set; }
+ public DbSet UserAcademy { get; set; }
+ public DbSet User { get; set; }
+ public DbSet Permission { get; set; }
+ // public DbSet Token { get; set; }
+ public DbSet Location { get; set; }
+ public DbSet Contact { get; set; }
+
+ //MARK: PUSH
+ public DbSet DBPayload { get; set; }
+ public DbSet PushCabinet { get; set; }
+ //MARK: CHATTING
+ // public DbSet<>
- //MARK: LOG
- public DbSet LogPush { get; set; }
- public DbSet LogUser { get; set; }
+ //MARK: LOG
+ public DbSet LogPush { get; set; }
+ public DbSet LogUser { get; set; }
+ public DbSet LogProject { get; set; }
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- modelBuilder.Entity()
- .HasKey(ua => new { ua.uid, ua.bid });
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity()
+ .HasKey(ua => new { ua.uid, ua.bid });
- // modelBuilder.Entity()
- // .HasKey(c => new { c.uid, c.bid, c.pid });
+ // modelBuilder.Entity()
+ // .HasKey(c => new { c.uid, c.bid, c.pid });
- modelBuilder.Entity()
- .HasKey(p => new { p.bid, p.pid });
+ modelBuilder.Entity()
+ .HasKey(p => new { p.bid, p.pid });
- // modelBuilder.Entity().HasNoKey();
+ // modelBuilder.Entity().HasNoKey();
+ }
}
}
\ No newline at end of file
diff --git a/Program/Common/JWTToken/JwtTokenService.cs b/Program/Common/JWTToken/JwtTokenService.cs
deleted file mode 100644
index 0cfd7b5..0000000
--- a/Program/Common/JWTToken/JwtTokenService.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-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 Microsoft.AspNetCore.Authentication;
-using Microsoft.AspNetCore.Mvc;
-
-
-using System.Security.Cryptography;
-
-namespace AcaMate.Common.Token;
-
-public class JwtTokenService
-{
- private readonly JwtSettings _jwtSettings;
-
- private readonly ILogger _logger;
-
- public JwtTokenService(IOptions jwtSettings, ILogger logger)
- {
- _jwtSettings = jwtSettings.Value;
- _logger = logger;
- }
-
- public string GenerateJwtToken(string uid)//, string role)
- {
- // 1. 클레임(Claim) 설정 - 필요에 따라 추가 정보도 포함
- var claims = new List
- {
- // 토큰 주체(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.Now.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 new RefreshToken()
- {
- uid = uid,
- refresh_token = Convert.ToBase64String(randomNumber),
- create_Date = DateTime.UtcNow,
- expire_date = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiryDays)
-
- };
- }
-
-
- ///
- /// 여기는 엑세스 토큰의 확인을 위한 jwt 서비스 내의 인증 메서드
- ///
- 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/Middleware/ExceptionMiddleware.cs b/Program/Common/Middleware/ExceptionMiddleware.cs
new file mode 100644
index 0000000..e1b1d58
--- /dev/null
+++ b/Program/Common/Middleware/ExceptionMiddleware.cs
@@ -0,0 +1,49 @@
+using System.Text.Json;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+using Back.Program.Common.Model;
+
+namespace Back.Program.Common.Middleware;
+
+public class ExceptionMiddleware
+{
+ private readonly RequestDelegate _next;
+ private readonly ILogger _logger;
+
+ public ExceptionMiddleware(RequestDelegate next, ILogger logger)
+ {
+ _next = next;
+ _logger = logger;
+ }
+
+ public async Task Invoke(HttpContext context)
+ {
+ try
+ {
+ await _next(context); // 다음 미들웨어 호출
+ }
+ catch (AcaException ex)
+ {
+ _logger.LogWarning(ex, $"예외 발생 : {ex.Message}");
+ // 400 : 이건 개발자가 직접 던지는 비즈니스 로직에 대한 예외 == 클라이언트의 오류
+ context.Response.StatusCode = ex.HttpStatus;
+ context.Response.ContentType = "application/json; charset=utf-8";
+
+ var response = APIResponse.Send(ex.Code, ex.Message, string.Empty);
+ var json = JsonSerializer.Serialize(response);
+ await context.Response.WriteAsync(json);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Unhandled Exception");
+ context.Response.StatusCode = 500;
+
+ var response = APIResponse.InternalSeverError("서버 내부 오류가 발생했습니다.");
+ var json = JsonSerializer.Serialize(response);
+
+ context.Response.ContentType = "application/json; charset=utf-8";
+ await context.Response.WriteAsync(json);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Program/Common/Model/APISetting.cs b/Program/Common/Model/APISetting.cs
new file mode 100644
index 0000000..d1d4ac3
--- /dev/null
+++ b/Program/Common/Model/APISetting.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Back.Program.Common.Model
+{
+ [Table("api_header")]
+ public class APIHeader
+ {
+ [Key]
+ public string specific_id { get; set; }
+
+ public DateTime connect_date { get; set; }
+ public string h_key { get; set; }
+ public string h_value { get; set; }
+ }
+}
+
+/*
+ h_key : h_value
+ iOS_AM_Connect_Key
+ And_AM_Connect_Key
+ Web_AM_Connect_Key
+*/
\ No newline at end of file
diff --git a/Program/Common/Model/AcaException.cs b/Program/Common/Model/AcaException.cs
new file mode 100644
index 0000000..a6d069d
--- /dev/null
+++ b/Program/Common/Model/AcaException.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+
+namespace Back.Program.Common.Model;
+
+public static class ResposeCode
+{
+ public const string Success = "000";
+ public const string InputErr = "100";
+ public const string OutputErr = "200";
+ public const string NetworkErr = "300";
+ public const string UnknownErr = "999";
+}
+public class AcaException : Exception
+{
+ public string Code { get; }
+ public int HttpStatus { get; }
+
+ public AcaException(string code, string message, int httpStatus = 400) : base(message)
+ {
+ this.Code = code;
+ this.HttpStatus = httpStatus;
+ }
+}
+
+
diff --git a/Program/Common/Model/JwtSettings.cs b/Program/Common/Model/JwtSettings.cs
index 5836c3e..ae86424 100644
--- a/Program/Common/Model/JwtSettings.cs
+++ b/Program/Common/Model/JwtSettings.cs
@@ -1,39 +1,41 @@
-using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
-namespace AcaMate.Common.Models;
-
-public class JwtSettings
+namespace Back.Program.Common.Model
{
- public string SecretKey { get; set; }
- public string Issuer { get; set; }
- public string Audience { get; set; }
- public int ExpiryMinutes { get; set; }
- public int ClockSkewMinutes { get; set; }
- public int RefreshTokenExpiryDays { get; set; }
-}
+ public class JwtSettings
+ {
+ public string SecretKey { get; set; }
+ public string Issuer { get; set; }
+ public string Audience { 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 refresh_token { get; set; }
- public DateTime create_Date { get; set; }
- public DateTime expire_date { get; set; }
+ [Table("refresh_token")]
+ public class RefreshToken
+ {
+ [Key]
+ [Required(ErrorMessage = "필수 항목 누락")]
+ public string uid { 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; }
+ // 이건 로그아웃시에 폐기 시킬예정이니 그떄 변경하는걸로 합시다.
+ public DateTime? revoke_Date { get; set; }
+ }
+
+ public class ValidateToken
+ {
+ public string token { get; set; }
+ public string refresh { get; set; }
+ public string uid { get; set; }
+ }
}
-public class ValidateToken
-{
- public string token { get; set; }
- public string refresh { get; set; }
- public string uid { get; set; }
-}
/*
"""
토큰 동작 관련
diff --git a/Program/Common/Model/Status.cs b/Program/Common/Model/Status.cs
index 25b8a3e..02db010 100644
--- a/Program/Common/Model/Status.cs
+++ b/Program/Common/Model/Status.cs
@@ -1,64 +1,74 @@
using System.Text.Json;
-namespace AcaMate.Common.Models;
-
-public class APIResponseStatus
+namespace Back.Program.Common.Model
{
- public Status status { get; set; }
- public T? data { get; set; }
+ public class APIResponseStatus
+ {
+ public Status status { get; set; }
+ public T? data { get; set; }
- public string JsonToString()
- {
- return JsonSerializer.Serialize(this);
- }
-}
-
-public class Status
-{
- public string code { get; set; }
- public string message { get; set; }
-
-}
-
-public static class APIResponse
-{
- public static APIResponseStatus Send(string code, string message, T data)
- {
- return new APIResponseStatus
+ public string JsonToString()
{
- status = new Status()
+ return JsonSerializer.Serialize(this);
+ }
+ }
+
+ public class Status
+ {
+ public string code { get; set; }
+ public string message { get; set; }
+
+ }
+
+ public static class APIResponse
+ {
+ public static APIResponseStatus Send(string code, string message, T data)
+ {
+ return new APIResponseStatus
{
- code = code,
- message = message
- },
- data = data
- };
- }
+ status = new Status()
+ {
+ code = code,
+ message = message
+ },
+ data = data
+ };
+ }
- ///
- /// 반환값 없는 API 정상 동작시
- ///
- public static APIResponseStatus Success (){
- return Send("000", "정상", "");
- }
-
- public static APIResponseStatus InvalidInputError(string? msg = null)
- {
- return Send("100", msg ?? "입력 값이 유효하지 않습니다.", "");
- }
-
- public static APIResponseStatus NotFoundError(string? msg = null)
- {
- return Send("200", msg ?? "알맞은 값을 찾을 수 없습니다.", "");
- }
-
- public static APIResponseStatus InternalSeverError(string? msg = null)
- {
- return Send("300", msg ?? "통신에 오류가 발생하였습니다.", "");
- }
+ ///
+ /// 반환값 없는 API 정상 동작시
+ ///
+ public static APIResponseStatus