1. AddAuthentication
Cookie Authentication을 사용하기 위해서 아래와 같이 서비스에 등록한다.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<AppDbContext>(option => option.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection")
));
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.SlidingExpiration = true;
options.AccessDeniedPath = "/Forbidden/";
});
해당 케이스는 하나의 Scheme을 등록했기 때문에 상관없지만, 여러개를 등록한 경우 Authorize attribute에서 AuthenticationSchemes argument에 name을 명시해주어야 한다. 명시해주지 않은 경우 default name("Cookies")을 사용한다.
[Authorize(AuthenticationSchemes ="Cookies")]
2. Authentication Middleware
middleware도 등록한다.
app.UseAuthentication();
app.UseAuthorization();
3. UserLoginModel
using System.ComponentModel.DataAnnotations;
namespace TEST.Models
{
public class UserLoginModel
{
[Required]
public string Id { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
4. Login Form
AccountController
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TEST.Models;
using TEST.Services;
using System.Security.Claims;
namespace TEST.Controllers
{
public class AccountController : Controller
{
private readonly IAccountService _accountService;
public AccountController(IAccountService accountService)
{
_accountService = accountService;
}
public IActionResult Login(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
[HttpPost]
public async Task<IActionResult> Login(UserLoginModel userModel, string? returnUrl = null)
{
if (!ModelState.IsValid)
{
return View(userModel);
}
var account = await _accountService.Login(userModel.Id, userModel.Password);
if (account == null)
{
ViewBag.msg = "invalid account info";
return View("Login");
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, account.EmpId),
new Claim(ClaimTypes.Name, account.EmpName),
new Claim("CenterCod", account.CenterCod.ToString()),
new Claim(ClaimTypes.Role, "Administrator"),
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
//AllowRefresh = <bool>,
// Refreshing the authentication session should be allowed.
//ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
// The time at which the authentication ticket expires. A
// value set here overrides the ExpireTimeSpan option of
// CookieAuthenticationOptions set with AddCookie.
//IsPersistent = true,
// Whether the authentication session is persisted across
// multiple requests. When used with cookies, controls
// whether the cookie's lifetime is absolute (matching the
// lifetime of the authentication ticket) or session-based.
//IssuedUtc = <DateTimeOffset>,
// The time at which the authentication ticket was issued.
//RedirectUri = <string>
// The full path or absolute URI to be used as an http
// redirect response value.
};
//.AspNetCore.Cookies 암호화된 cookie를 브라우저에 생성한다.
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
if (!string.IsNullOrEmpty(returnUrl))
{
return LocalRedirect(returnUrl);
}
return RedirectToAction("Welcome");
}
[Authorize]
public async Task<IActionResult> LogOut()
{
// Clear the existing external cookie
await HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
return Redirect("/");
}
[Authorize]
public IActionResult Welcome()
{
return View("Welcome");
}
}
}
Login.cshtml
@model UserLoginModel
<h3>Login</h3>
@ViewBag.msg
<form method="post" asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]">
UserId <input type="text" asp-for="Id" name="Id" />
<span asp-validation-for="Id" class="text-danger"></span>
<br />
Password <input type="password" name="password" />
<span asp-validation-for="Password" class="text-danger"></span>
<br />
<input type="submit" value="Login" />
</form>
_Layout.cshtml
아래 내용 추가
User의 로그인 여부는 ClaimsPrincipal 객체인 User Property로 알 수 있으며, 자동으로 Controller와 Razor Pages에 주입된다.
@using System.Security.Claims
@if (User.Identity.IsAuthenticated)
{
if (@User.Claims.Any(
x => x.Type == ClaimTypes.Name))
{
<li class="nav-item">
<a class="nav-link text-dark"
asp-controller="Account" asp-action="Profile">
@User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name).Value
</a>
</li>
}
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="LogOut">LogOut</a>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="Login">Login</a>
</li>
}
로그인 하지 않은 상태로, /Account/Welcome에 접속을 하면 ASP.NET Core는 자동으로 ReturnUrl query string을 붙여서 로그인 url로 이동시킨다.
/Account/Login?ReturnUrl=%2FAccount%2FWelcome
로그인 후에, /Account/Welcome으로 자동 redirect된다.
쿠키가 생성되었다.
5. Authorize Attribute Roles Argument
UserController.cs
User Role에만 접속권한을 부여하였다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using TEST.Data;
using TEST.Models;
using TEST.Services;
namespace TEST.Controllers
{
[Authorize(Roles = "User")]
public class UserController : Controller
{
private readonly IUnitOfWork _unitOfWork;
private readonly IRazorRenderService _renderService;
public UserController(IUnitOfWork unitOfWork, IRazorRenderService renderService)
{
_unitOfWork = unitOfWork;
_renderService = renderService;
}
public IActionResult Index()
{
return View();
}
}
}
로그인 후, 접속 시 Role이 일치하지 않기 때문에 ForbidAsync()함수를 실행하고 404 not found response를 받는다.
option으로 설정한 /Forbidden/ 경로로 이동한다.
6. IHttpContextAccessor
Controller Constructor에서 User Property값을 얻기 위해서는 IHttpContextAccessor interface를 주입한다.
IHttpContextAccessor를 통해서 HttpContext object를 얻을 수 있다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TEST.Data;
using TEST.Models;
using TEST.ViewModels;
using System.Security.Claims;
namespace TEST.Controllers
{
[Authorize]
public class FileController : Controller
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IUnitOfWork _unitOfWork;
public FileController(IHttpContextAccessor httpContextAccessor, IUnitOfWork unitOfWork)
{
_httpContextAccessor = httpContextAccessor;
_unitOfWork = unitOfWork;
Console.WriteLine("User Id: " + _httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier));
}
public async Task<IActionResult> Index()
{
Console.WriteLine("Role: " + User.FindFirstValue(ClaimTypes.Role));
return View();
}
}
}
Program.cs
builder.Services.AddHttpContextAccessor();
'ASP.NET Core' 카테고리의 다른 글
ASP.NET Core API] Audit Trail(API 로그) (0) | 2023.11.01 |
---|---|
ASP.NET Core] HTTP error status code 404 처리 (0) | 2023.05.12 |
ASP.NET Core] Asynchronous Generic Repository (0) | 2023.04.26 |
ASP.NET Core] Asynchronous Programming - Async, Await (0) | 2023.04.26 |
ASP.NET Core Razor Pages] 2. Page Models, Routing, Filters (0) | 2023.02.27 |
댓글