ASP.NET CORE JWT Clean Architecture
참고 사이트:
https://www.youtube.com/watch?v=aX0a2Wajvtc
- clean architecture 은 Robert c Martin 으로 부터 소개된 소프트웨어 디자인 철학 입니다.
1. 아키텍처 4개 구성요소
1) implementation layer (구현 계층)
- entities 또는 domain
2) application layer
3) presentation layer
4) dinfrastructure layer
<프로젝트 생성>
1. Blank Solution 생성
2. 3개 클래스 추가
1) Class Library 추가 : domain , application , Infrastructure
- 프로젝트 우클릭 -> 새 프로젝트 추가
3. ASP.NET Core Web API 추가
- web API 를 호출합니다.
4. 각 클래스 참조
1) Infrastructure 프로젝트에 Application 프로젝트 참조
2) Application 프로젝트에 Domain 프로젝트 참조
5. EF tools EF SQL Server
Nuget Pakage 다운로드
Infrastucture 프로젝트에
1. Microsoft.EntityFrameworkCore
2. Microsoft.EntitiyFrameworkCore.Tools
3. Microsoft.EntityFrameworkCore.SqlServer
4. BCrypt.Net.Next (https://github.com/BcryptNet/bcrypt.net/blob/main/readme.md) nuget 라이브러리 라이센스 / 공지 관련 링크
WebAPI 프로젝트에
1. Microsoft.EntitiyFrameworkCore.Tools
6. Domain에 Entites 폴더 생성 후 ApplicationUser.cs 생성
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Application.Entites
{
internal class ApplicationUser
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
public string? Password { get; set; }
}
}
7. Infrastructure 에 Data 폴더 생성 후 AppDbcontext.cs 생성
using Domain.Entites;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Client;
namespace Infrastructure.Data
{
internal class AppDbcontext : DbContext
{
public AppDbcontext(DbContextOptions options) : base(options)
{
}
public DbSet<ApplicationUser> Users { get; set; }
}
}
8. appsettings.json 에서 connection string 추가
{
"ConnectionStrings": {
"Default": "Data Source=172.30.1.56;Initial Catalog=rararete;User ID=sa;Password=password;Trust Server Certificate=True"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
9. Infrastructure 프로젝트에 DependencyInjection 폴더 생성 후 ServiceContainer.cs
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
{
public static IServiceCollection InfrastructureService(this IServiceCollection services,
IConfiguration configuration)
{
services.AddDbContext<AppDbcontext>(options =>
options.UseSqlServer(configuration.GetConnectionString("Default"),
b => b.MigrationsAssembly(typeof(ServiceContainer).Assembly.FullName)),
ServiceLifetime.Scoped);
return services;
}
}
}
10. WebAPI 에 Infrastructure, Application 프로젝트 의존성 등록 및 Program.cs 에 Service 등록
using Infrastructure.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.InfrastructureService(builder.Configuration); // 19분57초
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
11. Application 프로젝트에 DTOs 폴더 생성 후 LoginDTO.cs , RegisterUserDTO.cs 생성
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Application.DTOs
{
public record RegisterUserDTO(string? Name, string? Email, string? Password );
}
* record 한정자 사용
: 데이터를 캡슐화하는 내장된 기능 제공
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Application.DTOs
{
public class LoginDTO
{
[Required, EmailAddress]
public string? Email { get; set; } = string.Empty;
[Required]
public string? Password { get; set; } = string.Empty;
}
}
12. Application 프로젝트에 Contracts 폴더 생성 후 IUser 인터페이스 생성 / 메소드 구현
using Application.Contracts;
using Application.DTOs;
using Domain.Entites;
using Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization.Metadata;
using System.Threading.Tasks;
namespace Infrastructure.Repo
{
internal class UserRepo : IUser
{
private readonly AppDbcontext appDbContext;
public UserRepo(AppDbcontext appDbContext)
{
this.appDbContext = appDbContext;
}
public async Task<LoginResponse> LoginUserAsync(LoginDTO loginDTO)
{
var getUser = await FindUserByEmail(loginDTO.Email);
if (getUser == null) return new LoginResponse(false, "User not found, sorry");
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 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, "Uaer 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");
}
}
}
- JWT bear nuget 다운로드
13. ServiceContainer.cs 에 jwt 토큰 생성하는 서비스 추가
using Application.Contracts;
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
{
public static IServiceCollection InfrastructureServices(this IServiceCollection services,
IConfiguration configuration)
{
services.AddDbContext<AppDbcontext>(options =>
options.UseSqlServer(configuration.GetConnectionString("Default"),
b => b.MigrationsAssembly(typeof(ServiceContainer).Assembly.FullName)),
ServiceLifetime.Scoped);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = configuration["Jwt:Isuer"],
ValidAudience = configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(configuration["Jwt:key"]!))
};
});
services.AddScoped<IUser, UserRepo>();
return services;
}
}
}
- Program.cs 에 app.UseAuthentication() 추가해서 먼저 사용할수 있게 함.
14. appsetting.json 에 Jwt key 추가
15. UserRepo.cs 에 토큰 생성 로직 추가
private string GenerateJWTToken(ApplicationUser user)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var userClaims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Name!),
new Claim(ClaimTypes.Email, user.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);
}
16. WebAPI 프로젝트의 Controllers 폴더에 User.cs 파일 생성
- 로그인, 사용자 등록 api 작성
using Application.Contracts;
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);
}
}
}
1. JWT Web Token 3개 파트(점으로 구분)로 구성되어 있다.
1) Header : 2개의 부분으로 구성. 첫번째는 jwt의 유형이고 두번째는 SHA256 이나 RSA 같은 암호화 알고리즘
2) Payload : iss(issuer), exp (유효일자) , sub(주제) 등을 포함/ public , private 나 등록된 유형으로 나뉜다.
3) Signature (서명) : 서명은 인코딩된 헤더, 인코딩된 페이로드, 헤더에 지정된 알고리즘을 가져와 생성. 서명은 메세지가 경로를 따라 변경되지 않았는지 확인하는 토큰이 개인키로 서명되었는지 확인하는데 사용. 서명을 통해 보낸 사람의 신원 확인.