1. ๊ฐ์
Spring Boot๋ก ์น ๊ฐ๋ฐ๋ง ํ๋ค๊ฐ c# ์ผ๋ก ํ ์๋..? ์๊ฒ ๋๋๋ฐ c# ์ด๋ .asp.net core ๊ด๋ จํด์ ๊ตญ๋ด ์๋ฃ๊ฐ ์ง์ง ๋์ด์ด์ด๋ฌด ์๋ค.
๊ฐ์ฒด์งํฅ, DI ๋ฑ Spring Boot์ ๊ต์ฅํ ์ ์ฌํ๋ค๊ณ ๋ ํ์ง๋ง ๋ณด๊ณ ๋ฐฐ์ธ ์๋ฃ๋ค์ด ๋๋ฌด ๋ถ์กฑํ๋ค๋๊ฑธ ๋๊ผ๋ค. ์์ผ Java ์ํ๊ณ๊ฐ ํฌ๊ตฌ๋ ๋ผ๋ ์๊ฐ์ด ๋ค์๋ค..
๊ทธ๋์ c# ๊ณต๋ถ๋ฅผ ํ๋ฉฐ ๋ฐฐ์ด ๋ด์ฉ์ ๊ณต์ ํ๊ณ ์ ๊ธ์ ์ด๋ค.
์๋๋ผ๋ฉด ํ๋ก์ ํธ ๊ตฌ์ฑํ๋ ๋ด์ฉ ๋ฐ์(?) ๋ธ๋ก๊ทธ์ ์ ๋ ์ฐ์ง ์์ง๋ง, Java, Spring Boot ๋ง ํ๋ ๋์๊ฒ c#์ ์์ ์กฐ์ฐจ ์ด๋ ค์ ๊ธฐ ๋๋ฌธ์ ์์ธํ๊ฒ ๊ธฐ๋กํ๋๋ก ํ๊ฒ ๋ค.
2. Clean Architecture๋?
ํด๋ฆฐ ์ํคํ ์ฒ๊ฐ ๋ฌด์์ธ์ง ์ ์ํ๊ธฐ์ ์์, ์ํคํ ์ฒ๋ ๋ฌด์์ธ๊ฐ?
์ผ๋จ ์ํคํ ์ฒ(Architecture)๋ ์ฐ๋ฆฌ๊ฐ ๋ง๋ค๊ณ ์ ํ๋ ์๋น์ค ํน์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ฑ์์ ๋ฐ ๊ตฌ์ฑ์์๊ฐ์ ๊ด๊ณ, ์ธ๋ถ(Client)์ ๊ตฌ์ฑ์์๊ฐ์ ๊ด๊ณ ๋ฑ์ ์ ์ํ๊ณ ์ค๋ช ํ๋ '์ค๊ณ๋' ๊ฐ์ ๊ฐ๋ ์ด๋ผ๊ณ ์ ์ํ ์ ์์ ๊ฒ ๊ฐ๋ค.
๊ทธ๋ผ ํด๋ฆฐ ์ํคํ ์ฒ๋ ๋ฌด์์ผ๊น?
"ํด๋ฆฐ ์ํคํ ์ฒ(Clean Architecture)"๋ ์ํํธ์จ์ด ๊ฐ๋ฐ์ ์ฌ์ฉ๋๋ ๋์์ธ ํจํด ์ค ํ๋๋ก, ๋ก๋ฒํธ C. ๋งํด(Robert C. Martin)์ด ์ ์ํ ์ํคํ ์ฒ ์์น์ด๋ค
ํด๋ฆฐ ์ํคํ ์ฒ์ ๊ตฌ์ฑ ์์๋ ๋ค์๊ณผ ๊ฐ๋ค.
- Adapter(Infrastructure)
- ์ธ๋ถ ์ธ๊ณ(Web, DB, UI)์ ์ ํ๋ฆฌ์ผ์ด์ ๋ด๋ถ์ ๋๋ฉ์ธ ๋ฐ ๋ก์ง ์ฌ์ด๋ฅผ ์ฐ๊ฒฐํ๋ ์ญํ .
- ํด๋ฆฐ ์ํคํ ์ฒ์ ๊ฒฝ๊ณ๋ฅผ ํ์ฑํ๋ฉฐ, ์์คํ ์ ๋ ๋ฆฝ์ฑ์ ๋ณด์ฅ
- in: Controller, Ui
- out: JpaRepository
- Application(Use Case)
- ๋น์ฆ๋์ค ๋ก์ง์ ์ง์ ์ ์ ์ ๊ณต
- ์์คํ ์ usecase๋ฅผ ์ ์ํ๊ณ ๊ตฌํํ๋ ์๋น์ค๊ฐ ์์น
- domain๊ณผ adapter์ฌ์ด์ ์ํธ์์ฉ์ ์กฐ์ ํ๊ณ ํ๋ฆ์ ์ ์ด
- Domain
- ์์คํ ์ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง์ ๊ตฌํ
- ๋น์ฆ๋์ค ๊ฐ์ฒด, ๋๋ฉ์ธ ์๋น์ค , ๋๋ฉ์ธ ์ด๋ฒคํธ ๋ฑ์ด ํฌํจ
- ์ธ๋ถ ์์์ ์์กดํ์ง ์์ผ๋ฉฐ ์์์ฑ์ ๋ณด์ฅ
3. ํด๋ฆฐ ์ํคํ ์ฒ์ ๊ธฐ๋ณธ ์๋ฆฌ
ํด๋ฆฐ ์ํคํ ์ฒ๋ ์ํํธ์จ์ด ์์คํ ์ ์ฌ๋ฌ ๊ณ์ธต์ผ๋ก ๊ตฌ๋ถํ๊ณ , ๊ฐ ๊ณ์ธต ๊ฐ์ ์์กด์ฑ์ ์๊ฒฉํ๊ฒ ๊ด๋ฆฌํ๋ ๊ฒ์ ๊ธฐ๋ณธ ์๋ฆฌ๋ก ํ๋ค.
์ด๋ '์์กด์ฑ ์ญ์ ์์น(Dependency Inversion Principle)'๊ณผ ๋ฐ์ ํ๊ฒ ์ฐ๊ด๋์ด ์๋ค.
๊ฐ์ฅ ์ค์ฌ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋น์ฆ๋์ค ๊ท์น์ ๋ด๊ณ ์๋ ์ํฐํฐ(Entity)์ ์ ์ค์ผ์ด์ค(Use Cases)๊ฐ ์์นํฉ๋๋ค. ์ด๋ค์ ์์คํ ์ ํต์ฌ ๊ธฐ๋ฅ์ ์ ์ํ๋ฉฐ, ์ธ๋ถ ์์์ ๋ณ๊ฒฝ์ ์ํฅ์ ๋ฐ์ง ์๋๋ค.
์๋ํ๋ฉด ํด๋ฆฐ ์ํคํ ์ฒ๋ ๋ด๋ถ ๊ณ์ธต์ด ์ธ๋ถ ๊ณ์ธต์ ์์กดํ์ง ์๋๋ก ์ค๊ณ๋์ด ์๊ธฐ ๋๋ฌธ์ด๋ค. ์ด๋ฅผ ํตํด ๋น์ฆ๋์ค ๋ก์ง์ ๋ ๋ฆฝ์ฑ์ ๋ณด์ฅํ๊ณ , ์์คํ ์ ์ ์ฐ์ฑ์ ๋์ผ ์ ์๋ค.
์ธ๋ถ๋ก ๊ฐ์๋ก ์ธํฐํ์ด์ค ์ด๋ํฐ, ํ๋ ์ ํ ์ด์ , ์ธํ๋ผ์คํธ๋ญ์ฒ ๋ฑ์ ๊ณ์ธต์ด ์์นํ๋ฉฐ, ์ด๋ค์ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ์ฌ์ฉ์ ์ธํฐํ์ด์ค, ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ํต์ ์ ๋ด๋นํ๋ค.
์ด๋ฌํ ๊ณ์ธต ๊ตฌ์กฐ๋ ์์คํ ์ ๊ฐ ๋ถ๋ถ์ ๋ ๋ฆฝ์ ์ผ๋ก ๊ฐ๋ฐํ๊ณ ํ ์คํธํ ์ ์๊ฒ ํ๋ฉฐ, ๋ณ๊ฒฝ ์ฌํญ์ด ํ ๋ถ๋ถ์ ๊ตญํ๋๋๋ก ํ๋ค.
ํด๋ฆฐ ์ํคํ ์ฒ๋ฅผ ์ ์ฉํ๊ธฐ ์ํด์๋ ๋ช ๊ฐ์ง ๋ฐฉ๋ฒ์ ๋ฐ๋ผ์ผ ํ๋ค.
1. ์์คํ ์ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง์ ์ค์ฌ์ผ๋ก ์ํฐํฐ์ ์ ์ค์ผ์ด์ค๋ฅผ ์ ์
2. ๊ฐ ๊ณ์ธต์ ์ฑ ์๊ณผ ์ธํฐํ์ด์ค๋ฅผ ๋ช ํํ ํ๊ณ , ๊ณ์ธต ๊ฐ์ ์์กด์ฑ์ ์ญ์ ์ํค๋ ๋ฐฉ๋ฒ์ผ๋ก ์ค๊ณํ๋ค. ์ด๋ ์ธํฐํ์ด์ค์ ์ถ์ํ๋ฅผ ํตํด ๊ตฌํํ๋ค.
3. ์์คํ ์ ์ธ๋ถ ์์์์ ํต์ ์ ๋ด๋นํ๋ ์ด๋ํฐ์ ์ธํ๋ผ์คํธ๋ญ์ฒ๋ฅผ ๊ตฌํํ๋ค. ์ด ๊ณผ์ ์์ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ๋ ์์ํฌ์ ์์กด์ฑ์ ์ต์ํํ๊ณ , ์์คํ ์ ํต์ฌ ๋ก์ง๊ณผ ๊ฒฐํฉ๋๋ฅผ ๋ฎ์ถ๋ค.
4. ํ ์คํธ๋ฅผ ํตํด ๊ฐ ๊ณ์ธต์ ๋ ๋ฆฝ์ฑ๊ณผ ์์คํ ์ ์ ์ฒด์ ์ธ ์์ ์ฑ์ ๊ฒ์ฆํ๋ค. ํด๋ฆฐ ์ํคํ ์ฒ๋ ํ ์คํธ ์ฉ์ด์ฑ์ ๋์ด๋ ๊ตฌ์กฐ๋ฅผ ์ ๊ณตํ๋ค.
4. ํ๋ก์ ํธ ๊ตฌ์ฑ ๋ฐ ํ์ ๊ธฐ๋ฅ ๊ฐ๋ฐ
๋ค์ด๊ฐ๊ธฐ ์์ ์์ฑ๋ ํ๋ก์ ํธ ์ ์ฒด ํจํค์ง ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ๋ค.
๋ฃจํธ ํ๋ก์ ํธ๋ ๋น ํ๋ก์ ํธ๋ก ์์ฑํด์ค๋ค.
๊ทธ๋ฆฌ๊ณ ๋๋จธ์ง Application, Domain, Infrastructure, WebAPI ๋ ํด๋์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ(C#)์ผ๋ก ์์ฑํด์ค๋ค.
์์ฑ ํ ๋ชจ์ต์ ์๋์ ๊ฐ๋ค.
Clean Architecture์ ๋ฐ๋ผ ๊ฐ ํ๋ก์ ํธ์ dependency๋ฅผ ์ถ๊ฐํด์ค์ผ ํ๋ค.
application -> domain
infrastrucrue -> application
WebAPI -> application, Infrastructure
์ผ๋ก ์ค์ ํด์ค๋ค. Domain์ ๋ค๋ฅธ ๊ณ์ธต์ ์์กด์ฑ์ ๊ฐ์ ธ์๋ ์ ๋๋ค.
์ฐธ๊ณ ๋ก ์์กด์ฑ ์ถ๊ฐ๋ ์ข ์์ฑ ์ฐํด๋ฆญ ํ ์ถ๊ฐํด์ฃผ๋ฉด ๋๋ค.
InfraStructure ํ๋ก์ ํธ => NuGet ์์ efcore์ efcore tools, SqlServer ๋ฅผ ์ค์นํด์ค๋ค.
Entity Framework Core์ ํต์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ํธ ์์ฉํ๋ ๋ฐ ์ค์ํ ์ญํ ์ ํ๋ค. Java Spring ์์ JPA์ ๊ฐ์ ์ญํ ์ด๋ค.
์ถ๊ฐ๋ก ๋น๋ฐ๋ฒํธ ์ธ์ฝ๋ฉ์ ์ํ BCrtpy๋ ์ค์นํด์ฃผ์.
๊ทธ๋ฆฌ๊ณ efcore tools๋ WebAPI์๋ ์ค์นํด์ฃผ์.
์์ ๋ก ์ธ ์ํฐํฐ ํด๋์ค๋ฅผ Domain ํ๋ก์ ํธ ์์ ๋ง๋ค์ด์ค๋ค.
- ApplicationUser.cs
namespace Domain.Entities
{
public class ApplicationUser
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
public string? Password { get; set; }
}
}
infrastructure ์์ ๋ค์ ๋ ํด๋์ค๋ฅผ ๋ง๋ค์ด์ค๋ค.
- AppDbContext.cs
์ด ํด๋์ค๋ Entity Framework Core์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ํธ ์์ฉ์ ๊ด๋ฆฌํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปจํ ์คํธ(Database Context) ์ด๋ค. ์ด ํด๋์ค๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ์ ์ค์ ํ๊ณ , ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ๊ณผ ๋งคํ๋๋ ์ํฐํฐ(๋ชจ๋ธ)๋ฅผ ์ ์ํ๋ ์ญํ ์ ํ๋ค.
using Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace infrastructure.Data
{
internal class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<ApplicationUser> Users { get; set; } // ํ
์ด๋ธ ๋ช
}
}
- ServiceContainer.cs
์ด ํด๋์ค๋ ์์กด์ฑ ์ฃผ์ (Dependency Injection) ์ค์ ์ ๋ด๋นํ๋ ์๋น์ค ๊ตฌ์ฑ ํด๋์ค
using infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace infrastructure.DependencyInjection
{
public static class ServiceContainer
{
// IServiceCollection์ ์์กด์ฑ ์ฃผ์
๊ตฌ์ฑ
public static IServiceCollection InfrastructureServices(this IServiceCollection services, IConfiguration configuration)
{
// AddDbContext ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ AppDbContext๋ฅผ DI ์ปจํ
์ด๋์ ๋ฑ๋ก
services.AddDbContext<AppDbContext>(options =>
// SQL Server ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๋๋ก ์ค์
options.UseSqlServer(
configuration.GetConnectionString("Default"),
b => b.MigrationsAssembly(typeof(ServiceContainer).Assembly.FullName)
),
ServiceLifetime.Scoped // // DbContext์ ์๋ช
์ฃผ๊ธฐ๋ฅผ Scoped๋ก ์ค์ (์์ฒญ๋ง๋ค ์ธ์คํด์ค๋ฅผ ์๋ก ์์ฑ)
);
// ์์กด์ฑ ๋ฐํ
return services;
}
}
}
๊ทธ๋ฆฌ๊ณ appsettings.json์ ConnectionStrings ์ค์ ์ ํด์ค๋ค.
{
// ์ถ๊ฐ
// ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ๋ฌธ์์ด ์ค์
// Server=local : ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ฒ ์ฃผ์ ์ง์
// Database=[] ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๋ฆ ์ง์
// Trusted_Connection=true : true๋ก ๋์ด ์์ผ๋ฉด, ์ฌ์ฉ์ ์ด๋ฆ๊ณผ ๋น๋ฐ๋ฒํธ ๋์ Windows ์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ฌ์ฉํ์ฌ ์ฐ๊ฒฐ
// Trust Server Certificate=true : SSL ์ธ์ฆ์๋ฅผ ์ฌ์ฉํ ๋ ์๋ฒ ์ธ์ฆ์๋ฅผ ์ ๋ขฐํ ๊ฒ์ธ์ง
"ConnectionStrings": {
"Default": "Server=(local); Database=JWTWithCleanArchDB; Trusted_Connection=true; Trust Server Certificate=true;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
์๋ฃจ์ ๋น๋ ํ ํจํค์ง ๊ด๋ฆฌ์ ์ฝ์์ Infrastructure ํ๋ก์ ํธ ์ ํ ํ ์๋ ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅ
add-migration First // First ๋ผ๋ ์ด๋ฆ์ผ๋ก Migration ์์ฑ
update-database
(๋ง์ด๊ทธ๋ ์ด์ (Migration)์ ์์ฑํ๋ค๋ ๊ฒ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์คํค๋ง(๊ตฌ์กฐ) ๋ณ๊ฒฝ ์ฌํญ์ ์ฝ๋๋ก ๊ธฐ๋กํ๋ ๊ฒ์ ์๋ฏธ)
์ฐธ๊ณ ๋ก ํจํค์ง ๊ด๋ฆฌ์ ์ฝ์์ ๋ณด๊ธฐ => ๋ค๋ฅธ ์ฐฝ => ํจํค์ง ๊ด๋ฆฌ์ ์ฝ์ ์์ ํ์ฑํ ์ํฌ ์ ์๋ค.
database์ ์ ๋ฐ์ดํธ ํ๋ฉด ์๋์ ๊ฐ์ ๋ก๊ทธ๋ค์ ๋ณผ ์ ์๋ค.
ํ์ ๊ธฐ๋ฅ ๊ฐ๋ฐ
Application Layer์ Dto์ UseCase๋ฅผ ์์ฑํด์คฌ๋ค.
IUser UseCase๋ฅผ ๊ตฌํํ๋ UserRepo๋ Infrastructure.Repo ์์ ์์ฑํด์ฃผ์๋ค.
์์กด์ฑ ์ฃผ์ ์ ์ํด ServiceContainer์ JWT ๊ด๋ จ ์ค์ ์ ํด์ฃผ์๋ค.
using Application.Contract;
using infrastructure.Data;
using infrastructure.Repo;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace infrastructure.DependencyInjection
{
public static class ServiceContainer
{
// IServiceCollection์ ์์กด์ฑ ์ฃผ์
๊ตฌ์ฑ
public static IServiceCollection InfrastructureServices(this IServiceCollection services, IConfiguration configuration)
{
// AddDbContext ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ AppDbContext๋ฅผ DI ์ปจํ
์ด๋์ ๋ฑ๋ก
services.AddDbContext<AppDbContext>(options =>
// SQL Server ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๋๋ก ์ค์
options.UseSqlServer(
configuration.GetConnectionString("Default"),
b => b.MigrationsAssembly(typeof(ServiceContainer).Assembly.FullName)
),
ServiceLifetime.Scoped // // DbContext์ ์๋ช
์ฃผ๊ธฐ๋ฅผ Scoped๋ก ์ค์ (์์ฒญ๋ง๋ค ์ธ์คํด์ค๋ฅผ ์๋ก ์์ฑ)
);
// JWT
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, // ํ ํฐ์ ๋ฐ๊ธ์(issuer)๋ฅผ ๊ฒ์ฆ
ValidateAudience = true, // ํ ํฐ์ ์์ ์(audience)๋ฅผ ๊ฒ์ฆ
ValidateLifetime = true, // ํ ํฐ์ ์ ํจ ๊ธฐ๊ฐ์ ๊ฒ์ฆ
ValidateIssuerSigningKey = true, // ํ ํฐ์ ์๋ช
ํค๋ฅผ ๊ฒ์ฆ
ValidIssuer = configuration["Jwt:Issuer"], // ๋ฐ๊ธ์์ ์ ํจ์ฑ์ ๊ฒ์ฆํ ๋ ์ฌ์ฉํ ๊ฐ
ValidAudience = configuration["Jwt:Audience"], // ์์ ์์ ์ ํจ์ฑ์ ๊ฒ์ฆํ ๋ ์ฌ์ฉํ ๊ฐ
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]!)) // ํ ํฐ์ ๊ฒ์ฆํ ๋ ์ฌ์ฉํ ์๋ช
ํค
};
});
services.AddScoped<IUser, UserRepo>(); // IUser ์ธํฐํ์ด์ค์ UserRepo ํด๋์ค ๊ฐ์ ์์กด์ฑ์ DI ์ปจํ
์ด๋์ ๋ฑ๋ก
// ์์กด์ฑ ๋ฐํ
return services;
}
}
}
Repo์๋ ๋ก๊ทธ์ธ ์ ์ด๋ฉ์ผ, ์์ด๋ ๊ฒ์ฆ ํ token์ ์์ฑํด ๋ฐํํ๋ ๋ก์ง๊ณผ, ํ์ ๋ฑ๋ก ๋ก์ง์ ์์ฑํด์ฃผ์๋ค.
using Application.Contract;
using Application.DTOs;
using Domain.Entities;
using infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace infrastructure.Repo
{
internal class UserRepo : IUser
{
private readonly AppDbContext appDbContext;
private readonly IConfiguration configuration;
public UserRepo(AppDbContext appDbContext, IConfiguration configuration)
{
this.appDbContext = appDbContext;
this.configuration = configuration;
}
public async Task<LoginResponse> LoginUserAsync(LoginDTO loginDTO)
{
var getUser = await FindUserByEmail(loginDTO.Email!); // null์ด ์๋์ ๋ณด์ฅํ๋ค๋ ์๋ฏธ์์ ! ๋ฅผ ๋ถ์
if (getUser == null) return new LoginResponse(false, "User not found");
bool checkPassword = BCrypt.Net.BCrypt.Verify(loginDTO.Password, getUser.Password);
if (checkPassword)
{
return new LoginResponse(true, "Login successfully", GenerateJWTToken(getUser));
}
else
{
return new LoginResponse(false, "Invalid credentials");
}
}
private string GenerateJWTToken(ApplicationUser getUser)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]!));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var userClaims = new[]
{
new Claim(ClaimTypes.NameIdentifier, getUser.Id.ToString()),
new Claim(ClaimTypes.Name, getUser.Name!),
new Claim(ClaimTypes.Email, getUser.Email!)
};
var token = new JwtSecurityToken(
issuer: configuration["Jwt:Issuer"],
audience: configuration["Jwt:Audience"],
claims: userClaims,
expires: DateTime.Now.AddDays(5),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
private async Task<ApplicationUser> FindUserByEmail(string email) =>
await appDbContext.Users.FirstOrDefaultAsync(u => u.Email == email);
public async Task<RegistrationResponse> RegisterUserAsync(RegisterUserDTO registerUserDTO)
{
var getUser = await FindUserByEmail(registerUserDTO.Email!);
if (getUser != null)
return new RegistrationResponse(false, "User Already exist");
appDbContext.Users.Add(new ApplicationUser()
{
Name = registerUserDTO.Name,
Email = registerUserDTO.Email,
Password = BCrypt.Net.BCrypt.HashPassword(registerUserDTO.Password)
});
await appDbContext.SaveChangesAsync();
return new RegistrationResponse(true, "Registration completed");
}
}
}
๋ง์ง๋ง์ผ๋ก ์ปจํธ๋กค๋ฌ๋ฅผ WebAPI.Controllers ํจํค์ง์ ๋ง๋ค๊ณ , UseCase์ธ IUser๋ฅผ ์ฃผ์ ๋ฐ์์ฃผ์.
using Application.Contract;
using Application.DTOs;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace WebAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class User : ControllerBase
{
private readonly IUser user;
public User(IUser user)
{
this.user = user;
}
[HttpPost("login")]
public async Task<ActionResult<LoginResponse>> LogUserIn(LoginDTO loginDTO)
{
var result = await user.LoginUserAsync(loginDTO);
return Ok(result);
}
[HttpPost("register")]
public async Task<ActionResult<LoginResponse>> RegisterUser(RegisterUserDTO registerUserDTO)
{
var result = await user.RegisterUserAsync(registerUserDTO);
return Ok(result);
}
}
}
Swagger์์ ํ์๊ฐ์ ํ ๋ก๊ทธ์ธ ํด๋ณด๋ฉด ํ ํฐ์ด ์ ๋์จ๋ค.
Swagger์์ ํ ํฐ์ ํค๋์ ๋ด์ ์์ฒญํ ์ ์๋๋ก ํ๊ธฐ ์ํด Program.cs์ ์๋ ์ค์ ์ ์ถ๊ฐํด์ค๋ค.
builder.Services.AddSwaggerGen(swagger =>
{
swagger.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "ASP.NET 8 Web API",
Description = "Authentication with JWT"
});
// To Enable authorization using Swagger (JWT)
swagger.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header
});
swagger.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
Authorize ํ ํ ์ ์ฒด ํ์์ ๋ฐํํ๋ API๋ฅผ ์๋์ ๊ฐ์ด ๋ง๋ค์ด์ฃผ๊ณ ํ ์คํธํด๋ณด๋ฉด ํ ํฐ์ด ์์ ๋ 401 ์๋ฌ๋ฅผ ๋ฐํํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
[HttpGet("list")]
[Authorize]
public async Task<ActionResult<List<UserResponse>>> GetUserList()
{
return Ok(user.GetUserList());
}
- ํ ํฐ ์์ด ์์ฒญ
- ํ ํฐ ๋ฃ์ด์ ์์ฒญ
์ฌ๊ธฐ์ ์ฝ๊ฐ์ ์ฝ์ง์ ํ๋๋ฐ, Swagger๋ฅผ ์จ๋ณธ ์ฌ๋์ด๋ผ๋ฉด ์๋ ์ฌ์ง์ Authorize ๋ฒํผ์ JWT ํ ํฐ์ ๋ฃ์ด๋ณธ ๊ฒฝํ์ด ์์ ๊ฒ์ด๋ค.
๋ณดํต "Bearer " Prefix์์ด ๋ฃ์๋๋ฐ c#์์ ๋ฐ๋ก ์ถ๊ฐํด์ฃผ์ง ์์์ ์์กด์ฑ ์ฃผ์ ํ๋ InfrastructureServices ํด๋์ค์ Event๋ฅผ ๊ฑธ์ด์ฃผ์๋ค.
ํท๊ฐ๋ฆด๊น๋ด ์ ์ฒด ์ฝ๋๋ฅผ ์ฒจ๋ถํด๋๋ค.
using Application.Contract;
using infrastructure.Data;
using infrastructure.Repo;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace infrastructure.DependencyInjection
{
public static class ServiceContainer
{
// IServiceCollection์ ์์กด์ฑ ์ฃผ์
๊ตฌ์ฑ
public static IServiceCollection InfrastructureServices(this IServiceCollection services, IConfiguration configuration)
{
// AddDbContext ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ AppDbContext๋ฅผ DI ์ปจํ
์ด๋์ ๋ฑ๋ก
services.AddDbContext<AppDbContext>(options =>
// SQL Server ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๋๋ก ์ค์
options.UseSqlServer(
configuration.GetConnectionString("Default"),
b => b.MigrationsAssembly(typeof(ServiceContainer).Assembly.FullName)
),
ServiceLifetime.Scoped // // DbContext์ ์๋ช
์ฃผ๊ธฐ๋ฅผ Scoped๋ก ์ค์ (์์ฒญ๋ง๋ค ์ธ์คํด์ค๋ฅผ ์๋ก ์์ฑ)
);
// JWT Authentication ์ถ๊ฐ
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, // ํ ํฐ์ ๋ฐ๊ธ์(issuer)๋ฅผ ๊ฒ์ฆ
ValidateAudience = true, // ํ ํฐ์ ์์ ์(audience)๋ฅผ ๊ฒ์ฆ
ValidateLifetime = true, // ํ ํฐ์ ์ ํจ ๊ธฐ๊ฐ์ ๊ฒ์ฆ
ValidateIssuerSigningKey = true, // ํ ํฐ์ ์๋ช
ํค๋ฅผ ๊ฒ์ฆ
ValidIssuer = configuration["Jwt:Issuer"], // ๋ฐ๊ธ์์ ์ ํจ์ฑ์ ๊ฒ์ฆํ ๋ ์ฌ์ฉํ ๊ฐ
ValidAudience = configuration["Jwt:Audience"], // ์์ ์์ ์ ํจ์ฑ์ ๊ฒ์ฆํ ๋ ์ฌ์ฉํ ๊ฐ
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]!)) // ํ ํฐ์ ๊ฒ์ฆํ ๋ ์ฌ์ฉํ ์๋ช
ํค
};
// Bearer Prefix ์์ด ์์ฒญ์์ ๋ ์ถ๊ฐ
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var token = context.Request.Headers["Authorization"].FirstOrDefault();
if (!string.IsNullOrEmpty(token) && !token.StartsWith("Bearer "))
{
context.Request.Headers["Authorization"] = $"Bearer {token}";
}
return Task.CompletedTask;
}
};
});
services.AddScoped<IUser, UserRepo>(); // IUser ์ธํฐํ์ด์ค์ UserRepo ํด๋์ค ๊ฐ์ ์์กด์ฑ์ DI ์ปจํ
์ด๋์ ๋ฑ๋ก
// ์์กด์ฑ ๋ฐํ
return services;
}
}
}