ASP.NET Core 8.0 인증 및 권한 부여의 완전 가이드

  • 12 minutes to read

이 문서의 내용의 전체 소스는 다음 경로의 GitHub 리포지토리에 있습니다.

핸즈온랩으로 따라하기로 진행할 때에는 ASP.NET Core 8.0 Empty 프로젝트를 생성합니다.

Program.cs 파일에 이 문서 마지막에 제공되는 전체 소스 코드를 참고하여 단계별로 연습합니다.

동영상 강의

강의 내용

  1. Claim, ClaimsIdentity, ClaimsPrincipal 클래스로 최소한의 코드로 인증 쿠키 생성하기
  2. UseAuthentication 확장 메서드를 사용하여 인증된 사용자의 정보 가져오기
  3. JsonSerializer 클래스로 컬렉션 형태의 데이터를 JSON으로 변환하여 출력하기
  4. MIME 타입 변경 연습 및 SignOutAsync 메서드를 사용하여 로그아웃 기능 구현하기
  5. 로그인할 때 더 많은 Claim을 저장하고 IsInRole 메서드로 권한을 체크하는 방법 살펴보기
  6. 권한(Authorization) 관련 기능을 MVC와 Web API 컨트롤러에서 사용하기 데모

1. 기본 설정 및 서비스 추가

이 섹션에서는 ASP.NET Core 애플리케이션을 설정하는 기본 단계를 설명합니다. WebApplication.CreateBuilder(args)를 사용하여 프로젝트를 생성하고, 필요한 서비스를 추가하는 방법에 대해 배웁니다. MVC 컨트롤러와 쿠키 기반 인증을 위한 AddControllersWithViewsAddAuthentication().AddCookie() 메서드의 사용을 다룹니다.

코드: Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();

2. 개발 환경 설정

개발 환경에서의 오류 페이지 설정과 디버깅을 위한 도구 사용 방법을 소개합니다. app.UseDeveloperExceptionPage()를 통해 개발 시 오류 추적과 문제 해결이 용이하게 할 수 있는 방법을 배웁니다.

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

3. 라우팅 및 인증/권한 부여 설정

이 섹션에서는 라우팅 설정과 인증/권한 부여 미들웨어의 기본 구성을 다룹니다. app.UseRouting(), app.UseAuthentication(), app.UseAuthorization()를 사용하여 애플리케이션의 흐름을 제어하는 방법을 설명합니다.

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

4. 엔드포인트 및 라우트 구현

여러 엔드포인트 및 라우트를 설정하여 로그인, 정보 조회, 로그아웃 기능을 구현하는 방법을 배웁니다. app.MapGet() 메서드를 활용한 경로 설정과 각 경로에 대한 핸들러 구현 방법에 대해 자세히 다룹니다.

app.MapGet("/", async context => { /*...*/ });
app.MapGet("/Login/{Username}", async context => { /*...*/ });
// 기타 라우트 설정

5. 사용자 인증 및 권한 부여

사용자의 로그인 과정에서 ClaimsPrincipal을 생성하고, 사용자의 권한을 관리하는 방법을 설명합니다. 특히, 관리자와 일반 사용자의 권한을 구분하는 방법에 대해 집중적으로 다룹니다.

app.MapGet("/Login/{Username}", async context =>
{
    // 사용자 인증 및 Claim 설정
});

6. 인증 정보 확인 방법

로그인한 사용자의 정보를 확인하는 방법을 배웁니다. /InfoDetails, /Info, /InfoJson 엔드포인트를 통해 사용자의 인증 정보를 확인하고, 사용자가 인증되었는지 여부를 판단하는 방법을 다룹니다.

app.MapGet("/InfoDetails", async context => { /*...*/ });

7. 로그아웃 기능 구현

사용자의 로그아웃 기능을 구현하는 방법을 배웁니다. app.MapGet("/Logout", async context => { /*...*/ })를 사용하여 사용자가 시스템에서 안전하게 로그아웃할 수 있도록 하는 방법에 대해 설명합니다.

app.MapGet("/Logout", async context => { /*...*/ });

8. 역할 기반 접근 제어 및 API 구현

마지막 섹션에서는 역할 기반의 접근 제어를 구현하고, API 컨트롤러를 설정하는 방법을 배웁니다. LandingController, DashboardController, AuthServiceController를 사용하여 특정 역할에 따라 접근을 제한하고, 사용자의 클레임 정보를 제공하는 API 엔드포인트를 구현합니다.

[Authorize(Roles = "Administrators")]
[Route("/Dashboard")]
public class DashboardController : Controller { /*...*/ }

전체 소스 코드

코드: AuthenticationAuthorization\VisualAcademy\Program.cs

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;

var builder = WebApplication.CreateBuilder(args);

// 서비스 추가
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();

var app = builder.Build();

// 개발 환경 설정
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

#region Menu
// 엔드포인트 및 라우트 설정
app.MapGet("/", async context =>
{
    string content = "<h1>ASP.NET Core 인증과 권한 초간단 코드</h1>";
    content += "<a href=\"/Login\">로그인</a><br />";
    content += "<a href=\"/Login/User\">로그인(User)</a><br />";
    content += "<a href=\"/Login/Administrator\">로그인(Administrator)</a><br />";
    content += "<a href=\"/Info\">정보</a><br />";
    content += "<a href=\"/InfoDetails\">정보(Details)</a><br />";
    content += "<a href=\"/InfoJson\">정보(JSON)</a><br />";
    content += "<a href=\"/Logout\">로그아웃</a><br />";
    content += "<hr /><a href=\"/Landing\">랜딩페이지</a><br />";
    content += "<a href=\"/Greeting\">환영페이지</a><br />";
    content += "<a href=\"/Dashboard\">관리페이지</a><br />";
    content += "<a href=\"/api/AuthService\">로그인 정보(JSON)</a><br />";

    context.Response.Headers["Content-Type"] = "text/html; charset=utf-8";
    await context.Response.WriteAsync(content);
});
#endregion

#region Login/{Username}
app.MapGet("/Login/{Username}", async context =>
{
    var username = context.Request.RouteValues["Username"].ToString();
    var claims = new List<Claim>
{
        new Claim(ClaimTypes.NameIdentifier, username),
        new Claim(ClaimTypes.Name, username),
        new Claim(ClaimTypes.Email, username.ToLower() + "@youremail.com"),
        new Claim(ClaimTypes.Role, "Users"),
        new Claim("원하는 이름", "원하는 값")
};

    if (username == "Administrator")
    {
        claims.Add(new Claim(ClaimTypes.Role, "Administrators"));
    }

    var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
    await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, new AuthenticationProperties { IsPersistent = true });
    context.Response.Headers["Content-Type"] = "text/html; charset=utf-8";
    await context.Response.WriteAsync("<h3>로그인 완료</h3>");
});
#endregion

#region Login
app.MapGet("/Login", async context =>
{
    var claims = new List<Claim>
{
        new Claim(ClaimTypes.Name, "아이디")
};

    var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
    await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);
    context.Response.Headers["Content-Type"] = "text/html; charset=utf-8";
    await context.Response.WriteAsync("<h3>로그인 완료</h3>");
});
#endregion

#region InfoDetails
app.MapGet("/InfoDetails", async context =>
{
    string result = "";

    if (context.User.Identity.IsAuthenticated)
    {
        result += $"<h3>로그인 이름: {context.User.Identity.Name}</h3>";
        foreach (var claim in context.User.Claims)
        {
            result += $"{claim.Type} = {claim.Value}<br />";
        }
        if (context.User.IsInRole("Administrators") && context.User.IsInRole("Users"))
        {
            result += "<br />Administrators + Users 권한이 있습니다.<br />";
        }
    }
    else
    {
        result += "<h3>로그인하지 않았습니다.</h3>";
    }

    context.Response.Headers["Content-Type"] = "text/html; charset=utf-8";
    await context.Response.WriteAsync(result, Encoding.Default);
});
#endregion

#region Info
app.MapGet("/Info", async context =>
{
    string result = "";

    if (context.User.Identity.IsAuthenticated)
    {
        result += $"<h3>로그인 이름: {context.User.Identity.Name}</h3>";
    }
    else
    {
        result += "<h3>로그인하지 않았습니다.</h3>";
    }

    context.Response.Headers["Content-Type"] = "text/html; charset=utf-8";
    await context.Response.WriteAsync(result, Encoding.Default);
});
#endregion

#region InfoJson
app.MapGet("/InfoJson", async context =>
{
    string json = "";

    if (context.User.Identity.IsAuthenticated)
    {
        var claims = context.User.Claims.Select(c => new ClaimDto { Type = c.Type, Value = c.Value });
        json += JsonSerializer.Serialize<IEnumerable<ClaimDto>>(
            claims,
            new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
    }
    else
    {
        json += "{}";
    }

    // MIME 타입을 JSON 형식으로 변경 
    context.Response.Headers["Content-Type"] = "application/json; charset=utf-8";
    await context.Response.WriteAsync(json);
});
#endregion

#region Logout
app.MapGet("/Logout", async context =>
{
    //await context.SignOutAsync("Cookies");
    await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

    context.Response.Headers["Content-Type"] = "text/html; charset=utf-8";
    await context.Response.WriteAsync("<h3>로그아웃 완료</h3>");
});
#endregion

app.MapControllers();
app.Run();

#region DTO
// 컨트롤러 및 DTO 클래스
public class ClaimDto
{
    public string Type { get; set; }
    public string Value { get; set; }
}
#endregion

#region MVC Controller
[AllowAnonymous]
[Route("/Landing")]
public class LandingController : Controller
{
    [HttpGet]
    public IActionResult Index() => Content("누구나 접근 가능");

    [Authorize]
    [HttpGet("/Greeting")]
    public IActionResult Greeting()
    {
        var roleName = HttpContext.User.IsInRole("Administrators") ? "관리자" : "사용자";
        return Content($"<em>{roleName}</em> 님, 반갑습니다.", "text/html", Encoding.Default);
    }
}

[Authorize(Roles = "Administrators")]
[Route("/Dashboard")]
public class DashboardController : Controller
{
    [HttpGet]
    public IActionResult Index() => Content("관리자 님, 반갑습니다.");
}
#endregion

#region Web API Controller
[ApiController]
[Route("api/[controller]")]
public class AuthServiceController : ControllerBase
{
    [Authorize]
    [HttpGet]
    public IEnumerable<ClaimDto> Get() =>
        HttpContext.User.Claims.Select(c => new ClaimDto { Type = c.Type, Value = c.Value });
}
#endregion
VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com