Asp.net webapi와 Angular, Sqlite를 이용한 Jwt Login, Todo 만들기. #1
유튜브 : https://youtu.be/MPINGd-SfQs
GitHub : https://github.com/tigersarang/AngularAspnetJwtTodo
1. asp.net 생성
dotnet new webapi -controllers -n TodoWeb
2. nuget package 설치
3. Models, DbContext 생성
namespace TodoWeb.Models.Datas
{
public class TodoCustomer
{
public int Id { get; set; }
public required string UserName { get; set; }
public required string Email { get; set; }
public byte[]? PasswordHash { get; set; }
public byte[]? PasswordSalt { get; set; }
public IEnumerable<TodoItem>? TodoItems { get; set; }
}
}
namespace TodoWeb.Models.Datas
{
public class TodoItem
{
public int Id { get; set; }
public required string Title { get; set; }
public required string Description { get; set; }
public bool IsCompleted { get; set; }
public required TodoCustomer TodoCustomer { get; set; }
public required int TodoCustomerId { get; set; }
}
}
namespace TodoWeb.Models.Dtos
{
public class TodoCustomerDto
{
public int Id { get; set; }
public string UserName { get; set; } = default!;
public string? Email { get; set; } = default!;
public string? Password { get; set; }
public string? Token { get; set; }
public List<TodoItemDto> TodoItems { get; set; } = new();
}
}
namespace TodoWeb.Models.Dtos
{
public class TodoItemDto
{
public int Id { get; set; }
public string Title { get; set; } = default!;
public string Description { get; set; } = default!;
public bool IsCompleted { get; set; }
public int TodoCustomerId { get; set; }
}
}
namespace TodoWeb.Models.Datas
{
public class TodoDbContext(DbContextOptions options) : DbContext(options)
{
public DbSet<TodoCustomer> Customers { get; set; }
public DbSet<TodoItem> Items { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TodoItem>()
.HasOne(ti => ti.TodoCustomer)
.WithMany(tc => tc.TodoItems)
.HasForeignKey(ti => ti.TodoCustomerId)
.OnDelete(DeleteBehavior.Cascade)
;
base.OnModelCreating(modelBuilder);
}
}
}
4. config 설정(appsettings.Development.json)
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"conn" : "Data Source = todo.db"
},
"TokenKey": "asdfjksldfjkweuiroweudsf23423098492038DSFSADFSADFDS@!#$@!$#@sdfjsdakflk#455-0-weoisdkdfmndfsajk#@%DFDFXREdsf@!#$@!$#@sdfjsdakflk#455-0-weoisdkdfmndfsajk#@%DFDFXREdsf"
}
5. AutoMapper 설정
namespace TodoWeb.Helpers
{
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
CreateMap<TodoItem, TodoItemDto>().ReverseMap();
CreateMap<TodoCustomer, TodoCustomerDto>().ReverseMap();
}
}
}
6. Service 생성
namespace TodoWeb.Services
{
public interface ITokenRepository
{
string CreateToken(TodoCustomer todoCustomer);
}
}
namespace TodoWeb.Services
{
public class TokenService(IConfiguration configuration) : ITokenRepository
{
public string CreateToken(TodoCustomer todoCustomer)
{
var tokenKey = configuration["TokenKey"];
if (tokenKey.Length < 64) throw new ArgumentException("Token key must be at least 64 characters long.");
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenKey));
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, todoCustomer.Id.ToString()),
new Claim(ClaimTypes.Email, todoCustomer.Email),
new Claim(ClaimTypes.Name, todoCustomer.UserName)
};
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(7),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
}
namespace TodoWeb.Services
{
public interface ITodoCustomerRepository
{
Task<TodoCustomerDto> RegisterUser(TodoCustomer customer);
Task<TodoCustomerDto> LoginUser(TodoCustomerDto todoCustomerDto);
}
}
namespace TodoWeb.Services
{
public class TodoCustomerService(TodoDbContext _todoDbContext, ITokenRepository tokenRepository, IMapper mapper) : ITodoCustomerRepository
{
public async Task<TodoCustomerDto> LoginUser(TodoCustomerDto todoCustomerDto)
{
var customer = await _todoDbContext.Customers.FirstOrDefaultAsync(c => c.UserName == todoCustomerDto.UserName);
if (customer == null) return null;
using var hmac = new HMACSHA512(customer.PasswordSalt);
var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(todoCustomerDto.Password));
if (computedHash.SequenceEqual(customer.PasswordHash))
{
TodoCustomerDto customerDto = mapper.Map<TodoCustomerDto>(customer);
customerDto.Token = tokenRepository.CreateToken(customer);
return customerDto;
}
return null;
}
public async Task<TodoCustomerDto> RegisterUser(TodoCustomer customer)
{
_todoDbContext.Entry(customer).State = EntityState.Added;
await _todoDbContext.SaveChangesAsync();
return mapper.Map<TodoCustomerDto>(customer);
}
}
}
namespace TodoWeb.Services
{
public interface ITodoRepository
{
Task<IEnumerable<TodoItem>> GetAll();
Task<TodoItem> GetDodo(int id);
Task<TodoItem> AddTodo(TodoItem todoItem);
Task<TodoItem> UpdateTodo(TodoItem todoItem);
Task<bool> DeleteTodo(int id);
}
}
namespace TodoWeb.Services
{
public class TodoService(TodoDbContext _todoDbContext) : ITodoRepository
{
public async Task<TodoItem> AddTodo(TodoItem todoItem)
{
_todoDbContext.Items.Add(todoItem);
await _todoDbContext.SaveChangesAsync();
return todoItem;
}
public async Task<bool> DeleteTodo(int id)
{
var todoItem = await _todoDbContext.Items.FirstOrDefaultAsync(x => x.Id == id);
if (todoItem == null) return false;
_todoDbContext.Items.Remove(todoItem);
await _todoDbContext.SaveChangesAsync();
return true;
}
public async Task<IEnumerable<TodoItem>> GetAll()
{
return await _todoDbContext.Items.ToListAsync();
}
public async Task<TodoItem> GetDodo(int id)
{
return await _todoDbContext.Items.FirstOrDefaultAsync(x => x.Id == id);
}
public async Task<TodoItem> UpdateTodo(TodoItem todoItem)
{
_todoDbContext.Items.Update(todoItem);
await _todoDbContext.SaveChangesAsync();
return todoItem;
}
}
}
7. program.cs 수정
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using TodoWeb.Models.Datas;
using TodoWeb.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddDbContext<TodoDbContext>(options =>
{
options.UseSqlite(builder.Configuration.GetConnectionString("conn"));
});
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
var tokenKey = builder.Configuration["TokenKey"] ?? throw new Exception("TokenKey Error");
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenKey)),
ValidateAudience = false,
ValidateIssuer = false
};
});
builder.Services.AddCors();
builder.Services.AddScoped<ITodoCustomerRepository, TodoCustomerService>();
builder.Services.AddScoped<ITokenRepository, TokenService>();
builder.Services.AddScoped<ITodoRepository, TodoService>();
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Todo API",
Version = "v1",
Description = "Todo Api"
});
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT",
Description = "Description"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod().AllowCredentials().WithOrigins("http://localhost:4200"));
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
8. controller 설정
namespace TodoWeb.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class CustomerController(IMapper mapper, ITodoCustomerRepository todoCustomerRepository) : ControllerBase
{
[HttpPost("register")]
public async Task<ActionResult<TodoCustomerDto>> Register(TodoCustomerDto todoCustomerDto)
{
if (todoCustomerDto == null) return BadRequest("Invalid User Info");
var customer = mapper.Map<TodoCustomer>(todoCustomerDto);
using var hmac = new HMACSHA512();
customer.PasswordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(todoCustomerDto.Password));
customer.PasswordSalt = hmac.Key;
var result = await todoCustomerRepository.RegisterUser(customer);
if (result == null) return StatusCode(500, "Error registering user");
return mapper.Map<TodoCustomerDto>(result);
}
[HttpPost("login")]
public async Task<ActionResult<TodoCustomerDto>> LoginUser(TodoCustomerDto todoCustomerDto)
{
if (todoCustomerDto == null) return BadRequest("invalid user");
var result = await todoCustomerRepository.LoginUser(todoCustomerDto);
if (result == null) return Unauthorized("login failed");
return result;
}
}
}
namespace TodoWeb.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class TodoController(ITodoRepository todoService, IMapper mapper) : ControllerBase
{
[HttpPost("add")]
public async Task<ActionResult<TodoItemDto>> AddTodoItem(TodoItemDto todoItemDto)
{
if (todoItemDto == null) return BadRequest("todoitem is null.");
var todoItem = mapper.Map<TodoItem>(todoItemDto);
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim == null) return Unauthorized(" user error");
todoItem.TodoCustomerId = int.Parse(userIdClaim.Value);
var result = await todoService.AddTodo(todoItem);
if (result == null)
{
return BadRequest("todo 저장 실패.");
}
return Ok(mapper.Map<TodoItemDto>(todoItem));
}
[HttpGet("all")]
public async Task<ActionResult<List<TodoItemDto>>> GetTodo()
{
var todos = await todoService.GetAll();
return Ok(mapper.Map<List<TodoItemDto>>(todos));
}
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDto>> Get(int id)
{
var todo = await todoService.GetDodo(id);
if (todo == null)
{
return NotFound("Todo not found");
}
return Ok(mapper.Map<TodoItemDto>(todo));
}
[HttpPut("update")]
public async Task<ActionResult<TodoItemDto>> Update(TodoItemDto todoItemDto)
{
if (todoItemDto == null) return BadRequest("Invalid Todo Item");
TodoItem todoItem = mapper.Map<TodoItem>(todoItemDto);
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim == null) return Unauthorized("User Error");
todoItem.TodoCustomerId = int.Parse(userIdClaim.Value);
var result = await todoService.UpdateTodo(todoItem);
return mapper.Map<TodoItemDto>(result);
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItemAsync(int id)
{
var isDeleted = await todoService.DeleteTodo(id);
if (isDeleted) return Ok();
else return StatusCode(StatusCodes.Status500InternalServerError, "Todo 삭제 실패");
}
}
}
9. command
- dotnet ef migrations add init
- dotnet ef database update
- dotnet watch