JSON Web Token(JWT) 적용
패키지 추가
패키지 추가 Microsoft.AspNetCore.Authentication.JwtBearer Microsoft.IdentityModel.Tokens System.IdentityModel.Tokens.Jwt Microsoft.AspNetCore.Identity.EntityFrameworkCore
appsettings.json 변경
appsettings.json
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "NZWalksConnectionString": "Server=[서버],[포트];Database=[DB이름];User ID=[DB아이디];Password=[DB비];Trusted_Connection=True;TrustServerCertificate=True" }, "Jwt": { "key": "TestCustomKey", "Issuer": "https://localhost:7256", "Audience": "https://localhost:7256" } }
서비스 등록을 통한 의존성 주입(Dependency Injection, DI)
Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; // Entity Framework Core 기능을 사용하기 위한 네임스페이스 using Microsoft.IdentityModel.Tokens; using NZWalks.API.Data; using NZWalks.API.Mappings; using NZWalks.API.Models.Domain; using NZWalks.API.Repositories; using System.Text; // 데이터 컨텍스트를 사용하기 위한 네임스페이스 var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더 객체 생성 // Add services to the container. builder.Services.AddControllers(); // 컨트롤러 서비스를 추가하여 MVC 패턴을 지원 // Swagger/OpenAPI 설정에 대한 더 많은 정보를 얻으려면 https://aka.ms/aspnetcore/swashbuckle 참고 builder.Services.AddEndpointsApiExplorer(); // API 탐색기를 추가하여 API 문서를 자동 생성 builder.Services.AddSwaggerGen(); // Swagger 생성을 위한 서비스 추가 // DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정 builder.Services.AddDbContext<NZWalksDbcontext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksConnectionString"))); builder.Services.AddScoped<IRegionRepository, SQLRegionRepository>(); // Main DB Region builder.Services.AddScoped<IWalkRepository, SQLWalkRepository>(); // Main DB Walk // builder.Services.AddScoped<IRegionRepository, InMemoryRegionRepository>(); // Test DB (In Memory) builder.Services.AddAutoMapper(typeof(AutoMapperProfiles)); // JWT 인증 설정 추가 builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // JWT Bearer 인증 스키마 사용 .AddJwtBearer(options => // JWT Bearer 인증 설정 options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuer = true, // 발행자(issuer) 유효성 검사 활성화 ValidateAudience = true, // 수신자(audience) 유효성 검사 활성화 ValidateLifetime = true, // 토큰의 유효 기간 검사 활성화 ValidateIssuerSigningKey = true, // 서명 키 유효성 검사 활성화 ValidIssuer = builder.Configuration["Jwt:Issuer"], // 유효한 발행자 설정 ValidAudience = builder.Configuration["Jwt:Audience"], // 유효한 수신자 설정 IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) // 서명 키 설정 }); var app = builder.Build(); // 애플리케이션 빌드 // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { // 개발 환경에서만 Swagger를 사용하도록 설정 app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리디렉션 app.UseAuthentication(); // JWT 인증 미들웨어 추가 // 이 코드는 JWT 인증을 처리하여 사용자 요청을 검증함 app.UseAuthorization(); // 권한 부여 미들웨어 사용 // 인증된 사용자의 권한을 검증함 app.MapControllers(); // 컨트롤러에 대한 요청을 매핑 app.Run(); // 애플리케이션 실행
Controller에 [Authorize] 적용
using Microsoft.AspNetCore.Mvc; using NZWalks.API.Data; using NZWalks.API.Models.Domain; using NZWalks.API.Models.DTO; using AutoMapper; using NZWalks.API.CustomActionFilter; using Microsoft.AspNetCore.Authorization; namespace NZWalks.API.Controllers { [Route("api/[controller]")] // 컨트롤러의 기본 Route를 설정. [controller]는 컨트롤러 이름으로 대체됨. [ApiController] // API 컨트롤러 특성 적용 [Authorize] // 인증된 사용자만 접급 가능 public class RegionsController : ControllerBase { private readonly NZWalksDbcontext dbcontext; private readonly IRegionRepository regionRepository; private readonly IMapper mapper; public RegionsController(NZWalksDbcontext dbContext, IRegionRepository regionRepository, IMapper mapper) { this.dbcontext = dbContext; this.regionRepository = regionRepository; this.mapper = mapper; } // GET ALL REGIONS // GET: https://localhost:1234/api/regions [HttpGet] public async Task<IActionResult> GetAll() { // Get Data From Database - Domain models // var regionDomain = await dbcontext.Regions.ToListAsync(); // Change Async var regionDomain = await regionRepository.GetAllAsync(); // Map Domain Models to DTOs // var regionDto = new List<RegionDto>(); // foreach (var region in regionDomain) // { // regionDto.Add(new RegionDto() // { // Id = region.Id, // Code = region.Code, // Name = region.Name, // RegionImageUrl = region.RegionImageUrl // }); // } // Map Domain Models to DTOs var regionDto = mapper.Map<List<RegionDto>>(regionDomain); return Ok(regionDto); } // GET SINGLE REGION (Get Region By ID) // GET: https://localhost:1234/api/regions/{id} [HttpGet] [Route("{id:guid}")] public async Task<IActionResult> GetById([FromRoute]Guid id) { // Get Data From Database - Domain models // var region = dbcontext.Regions.Find(id); // var regionDomain = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id); var regionDomain = await regionRepository.GetByIdAsync(id); if (regionDomain == null) { return NotFound(); } // Map Domain Models to DTOs // var regionDto = new RegionDto() // { // Id = regionDomain.Id, // Code = regionDomain.Code, // Name = regionDomain.Name, // RegionImageUrl = regionDomain.RegionImageUrl // }; var regionDto = mapper.Map<RegionDto>(regionDomain); return Ok(regionDto); } // POST To Create New Region // POST : https://localhost:1234/api/regions [HttpPost] [ValidateModel] public async Task<IActionResult> Create([FromBody] AddRegionRequestDto addRegionRequestDto) { /* [ValidateModel] 적용전 if (ModelState.IsValid) // [ValidateModel] { // [Require] // string Code, Name // string? RegionImageUrl // Map or Convert DTO to Domain Model //var regionDomainModel = new Region //{ // Code = addRegionRequestDto.Code, // Name = addRegionRequestDto.Name, // RegionImageUrl = addRegionRequestDto.RegionImageUrl //}; // Map or Convert DTO to Domain Model var regionDomainModel = mapper.Map<Region>(addRegionRequestDto); // Use Domain Model to create Region // await dbcontext.Regions.AddAsync(regionDomainModel); // await dbcontext.SaveChangesAsync(); await regionRepository.CreateAsync(regionDomainModel); // Map Domain model back to DTO //var regionDto = new RegionDto //{ // Id = regionDomainModel.Id, // Code = addRegionRequestDto.Code, // Name = regionDomainModel.Name, // RegionImageUrl = regionDomainModel.RegionImageUrl //}; var regionDto = mapper.Map<RegionDto>(regionDomainModel); // 201 Created 응답으로 생성된 DTO 반환 return CreatedAtAction(nameof(GetById), new { id = regionDto.Id }, regionDto); // CreatedAtAction은 ASP.NET Core에서 HTTP POST 요청 후 201 Created 상태 코드를 반환할 때 사용되는 메서드 // 새로운 리소스를 생성한 후, 해당 리소스에 접근할 수 있는 URI를 함께 제공하여 클라이언트가 리소스를 쉽게 찾을 수 있음 } else { return BadRequest(ModelState); } */ // [Require] // string Code, Name // string? RegionImageUrl // Map or Convert DTO to Domain Model //var regionDomainModel = new Region //{ // Code = addRegionRequestDto.Code, // Name = addRegionRequestDto.Name, // RegionImageUrl = addRegionRequestDto.RegionImageUrl //}; // Map or Convert DTO to Domain Model var regionDomainModel = mapper.Map<Region>(addRegionRequestDto); // Use Domain Model to create Region // await dbcontext.Regions.AddAsync(regionDomainModel); // await dbcontext.SaveChangesAsync(); await regionRepository.CreateAsync(regionDomainModel); // Map Domain model back to DTO //var regionDto = new RegionDto //{ // Id = regionDomainModel.Id, // Code = addRegionRequestDto.Code, // Name = regionDomainModel.Name, // RegionImageUrl = regionDomainModel.RegionImageUrl //}; var regionDto = mapper.Map<RegionDto>(regionDomainModel); // 201 Created 응답으로 생성된 DTO 반환 return CreatedAtAction(nameof(GetById), new { id = regionDto.Id }, regionDto); // CreatedAtAction은 ASP.NET Core에서 HTTP POST 요청 후 201 Created 상태 코드를 반환할 때 사용되는 메서드 // 새로운 리소스를 생성한 후, 해당 리소스에 접근할 수 있는 URI를 함께 제공하여 클라이언트가 리소스를 쉽게 찾을 수 있음 } // Update Region // PUT: https://localhost:1234/api/regions/{id} [HttpPut] [Route("{id:guid}")] [ValidateModel] public async Task<IActionResult> Update([FromRoute] Guid id, [FromBody] UpdateRegionRequestDto updateRegionRequestDto) { /* [ValidateModel] 적용 전 if (ModelState.IsValid) { // Map //var regionDomainModel = new Region //{ // Code = updateRegionRequestDto.Code, // Name = updateRegionRequestDto.Name, // RegionImageUrl = updateRegionRequestDto.RegionImageUrl //}; var regionDomainModel = mapper.Map<Region>(updateRegionRequestDto); // Check if region exists // var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id); regionDomainModel = await regionRepository.UpdateAsync(id, regionDomainModel); if (regionDomainModel == null) { return NotFound(); } //// Map DTO to Domain model //regionDomainModel.Code = updateRegionRequestDto.Code; //regionDomainModel.Name = updateRegionRequestDto.Name; //regionDomainModel.RegionImageUrl = updateRegionRequestDto.RegionImageUrl; //await dbcontext.SaveChangesAsync(); // Convert Domain Model to DTO //var regionDto = new RegionDto //{ // Id = regionDomainModel.Id, // Code = regionDomainModel.Code, // Name = regionDomainModel.Name, // RegionImageUrl = regionDomainModel.RegionImageUrl //}; var regionDto = mapper.Map<RegionDto>(regionDomainModel); return Ok(regionDto); } else { return BadRequest(ModelState); } */ // Map //var regionDomainModel = new Region //{ // Code = updateRegionRequestDto.Code, // Name = updateRegionRequestDto.Name, // RegionImageUrl = updateRegionRequestDto.RegionImageUrl //}; var regionDomainModel = mapper.Map<Region>(updateRegionRequestDto); // Check if region exists // var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id); regionDomainModel = await regionRepository.UpdateAsync(id, regionDomainModel); if (regionDomainModel == null) { return NotFound(); } //// Map DTO to Domain model //regionDomainModel.Code = updateRegionRequestDto.Code; //regionDomainModel.Name = updateRegionRequestDto.Name; //regionDomainModel.RegionImageUrl = updateRegionRequestDto.RegionImageUrl; //await dbcontext.SaveChangesAsync(); // Convert Domain Model to DTO //var regionDto = new RegionDto //{ // Id = regionDomainModel.Id, // Code = regionDomainModel.Code, // Name = regionDomainModel.Name, // RegionImageUrl = regionDomainModel.RegionImageUrl //}; var regionDto = mapper.Map<RegionDto>(regionDomainModel); return Ok(regionDto); } // Delete Region // Delete https://localhost:1234/api/regions/{id} [HttpDelete] [Route("{id:guid}")] public async Task<IActionResult> Update([FromRoute] Guid id) { //var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id); var regionDomainModel = await regionRepository.DeleteAsync(id); if (regionDomainModel == null) { return NotFound(); } // dbcontext.Regions.Remove(regionDomainModel); // await dbcontext.SaveChangesAsync(); // return deleted Region back // Convert Domain Model to DTO //var regionDto = new RegionDto //{ // Id = regionDomainModel.Id, // Code = regionDomainModel.Code, // Name = regionDomainModel.Name, // RegionImageUrl = regionDomainModel.RegionImageUrl //}; var regionDto = mapper.Map<Region>(regionDomainModel); return Ok(regionDto); } } }
인증을 위한 DB 생성 및 연결
연결문자열 추가
appsettings.json
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "NZWalksConnectionString": "Server=[서버],[포트];Database=[DB이름];User ID=[DB아이디];Password=[DB비];Trusted_Connection=True;TrustServerCertificate=True", "NZWalksAuthConnectionString": "Server=[서버],[포트];Database=[DB이름];User ID=[DB아이디];Password=[DB비];Trusted_Connection=True;TrustServerCertificate=True" }, "Jwt": { "key": "TestCustomKey", "Issuer": "https://localhost:7256", "Audience": "https://localhost:7256" } }
NZWalksDbcontext.cs 생성
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace NZWalks.API.Data { public class NZWalksAuthDbcontext : IdentityDbContext { public NZWalksAuthDbcontext(DbContextOptions options) : base(options) { } } }
의존성 주입(Dependency Injection, DI)
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; // Entity Framework Core 기능을 사용하기 위한 네임스페이스 using Microsoft.IdentityModel.Tokens; using NZWalks.API.Data; using NZWalks.API.Mappings; using NZWalks.API.Models.Domain; using NZWalks.API.Repositories; using System.Text; // 데이터 컨텍스트를 사용하기 위한 네임스페이스 var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더 객체 생성 // Add services to the container. builder.Services.AddControllers(); // 컨트롤러 서비스를 추가하여 MVC 패턴을 지원 // Swagger/OpenAPI 설정에 대한 더 많은 정보를 얻으려면 https://aka.ms/aspnetcore/swashbuckle 참고 builder.Services.AddEndpointsApiExplorer(); // API 탐색기를 추가하여 API 문서를 자동 생성 builder.Services.AddSwaggerGen(); // Swagger 생성을 위한 서비스 추가 // DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정 builder.Services.AddDbContext<NZWalksDbcontext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksConnectionString"))); // DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정 builder.Services.AddDbContext<NZWalksAuthDbcontext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksAuthConnectionString"))); builder.Services.AddScoped<IRegionRepository, SQLRegionRepository>(); // Main DB Region builder.Services.AddScoped<IWalkRepository, SQLWalkRepository>(); // Main DB Walk // builder.Services.AddScoped<IRegionRepository, InMemoryRegionRepository>(); // Test DB (In Memory) builder.Services.AddAutoMapper(typeof(AutoMapperProfiles)); // JWT 인증 설정 추가 builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // JWT Bearer 인증 스키마 사용 .AddJwtBearer(options => // JWT Bearer 인증 설정 options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuer = true, // 발행자(issuer) 유효성 검사 활성화 ValidateAudience = true, // 수신자(audience) 유효성 검사 활성화 ValidateLifetime = true, // 토큰의 유효 기간 검사 활성화 ValidateIssuerSigningKey = true, // 서명 키 유효성 검사 활성화 ValidIssuer = builder.Configuration["Jwt:Issuer"], // 유효한 발행자 설정 ValidAudience = builder.Configuration["Jwt:Audience"], // 유효한 수신자 설정 IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) // 서명 키 설정 }); var app = builder.Build(); // 애플리케이션 빌드 // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { // 개발 환경에서만 Swagger를 사용하도록 설정 app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리디렉션 app.UseAuthentication(); // JWT 인증 미들웨어 추가 // 이 코드는 JWT 인증을 처리하여 사용자 요청을 검증함 app.UseAuthorization(); // 권한 부여 미들웨어 사용 // 인증된 사용자의 권한을 검증함 app.MapControllers(); // 컨트롤러에 대한 요청을 매핑 app.Run(); // 애플리케이션 실행
NZWalksDbcontext.cs
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using NZWalks.API.Models.Domain; namespace NZWalks.API.Data { public class NZWalksAuthDbcontext : IdentityDbContext { public NZWalksAuthDbcontext(DbContextOptions<NZWalksAuthDbcontext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); var readerRoleId = "4597cc15-43f1-481c-904a-27097ce8f294"; var writerRoleId = "4b9e9601-65e4-4b6d-9a30-bda7550c73e6"; var roles = new List<IdentityRole> { new IdentityRole { Id = readerRoleId, ConcurrencyStamp = readerRoleId, Name = "Reader", NormalizedName = "Reader".ToUpper() }, new IdentityRole { Id = writerRoleId, ConcurrencyStamp = writerRoleId, Name = "writer", NormalizedName = "writer".ToUpper() }, }; // Seed difficulties to the database modelBuilder.Entity<IdentityRole>().HasData(roles); // Add-Migration "Creating Auth Database" -Context "NZWalksAuthDbcontext" // Update-Database } } }
패키지 관리자 콘솔에 명령어 입력
Add-Migration "Creating Auth Database" -Context "NZWalksAuthDbcontext" Update-Database -Context "NZWalksAuthDbcontext"
Identity Core 서비스를 추가 및 옵션
Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; // Entity Framework Core 기능을 사용하기 위한 네임스페이스 using Microsoft.IdentityModel.Tokens; using NZWalks.API.Data; using NZWalks.API.Mappings; using NZWalks.API.Models.Domain; using NZWalks.API.Repositories; using System.Text; // 데이터 컨텍스트를 사용하기 위한 네임스페이스 var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더 객체 생성 // Add services to the container. builder.Services.AddControllers(); // 컨트롤러 서비스를 추가하여 MVC 패턴을 지원 // Swagger/OpenAPI 설정에 대한 더 많은 정보를 얻으려면 https://aka.ms/aspnetcore/swashbuckle 참고 builder.Services.AddEndpointsApiExplorer(); // API 탐색기를 추가하여 API 문서를 자동 생성 builder.Services.AddSwaggerGen(); // Swagger 생성을 위한 서비스 추가 // DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정 builder.Services.AddDbContext<NZWalksDbcontext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksConnectionString"))); // DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정 builder.Services.AddDbContext<NZWalksAuthDbcontext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksAuthConnectionString"))); builder.Services.AddScoped<IRegionRepository, SQLRegionRepository>(); // Main DB Region builder.Services.AddScoped<IWalkRepository, SQLWalkRepository>(); // Main DB Walk // builder.Services.AddScoped<IRegionRepository, InMemoryRegionRepository>(); // Test DB (In Memory) builder.Services.AddAutoMapper(typeof(AutoMapperProfiles)); // Identity Core 서비스를 추가하여 IdentityUser를 사용하도록 설정 builder.Services.AddIdentityCore<IdentityUser>() // IdentityRole을 추가하여 역할 관리를 지원 .AddRoles<IdentityRole>() // 데이터 보호 토큰 제공자를 추가하여 "NZWalks"라는 이름의 토큰 제공자 설정 .AddTokenProvider<DataProtectorTokenProvider<IdentityUser>>("NZWalks") // Entity Framework 스토어를 사용하여 NZWalksAuthDbcontext를 통한 저장소 관리 .AddEntityFrameworkStores<NZWalksAuthDbcontext>() // 기본 토큰 제공자 추가 .AddDefaultTokenProviders(); // Identity 옵션을 구성하여 비밀번호 정책 설정 builder.Services.Configure<IdentityOptions>(options => { options.Password.RequireDigit = false; // 비밀번호에 숫자 필요 없음 options.Password.RequireLowercase = false; // 비밀번호에 소문자 필요 없음 options.Password.RequireNonAlphanumeric = false; // 비밀번호에 알파벳 이외의 문자 필요 없음 options.Password.RequireUppercase = false; // 비밀번호에 대문자 필요 없음 options.Password.RequiredLength = 6; // 비밀번호 최소 길이 6자 options.Password.RequiredUniqueChars = 1; // 비밀번호에 최소 1개의 고유 문자 필요 }); // JWT 인증 설정 추가 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // JWT Bearer 인증 스키마 사용 .AddJwtBearer(options => // JWT Bearer 인증 설정 options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuer = true, // 발행자(issuer) 유효성 검사 활성화 ValidateAudience = true, // 수신자(audience) 유효성 검사 활성화 ValidateLifetime = true, // 토큰의 유효 기간 검사 활성화 ValidateIssuerSigningKey = true, // 서명 키 유효성 검사 활성화 ValidIssuer = builder.Configuration["Jwt:Issuer"], // 유효한 발행자 설정 ValidAudience = builder.Configuration["Jwt:Audience"], // 유효한 수신자 설정 IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) // 서명 키 설정 }); var app = builder.Build(); // 애플리케이션 빌드 // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { // 개발 환경에서만 Swagger를 사용하도록 설정 app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리디렉션 app.UseAuthentication(); // JWT 인증 미들웨어 추가 // 이 코드는 JWT 인증을 처리하여 사용자 요청을 검증함 app.UseAuthorization(); // 권한 부여 미들웨어 사용 // 인증된 사용자의 권한을 검증함 app.MapControllers(); // 컨트롤러에 대한 요청을 매핑 app.Run(); // 애플리케이션 실행
인증 컨트롤러 만들기
AuthController.cs
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using NZWalks.API.Models.DTO; namespace NZWalks.API.Controllers { [Route("api/[controller]")] // 컨트롤러의 기본 경로를 설정. [controller]는 컨트롤러 이름으로 대체됨. [ApiController] // API 컨트롤러 특성 적용 public class AuthController : ControllerBase { private readonly UserManager<IdentityUser> userManager; // UserManager를 주입받아 초기화 public AuthController(UserManager<IdentityUser> userManager) { this.userManager = userManager; } // Post: /api/Auth/Register [HttpPost] [Route("Register")] public async Task<IActionResult> Register([FromBody] RegisterRequestDto registerRequestDto) { // 새로운 IdentityUser 생성 var identityUser = new IdentityUser { UserName = registerRequestDto.Username, Email = registerRequestDto.Username }; // UserManager를 사용하여 사용자 생성 var identityResult = await userManager.CreateAsync(identityUser, registerRequestDto.Password); if (identityResult.Succeeded) { // 사용자에게 역할(Role) 추가 if (registerRequestDto.Roles != null && registerRequestDto.Roles.Any()) { identityResult = await userManager.AddToRolesAsync(identityUser, registerRequestDto.Roles); if (identityResult.Succeeded) { return Ok("User was registered! Please login."); // 사용자 등록 성공 메시지 반환 } } } return BadRequest("Something went wrong!"); // 오류 발생 시 반환 메시지 } } }
RegisterRequestDto.cs
using System.ComponentModel.DataAnnotations; namespace NZWalks.API.Models.DTO { public class RegisterRequestDto { [Required] [DataType(DataType.EmailAddress)] public string Username { get; set; } [Required] [DataType(DataType.EmailAddress)] public string Password { get; set; } public string[] Roles { get; set; } } }
Login 기능 만들기
AuthController.cs
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using NZWalks.API.Models.DTO; namespace NZWalks.API.Controllers { [Route("api/[controller]")] // 컨트롤러의 기본 경로를 설정. [controller]는 컨트롤러 이름으로 대체됨. [ApiController] // API 컨트롤러 특성 적용 public class AuthController : ControllerBase { private readonly UserManager<IdentityUser> userManager; // UserManager를 주입받아 초기화 public AuthController(UserManager<IdentityUser> userManager) { this.userManager = userManager; } // Post: /api/Auth/Register [HttpPost] [Route("Register")] public async Task<IActionResult> Register([FromBody] RegisterRequestDto registerRequestDto) { // 새로운 IdentityUser 생성 var identityUser = new IdentityUser { UserName = registerRequestDto.Username, Email = registerRequestDto.Username }; // UserManager를 사용하여 사용자 생성 var identityResult = await userManager.CreateAsync(identityUser, registerRequestDto.Password); if (identityResult.Succeeded) { // 사용자에게 역할(Role) 추가 if (registerRequestDto.Roles != null && registerRequestDto.Roles.Any()) { identityResult = await userManager.AddToRolesAsync(identityUser, registerRequestDto.Roles); if (identityResult.Succeeded) { return Ok("User was registered! Please login."); // 사용자 등록 성공 메시지 반환 } } } return BadRequest("Something went wrong!"); // 오류 발생 시 반환 메시지 } // Post: /api/Auth/Login [HttpPost] [Route("Login")] public async Task<IActionResult> Login([FromBody] LoginRequestDto loginRequestDto) { // 사용자 이메일로 사용자 찾기 var user = await userManager.FindByEmailAsync(loginRequestDto.Username); if (user != null) { // 사용자의 비밀번호 확인 var checkPasswordResult = await userManager.CheckPasswordAsync(user, loginRequestDto.Password); if (checkPasswordResult) { // 토큰 생성 로직 return Ok(); // 성공 응답 } } return BadRequest("Username or Password incorrect"); // 사용자 이름 또는 비밀번호가 잘못된 경우 오류 응답 } } }
LoginRequestDto.cs
using System.ComponentModel.DataAnnotations; namespace NZWalks.API.Models.DTO { public class LoginRequestDto { [Required] [DataType(DataType.EmailAddress)] public string Username { get; set; } [Required] [DataType(DataType.Password)] public string Password { get; set; } } }
JWT 토큰 생성 및 적용
JWT 토큰 생성
TokenRepository.cs
using Microsoft.AspNetCore.Identity; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace NZWalks.API.Repositories { public class TokenRepository : ITokenRepository { private readonly IConfiguration configuration; // IConfiguration 주입받아 초기화 public TokenRepository(IConfiguration configuration) { this.configuration = configuration; } // JWT 토큰 생성 메서드 public string CreateJWTToken(IdentityUser user, List<string> roles) { // 클레임 생성 // Claim은 사용자의 특정 속성이나 권한을 나타내는 정보 조각 // JWT 토큰에서는 이러한 클레임을 사용하여 사용자의 신원과 권한을 나타냄 var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Email, user.Email), new Claim(ClaimTypes.Name, user.UserName) }; // 역할(Role)을 클레임에 추가 foreach (var role in roles) { claims.Add(new Claim(ClaimTypes.Role, role)); } // 대칭 보안 키 생성 (Jwt:Key 사용) var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"])); // 서명 자격 증명 생성 (HmacSha256 알고리즘 사용) var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); // JWT 토큰 생성 var token = new JwtSecurityToken( configuration["Jwt:Issuer"], // 유효한 발행자 configuration["Jwt:Audience"], // 유효한 수신자 claims, // 클레임 expires: DateTime.Now.AddMinutes(15), // 토큰 만료 시간 (현재 시간부터 15분 후) signingCredentials: credentials // 서명 자격 증명 ); // 토큰 문자열로 반환 return new JwtSecurityTokenHandler().WriteToken(token); } } }
ITokenRepository.cs
using Microsoft.AspNetCore.Identity; namespace NZWalks.API.Repositories { public interface ITokenRepository { string CreateJWTToken(IdentityUser user, List<string> roles) } }
JWT 토큰 적용
program.cs 수정
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; // Entity Framework Core 기능을 사용하기 위한 네임스페이스 using Microsoft.IdentityModel.Tokens; using NZWalks.API.Data; using NZWalks.API.Mappings; using NZWalks.API.Models.Domain; using NZWalks.API.Repositories; using System.Text; // 데이터 컨텍스트를 사용하기 위한 네임스페이스 var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더 객체 생성 // Add services to the container. builder.Services.AddControllers(); // 컨트롤러 서비스를 추가하여 MVC 패턴을 지원 // Swagger/OpenAPI 설정에 대한 더 많은 정보를 얻으려면 https://aka.ms/aspnetcore/swashbuckle 참고 builder.Services.AddEndpointsApiExplorer(); // API 탐색기를 추가하여 API 문서를 자동 생성 builder.Services.AddSwaggerGen(); // Swagger 생성을 위한 서비스 추가 // DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정 builder.Services.AddDbContext<NZWalksDbcontext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksConnectionString"))); // DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정 builder.Services.AddDbContext<NZWalksAuthDbcontext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksAuthConnectionString"))); builder.Services.AddScoped<IRegionRepository, SQLRegionRepository>(); // Main DB Region builder.Services.AddScoped<IWalkRepository, SQLWalkRepository>(); // Main DB Walk builder.Services.AddScoped<ITokenRepository, TokenRepository>(); // Token - Controller에서 사용가능 // builder.Services.AddScoped<IRegionRepository, InMemoryRegionRepository>(); // Test DB (In Memory) builder.Services.AddAutoMapper(typeof(AutoMapperProfiles)); // Identity Core 서비스를 추가하여 IdentityUser를 사용하도록 설정 builder.Services.AddIdentityCore<IdentityUser>() // IdentityRole을 추가하여 역할 관리를 지원 .AddRoles<IdentityRole>() // 데이터 보호 토큰 제공자를 추가하여 "NZWalks"라는 이름의 토큰 제공자 설정 .AddTokenProvider<DataProtectorTokenProvider<IdentityUser>>("NZWalks") // Entity Framework 스토어를 사용하여 NZWalksAuthDbcontext를 통한 저장소 관리 .AddEntityFrameworkStores<NZWalksAuthDbcontext>() // 기본 토큰 제공자 추가 .AddDefaultTokenProviders(); // Identity 옵션을 구성하여 비밀번호 정책 설정 builder.Services.Configure<IdentityOptions>(options => { options.Password.RequireDigit = false; // 비밀번호에 숫자 필요 없음 options.Password.RequireLowercase = false; // 비밀번호에 소문자 필요 없음 options.Password.RequireNonAlphanumeric = false; // 비밀번호에 알파벳 이외의 문자 필요 없음 options.Password.RequireUppercase = false; // 비밀번호에 대문자 필요 없음 options.Password.RequiredLength = 6; // 비밀번호 최소 길이 6자 options.Password.RequiredUniqueChars = 1; // 비밀번호에 최소 1개의 고유 문자 필요 }); // JWT 인증 설정 추가 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // JWT Bearer 인증 스키마 사용 .AddJwtBearer(options => // JWT Bearer 인증 설정 options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuer = true, // 발행자(issuer) 유효성 검사 활성화 ValidateAudience = true, // 수신자(audience) 유효성 검사 활성화 ValidateLifetime = true, // 토큰의 유효 기간 검사 활성화 ValidateIssuerSigningKey = true, // 서명 키 유효성 검사 활성화 ValidIssuer = builder.Configuration["Jwt:Issuer"], // 유효한 발행자 설정 ValidAudience = builder.Configuration["Jwt:Audience"], // 유효한 수신자 설정 IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) // 서명 키 설정 }); var app = builder.Build(); // 애플리케이션 빌드 // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { // 개발 환경에서만 Swagger를 사용하도록 설정 app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리디렉션 app.UseAuthentication(); // JWT 인증 미들웨어 추가 // 이 코드는 JWT 인증을 처리하여 사용자 요청을 검증함 app.UseAuthorization(); // 권한 부여 미들웨어 사용 // 인증된 사용자의 권한을 검증함 app.MapControllers(); // 컨트롤러에 대한 요청을 매핑 app.Run(); // 애플리케이션 실행
AuthController.cs
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using NZWalks.API.Models.DTO; using NZWalks.API.Repositories; namespace NZWalks.API.Controllers { [Route("api/[controller]")] // 컨트롤러의 기본 경로를 설정. [controller]는 컨트롤러 이름으로 대체됨. [ApiController] // API 컨트롤러 특성 적용 public class AuthController : ControllerBase { private readonly UserManager<IdentityUser> userManager; private readonly ITokenRepository tokenRepository; // UserManager와 ITokenRepository를 주입받아 초기화 public AuthController(UserManager<IdentityUser> userManager, ITokenRepository tokenRepository) { this.userManager = userManager; this.tokenRepository = tokenRepository; } // Post: /api/Auth/Register [HttpPost] [Route("Register")] public async Task<IActionResult> Register([FromBody] RegisterRequestDto registerRequestDto) { // 새로운 IdentityUser 생성 var identityUser = new IdentityUser { UserName = registerRequestDto.Username, Email = registerRequestDto.Username }; // UserManager를 사용하여 사용자 생성 var identityResult = await userManager.CreateAsync(identityUser, registerRequestDto.Password); if (identityResult.Succeeded) { // 사용자에게 역할(Role) 추가 if (registerRequestDto.Roles != null && registerRequestDto.Roles.Any()) { identityResult = await userManager.AddToRolesAsync(identityUser, registerRequestDto.Roles); if (identityResult.Succeeded) { return Ok("User was registered! Please login."); // 사용자 등록 성공 메시지 반환 } } } return BadRequest("Something went wrong!"); // 오류 발생 시 반환 메시지 } // Post: /api/Auth/Login [HttpPost] [Route("Login")] public async Task<IActionResult> Login([FromBody] LoginRequestDto loginRequestDto) { // 사용자 이메일로 사용자 찾기 var user = await userManager.FindByEmailAsync(loginRequestDto.Username); if (user != null) { // 사용자의 비밀번호 확인 var checkPasswordResult = await userManager.CheckPasswordAsync(user, loginRequestDto.Password); if (checkPasswordResult) { // 사용자 역할(Role) 가져오기 var roles = await userManager.GetRolesAsync(user); if (roles != null) { // 토큰 생성 로직 var jwtToken = tokenRepository.CreateJWTToken(user, roles.ToList()); var response = new LoginResposeDto { JwtToken = jwtToken }; return Ok(response); // 생성된 토큰을 포함한 응답 반환 } } } return BadRequest("Username or Password incorrect"); // 사용자 이름 또는 비밀번호가 잘못된 경우 오류 응답 } } }
LoginResposeDto.cs
namespace NZWalks.API.Models.DTO { public class LoginResposeDto { public string JwtToken { get; set; } } }
RegionsController.cs – Role에 따른 접근으로 변경
using Microsoft.AspNetCore.Mvc; using NZWalks.API.Data; using NZWalks.API.Models.Domain; using NZWalks.API.Models.DTO; using AutoMapper; using NZWalks.API.CustomActionFilter; using Microsoft.AspNetCore.Authorization; namespace NZWalks.API.Controllers { [Route("api/[controller]")] // 컨트롤러의 기본 Route를 설정. [controller]는 컨트롤러 이름으로 대체됨. [ApiController] // API 컨트롤러 특성 적용 public class RegionsController : ControllerBase { private readonly NZWalksDbcontext dbcontext; private readonly IRegionRepository regionRepository; private readonly IMapper mapper; public RegionsController(NZWalksDbcontext dbContext, IRegionRepository regionRepository, IMapper mapper) { this.dbcontext = dbContext; this.regionRepository = regionRepository; this.mapper = mapper; } // GET ALL REGIONS // GET: https://localhost:1234/api/regions [HttpGet] [Authorize(Roles = "Reader, writer")] // Reader, writer에서 인증된 사용자만 접근 가능 public async Task<IActionResult> GetAll() { // Get Data From Database - Domain models // var regionDomain = await dbcontext.Regions.ToListAsync(); // Change Async var regionDomain = await regionRepository.GetAllAsync(); // Map Domain Models to DTOs // var regionDto = new List<RegionDto>(); // foreach (var region in regionDomain) // { // regionDto.Add(new RegionDto() // { // Id = region.Id, // Code = region.Code, // Name = region.Name, // RegionImageUrl = region.RegionImageUrl // }); // } // Map Domain Models to DTOs var regionDto = mapper.Map<List<RegionDto>>(regionDomain); return Ok(regionDto); } // GET SINGLE REGION (Get Region By ID) // GET: https://localhost:1234/api/regions/{id} [HttpGet] [Route("{id:guid}")] [Authorize(Roles = "Reader")] // Reader에서 인증된 사용자만 접근 가능 public async Task<IActionResult> GetById([FromRoute]Guid id) { // Get Data From Database - Domain models // var region = dbcontext.Regions.Find(id); // var regionDomain = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id); var regionDomain = await regionRepository.GetByIdAsync(id); if (regionDomain == null) { return NotFound(); } // Map Domain Models to DTOs // var regionDto = new RegionDto() // { // Id = regionDomain.Id, // Code = regionDomain.Code, // Name = regionDomain.Name, // RegionImageUrl = regionDomain.RegionImageUrl // }; var regionDto = mapper.Map<RegionDto>(regionDomain); return Ok(regionDto); } // POST To Create New Region // POST : https://localhost:1234/api/regions [HttpPost] [ValidateModel] [Authorize(Roles = "writer")] // writer에서 인증된 사용자만 접근 가능 public async Task<IActionResult> Create([FromBody] AddRegionRequestDto addRegionRequestDto) { /* [ValidateModel] 적용전 if (ModelState.IsValid) // [ValidateModel] { // [Require] // string Code, Name // string? RegionImageUrl // Map or Convert DTO to Domain Model //var regionDomainModel = new Region //{ // Code = addRegionRequestDto.Code, // Name = addRegionRequestDto.Name, // RegionImageUrl = addRegionRequestDto.RegionImageUrl //}; // Map or Convert DTO to Domain Model var regionDomainModel = mapper.Map<Region>(addRegionRequestDto); // Use Domain Model to create Region // await dbcontext.Regions.AddAsync(regionDomainModel); // await dbcontext.SaveChangesAsync(); await regionRepository.CreateAsync(regionDomainModel); // Map Domain model back to DTO //var regionDto = new RegionDto //{ // Id = regionDomainModel.Id, // Code = addRegionRequestDto.Code, // Name = regionDomainModel.Name, // RegionImageUrl = regionDomainModel.RegionImageUrl //}; var regionDto = mapper.Map<RegionDto>(regionDomainModel); // 201 Created 응답으로 생성된 DTO 반환 return CreatedAtAction(nameof(GetById), new { id = regionDto.Id }, regionDto); // CreatedAtAction은 ASP.NET Core에서 HTTP POST 요청 후 201 Created 상태 코드를 반환할 때 사용되는 메서드 // 새로운 리소스를 생성한 후, 해당 리소스에 접근할 수 있는 URI를 함께 제공하여 클라이언트가 리소스를 쉽게 찾을 수 있음 } else { return BadRequest(ModelState); } */ // [Require] // string Code, Name // string? RegionImageUrl // Map or Convert DTO to Domain Model //var regionDomainModel = new Region //{ // Code = addRegionRequestDto.Code, // Name = addRegionRequestDto.Name, // RegionImageUrl = addRegionRequestDto.RegionImageUrl //}; // Map or Convert DTO to Domain Model var regionDomainModel = mapper.Map<Region>(addRegionRequestDto); // Use Domain Model to create Region // await dbcontext.Regions.AddAsync(regionDomainModel); // await dbcontext.SaveChangesAsync(); await regionRepository.CreateAsync(regionDomainModel); // Map Domain model back to DTO //var regionDto = new RegionDto //{ // Id = regionDomainModel.Id, // Code = addRegionRequestDto.Code, // Name = regionDomainModel.Name, // RegionImageUrl = regionDomainModel.RegionImageUrl //}; var regionDto = mapper.Map<RegionDto>(regionDomainModel); // 201 Created 응답으로 생성된 DTO 반환 return CreatedAtAction(nameof(GetById), new { id = regionDto.Id }, regionDto); // CreatedAtAction은 ASP.NET Core에서 HTTP POST 요청 후 201 Created 상태 코드를 반환할 때 사용되는 메서드 // 새로운 리소스를 생성한 후, 해당 리소스에 접근할 수 있는 URI를 함께 제공하여 클라이언트가 리소스를 쉽게 찾을 수 있음 } // Update Region // PUT: https://localhost:1234/api/regions/{id} [HttpPut] [Route("{id:guid}")] [ValidateModel] [Authorize(Roles = "writer")] // writer에서 인증된 사용자만 접근 가능 public async Task<IActionResult> Update([FromRoute] Guid id, [FromBody] UpdateRegionRequestDto updateRegionRequestDto) { /* [ValidateModel] 적용 전 if (ModelState.IsValid) { // Map //var regionDomainModel = new Region //{ // Code = updateRegionRequestDto.Code, // Name = updateRegionRequestDto.Name, // RegionImageUrl = updateRegionRequestDto.RegionImageUrl //}; var regionDomainModel = mapper.Map<Region>(updateRegionRequestDto); // Check if region exists // var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id); regionDomainModel = await regionRepository.UpdateAsync(id, regionDomainModel); if (regionDomainModel == null) { return NotFound(); } //// Map DTO to Domain model //regionDomainModel.Code = updateRegionRequestDto.Code; //regionDomainModel.Name = updateRegionRequestDto.Name; //regionDomainModel.RegionImageUrl = updateRegionRequestDto.RegionImageUrl; //await dbcontext.SaveChangesAsync(); // Convert Domain Model to DTO //var regionDto = new RegionDto //{ // Id = regionDomainModel.Id, // Code = regionDomainModel.Code, // Name = regionDomainModel.Name, // RegionImageUrl = regionDomainModel.RegionImageUrl //}; var regionDto = mapper.Map<RegionDto>(regionDomainModel); return Ok(regionDto); } else { return BadRequest(ModelState); } */ // Map //var regionDomainModel = new Region //{ // Code = updateRegionRequestDto.Code, // Name = updateRegionRequestDto.Name, // RegionImageUrl = updateRegionRequestDto.RegionImageUrl //}; var regionDomainModel = mapper.Map<Region>(updateRegionRequestDto); // Check if region exists // var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id); regionDomainModel = await regionRepository.UpdateAsync(id, regionDomainModel); if (regionDomainModel == null) { return NotFound(); } //// Map DTO to Domain model //regionDomainModel.Code = updateRegionRequestDto.Code; //regionDomainModel.Name = updateRegionRequestDto.Name; //regionDomainModel.RegionImageUrl = updateRegionRequestDto.RegionImageUrl; //await dbcontext.SaveChangesAsync(); // Convert Domain Model to DTO //var regionDto = new RegionDto //{ // Id = regionDomainModel.Id, // Code = regionDomainModel.Code, // Name = regionDomainModel.Name, // RegionImageUrl = regionDomainModel.RegionImageUrl //}; var regionDto = mapper.Map<RegionDto>(regionDomainModel); return Ok(regionDto); } // Delete Region // Delete https://localhost:1234/api/regions/{id} [HttpDelete] [Route("{id:guid}")] [Authorize(Roles = "writer")] // writer에서 인증된 사용자만 접근 가능 public async Task<IActionResult> Update([FromRoute] Guid id) { //var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id); var regionDomainModel = await regionRepository.DeleteAsync(id); if (regionDomainModel == null) { return NotFound(); } // dbcontext.Regions.Remove(regionDomainModel); // await dbcontext.SaveChangesAsync(); // return deleted Region back // Convert Domain Model to DTO //var regionDto = new RegionDto //{ // Id = regionDomainModel.Id, // Code = regionDomainModel.Code, // Name = regionDomainModel.Name, // RegionImageUrl = regionDomainModel.RegionImageUrl //}; var regionDto = mapper.Map<Region>(regionDomainModel); return Ok(regionDto); } } }
Swagger 적용
Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; // Entity Framework Core 기능을 사용하기 위한 네임스페이스 using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using NZWalks.API.Data; using NZWalks.API.Mappings; using NZWalks.API.Models.Domain; using NZWalks.API.Repositories; using System.Text; // 데이터 컨텍스트를 사용하기 위한 네임스페이스 var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더 객체 생성 // Add services to the container. builder.Services.AddControllers(); // 컨트롤러 서비스를 추가하여 MVC 패턴을 지원 // Swagger/OpenAPI 설정에 대한 더 많은 정보를 얻으려면 https://aka.ms/aspnetcore/swashbuckle 참고 builder.Services.AddEndpointsApiExplorer(); // API 탐색기를 추가하여 API 문서를 자동 생성 // Swagger 생성을 위한 서비스 추가 builder.Services.AddSwaggerGen(options => { // 제목과 버전을 정의하여 Swagger 문서 설정 options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "NZ Walks API", Version = "v1" }); // JWT Bearer 보안 정의 추가 options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new Microsoft.OpenApi.Models.OpenApiSecurityScheme() { Name = "Authorization", // 인증 헤더 이름 In = ParameterLocation.Header, // 인증 정보의 위치 (헤더) Type = SecuritySchemeType.ApiKey, // 인증 유형 (API 키) Scheme = JwtBearerDefaults.AuthenticationScheme // 스키마 설정 } ); // 정의된 보안 스키마를 사용한 보안 요구 사항 추가 options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, // 참조 유형 설정 Id = JwtBearerDefaults.AuthenticationScheme // 참조 ID 설정 }, Scheme = "Oauth2", // 스키마 설정 Name = JwtBearerDefaults.AuthenticationScheme, // 스키마 이름 In = ParameterLocation.Header // 위치 설정 }, new List<string>() } }); }); // DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정 builder.Services.AddDbContext<NZWalksDbcontext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksConnectionString"))); // DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정 builder.Services.AddDbContext<NZWalksAuthDbcontext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksAuthConnectionString"))); builder.Services.AddScoped<IRegionRepository, SQLRegionRepository>(); // Main DB Region builder.Services.AddScoped<IWalkRepository, SQLWalkRepository>(); // Main DB Walk builder.Services.AddScoped<ITokenRepository, TokenRepository>(); // Token - Controller에서 사용가능 // builder.Services.AddScoped<IRegionRepository, InMemoryRegionRepository>(); // Test DB (In Memory) builder.Services.AddAutoMapper(typeof(AutoMapperProfiles)); // Identity Core 서비스를 추가하여 IdentityUser를 사용하도록 설정 builder.Services.AddIdentityCore<IdentityUser>() // IdentityRole을 추가하여 역할 관리를 지원 .AddRoles<IdentityRole>() // 데이터 보호 토큰 제공자를 추가하여 "NZWalks"라는 이름의 토큰 제공자 설정 .AddTokenProvider<DataProtectorTokenProvider<IdentityUser>>("NZWalks") // Entity Framework 스토어를 사용하여 NZWalksAuthDbcontext를 통한 저장소 관리 .AddEntityFrameworkStores<NZWalksAuthDbcontext>() // 기본 토큰 제공자 추가 .AddDefaultTokenProviders(); // Identity 옵션을 구성하여 비밀번호 정책 설정 builder.Services.Configure<IdentityOptions>(options => { options.Password.RequireDigit = false; // 비밀번호에 숫자 필요 없음 options.Password.RequireLowercase = false; // 비밀번호에 소문자 필요 없음 options.Password.RequireNonAlphanumeric = false; // 비밀번호에 알파벳 이외의 문자 필요 없음 options.Password.RequireUppercase = false; // 비밀번호에 대문자 필요 없음 options.Password.RequiredLength = 6; // 비밀번호 최소 길이 6자 options.Password.RequiredUniqueChars = 1; // 비밀번호에 최소 1개의 고유 문자 필요 }); // JWT 인증 설정 추가 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // JWT Bearer 인증 스키마 사용 .AddJwtBearer(options => // JWT Bearer 인증 설정 options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuer = true, // 발행자(issuer) 유효성 검사 활성화 ValidateAudience = true, // 수신자(audience) 유효성 검사 활성화 ValidateLifetime = true, // 토큰의 유효 기간 검사 활성화 ValidateIssuerSigningKey = true, // 서명 키 유효성 검사 활성화 ValidIssuer = builder.Configuration["Jwt:Issuer"], // 유효한 발행자 설정 ValidAudience = builder.Configuration["Jwt:Audience"], // 유효한 수신자 설정 IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) // 서명 키 설정 }); var app = builder.Build(); // 애플리케이션 빌드 // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { // 개발 환경에서만 Swagger를 사용하도록 설정 app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리디렉션 app.UseAuthentication(); // JWT 인증 미들웨어 추가 // 이 코드는 JWT 인증을 처리하여 사용자 요청을 검증함 app.UseAuthorization(); // 권한 부여 미들웨어 사용 // 인증된 사용자의 권한을 검증함 app.MapControllers(); // 컨트롤러에 대한 요청을 매핑 app.Run(); // 애플리케이션 실행