ASP.NET Core 8.0 인증 및 권한 부여의 완전 가이드
이 문서의 내용의 전체 소스는 다음 경로의 GitHub 리포지토리에 있습니다.
핸즈온랩으로 따라하기로 진행할 때에는 ASP.NET Core 8.0 Empty 프로젝트를 생성합니다.
Program.cs 파일에 이 문서 마지막에 제공되는 전체 소스 코드를 참고하여 단계별로 연습합니다.
동영상 강의
강의 내용
Claim,ClaimsIdentity,ClaimsPrincipal클래스로 최소한의 코드로 인증 쿠키 생성하기UseAuthentication확장 메서드를 사용하여 인증된 사용자의 정보 가져오기JsonSerializer클래스로 컬렉션 형태의 데이터를 JSON으로 변환하여 출력하기- MIME 타입 변경 연습 및
SignOutAsync메서드를 사용하여 로그아웃 기능 구현하기 - 로그인할 때 더 많은
Claim을 저장하고IsInRole메서드로 권한을 체크하는 방법 살펴보기 - 권한(Authorization) 관련 기능을 MVC와 Web API 컨트롤러에서 사용하기 데모
1. 기본 설정 및 서비스 추가
이 섹션에서는 ASP.NET Core 애플리케이션을 설정하는 기본 단계를 설명합니다. WebApplication.CreateBuilder(args)를 사용하여 프로젝트를 생성하고, 필요한 서비스를 추가하는 방법에 대해 배웁니다. MVC 컨트롤러와 쿠키 기반 인증을 위한 AddControllersWithViews와 AddAuthentication().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