JSON Web Token(JWT) 적용

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(); // 애플리케이션 실행
JSON Web Token 파트
UseAuthentication(); 추가로 사용자 요청을 검증

Controller에 [Authorize] 적용

[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);
        }
    }
}
승인되지 않음 401

인증을 위한 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(); // 애플리케이션 실행
[Authorize] 없이 사용 시
NZWalksDbcontext.cs 수정
NZWalksAuthDbcontext.cs 수정

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; }
    }
}
로그인 실패 (400)
로그인 성공 (200)

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(); // 애플리케이션 실행
program.cs 수정
AuthController.cs 생성자 수정

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; }
    }
}

현재 사용 중인 대칭 키가 HS256 암호화 알고리즘에 필요한 최소 128비트 키보다 작기 때문에 발생
아직 사용하지 못하는 API

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);
        }
    }
}
writer 등록
Reader가 Write 역할의 Api 사용 불가 (403)
Write 역할을 이용하여 제거

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(); // 애플리케이션 실행

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤