인증 및 권한 부여를 사용하여 애플리케이션 보호
이 자습서는 ASP.NET MVC 1을 사용하여 작지만 완전한 웹 애플리케이션을 빌드하는 방법을 안내하는 무료 "NerdDinner" 애플리케이션 자습서 의 9단계입니다.
9단계에서는 NerdDinner 애플리케이션을 보호하기 위해 인증 및 권한 부여를 추가하여 사용자가 새 저녁 식사를 만들기 위해 사이트에 등록하고 로그인해야 하며 저녁 식사를 호스팅하는 사용자만 나중에 편집할 수 있도록 하는 방법을 보여 줍니다.
ASP.NET MVC 3을 사용하는 경우 MVC 3 또는 MVC Music Store 시작 따라가는 것이 좋습니다.
NerdDinner 9단계: 인증 및 권한 부여
현재 NerdDinner 애플리케이션은 사이트를 방문하는 모든 사람에게 저녁 식사의 세부 정보를 만들고 편집할 수 있는 기능을 부여합니다. 사용자가 사이트에 등록하고 로그인하여 새 저녁 식사를 만들고 저녁 식사를 호스팅하는 사용자만 나중에 편집할 수 있도록 제한을 추가하도록 변경해 보겠습니다.
이를 사용하도록 설정하려면 인증 및 권한 부여를 사용하여 애플리케이션을 보호합니다.
인증 및 권한 부여 이해
인증 은 애플리케이션에 액세스하는 클라이언트의 ID를 식별하고 유효성을 검사하는 프로세스입니다. 간단히 말해서, 최종 사용자가 웹 사이트를 방문할 때 "누구"를 식별하는 것입니다. ASP.NET 브라우저 사용자를 인증하는 여러 방법을 지원합니다. 인터넷 웹 애플리케이션의 경우 사용되는 가장 일반적인 인증 접근 방식을 "Forms Authentication"이라고 합니다. Forms Authentication을 사용하면 개발자가 애플리케이션 내에서 HTML 로그인 양식을 작성한 다음 최종 사용자가 데이터베이스 또는 기타 암호 자격 증명 저장소에 대해 제출하는 사용자 이름/암호의 유효성을 검사할 수 있습니다. 사용자 이름/암호 조합이 올바른 경우 개발자는 ASP.NET 암호화된 HTTP 쿠키를 발급하여 향후 요청에서 사용자를 식별하도록 요청할 수 있습니다. NerdDinner 애플리케이션에서 양식 인증을 사용합니다.
권한 부여 는 인증된 사용자에게 특정 URL/리소스에 액세스하거나 일부 작업을 수행할 수 있는 권한이 있는지 여부를 결정하는 프로세스입니다. 예를 들어 NerdDinner 애플리케이션 내에서 로그인한 사용자만 /Dinners/Create URL에 액세스하고 새 Dinners를 만들 수 있도록 권한을 부여하려고 합니다. 또한 저녁 식사를 호스팅하는 사용자만 편집할 수 있도록 권한 부여 논리를 추가하고 다른 모든 사용자에 대한 편집 액세스를 거부하려고 합니다.
양식 인증 및 AccountController
ASP.NET MVC에 대한 기본 Visual Studio 프로젝트 템플릿은 새 ASP.NET MVC 애플리케이션을 만들 때 양식 인증을 자동으로 사용하도록 설정합니다. 또한 미리 빌드된 계정 로그인 페이지 구현을 프로젝트에 자동으로 추가하므로 사이트 내에서 보안을 쉽게 통합할 수 있습니다.
기본 사이트입니다. master master 페이지에는 액세스하는 사용자가 인증되지 않은 경우 사이트 오른쪽 위에 "로그온" 링크가 표시됩니다.
"로그온" 링크를 클릭하면 사용자가 /Account/LogOn URL로 이동합니다.
등록하지 않은 방문자는 "등록" 링크를 클릭하여 / Account/Register URL로 이동하여 계정 세부 정보를 입력할 수 있습니다.
"등록" 단추를 클릭하면 ASP.NET 멤버 자격 시스템 내에 새 사용자가 생성되고 양식 인증을 사용하여 사이트에 사용자를 인증합니다.
사용자가 로그인하면 사이트입니다. master 페이지의 오른쪽 상단을 변경하여 "시작 [사용자 이름]!" 메시지를 출력하고 "로그온" 링크 대신 "로그오프" 링크를 렌더링합니다. "로그오프" 링크를 클릭하면 사용자가 로그아웃됩니다.
위의 로그인, 로그아웃 및 등록 기능은 프로젝트를 만들 때 Visual Studio에서 프로젝트에 추가한 AccountController 클래스 내에서 구현됩니다. AccountController의 UI는 \Views\Account 디렉터리 내의 보기 템플릿을 사용하여 구현됩니다.
AccountController 클래스는 ASP.NET Forms 인증 시스템을 사용하여 암호화된 인증 쿠키를 발급하고 ASP.NET 멤버 자격 API를 사용하여 사용자 이름/암호를 저장하고 유효성을 검사합니다. ASP.NET 멤버 자격 증명 API는 확장 가능하며 모든 암호 자격 증명 저장소를 사용할 수 있습니다. ASP.NET SQL 데이터베이스 또는 Active Directory 내에서 사용자 이름/암호를 저장하는 기본 제공 멤버 자격 공급자 구현과 함께 제공됩니다.
프로젝트의 루트에서 "web.config" 파일을 열고 그 안에 있는 멤버 자격 섹션을 찾아 NerdDinner 애플리케이션에서 사용해야 하는 멤버 자격> 공급자를 <구성할 수 있습니다. 프로젝트를 만들 때 추가된 기본 web.config SQL 멤버 자격 공급자를 등록하고 "ApplicationServices"라는 연결 문자열을 사용하여 데이터베이스 위치를 지정하도록 구성합니다.
기본 "ApplicationServices" 연결 문자열(web.config 파일의 connectionStrings> 섹션 내에 <지정됨)은 SQL Express를 사용하도록 구성됩니다. "ASPNETDB"라는 SQL Express 데이터베이스를 가리킵니다. 애플리케이션의 "App_Data" 디렉터리 아래에 있는 MDF입니다. 이 데이터베이스가 애플리케이션 내에서 멤버 자격 API를 처음 사용할 때 존재하지 않는 경우 ASP.NET 자동으로 데이터베이스를 만들고 해당 데이터베이스 내에 적절한 멤버 자격 데이터베이스 스키마를 프로비전합니다.
SQL Express를 사용하는 대신 전체 SQL Server instance 사용하거나 원격 데이터베이스에 연결하려는 경우 web.config 파일 내에서 "ApplicationServices" 연결 문자열을 업데이트하고 해당 멤버 자격 스키마가 가리키는 데이터베이스에 추가되었는지 확인하기만 하면 됩니다. \Windows\Microsoft.NET\Framework\v2.0.50727\ 디렉터리 내에서 "aspnet_regsql.exe" 유틸리티를 실행하여 멤버 자격 및 다른 ASP.NET 애플리케이션 서비스에 적절한 스키마를 데이터베이스에 추가할 수 있습니다.
[권한 부여] 필터를 사용하여 /Dinners/Create URL 권한 부여
NerdDinner 애플리케이션에 대한 보안 인증 및 계정 관리 구현을 사용하도록 설정하는 코드를 작성할 필요가 없었습니다. 사용자는 애플리케이션에 새 계정을 등록하고 사이트의 로그인/로그아웃을 수행할 수 있습니다.
이제 애플리케이션에 권한 부여 논리를 추가하고 방문자의 인증 상태 및 사용자 이름을 사용하여 사이트 내에서 수행할 수 있는 작업과 수행할 수 없는 작업을 제어할 수 있습니다. 먼저 DinnersController 클래스의 "만들기" 작업 메서드에 권한 부여 논리를 추가해 보겠습니다. 특히 /Dinners/Create URL에 액세스하는 사용자를 로그인해야 합니다. 로그인하지 않은 경우 로그인할 수 있도록 로그인 페이지로 리디렉션됩니다.
이 논리를 구현하는 것은 매우 쉽습니다. 다음과 같이 만들기 작업 메서드에 [Authorize] 필터 특성을 추가하기만 하면됩니다.
//
// GET: /Dinners/Create
[Authorize]
public ActionResult Create() {
...
}
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(Dinner dinnerToCreate) {
...
}
ASP.NET MVC는 작업 메서드에 선언적으로 적용할 수 있는 재사용 가능한 논리를 구현하는 데 사용할 수 있는 "작업 필터"를 만드는 기능을 지원합니다. [Authorize] 필터는 ASP.NET MVC에서 제공하는 기본 제공 작업 필터 중 하나이며 개발자가 작업 메서드 및 컨트롤러 클래스에 권한 부여 규칙을 선언적으로 적용할 수 있습니다.
위의 매개 변수 없이 적용된 경우 [Authorize] 필터는 작업 메서드 요청을 만드는 사용자가 로그인해야 하며, 그렇지 않으면 브라우저를 로그인 URL로 자동으로 리디렉션합니다. 이 리디렉션을 수행할 때 원래 요청된 URL이 querystring 인수로 전달됩니다(예: /Account/LogOn? ReturnUrl=%2fDinners%2fCreate). 그러면 AccountController가 로그인하면 사용자를 원래 요청된 URL로 다시 리디렉션합니다.
[권한 부여] 필터는 필요에 따라 사용자가 로그인하고 허용된 사용자 또는 허용된 보안 역할의 멤버 목록 내에 있어야 하는 데 사용할 수 있는 "사용자" 또는 "역할" 속성을 지정하는 기능을 지원합니다. 예를 들어 아래 코드는 "scottgu" 및 "billg" 두 명의 특정 사용자만 /Dinners/Create URL에 액세스할 수 있도록 허용합니다.
[Authorize(Users="scottgu,billg")]
public ActionResult Create() {
...
}
하지만 코드에 특정 사용자 이름을 포함하는 것은 유지 관리가 불가능한 경향이 있습니다. 더 나은 방법은 코드에서 검사하는 상위 수준 "역할"을 정의한 다음 데이터베이스 또는 Active Directory 시스템을 사용하여 사용자를 역할에 매핑하는 것입니다(실제 사용자 매핑 목록을 코드에서 외부로 저장할 수 있도록 설정). ASP.NET 기본 제공 역할 관리 API뿐만 아니라 이 사용자/역할 매핑을 수행하는 데 도움이 되는 기본 제공 역할 공급자 집합(SQL 및 Active Directory용 역할 공급자 포함)이 포함되어 있습니다. 그런 다음 특정 "관리자" 역할 내의 사용자만 /Dinners/Create URL에 액세스할 수 있도록 코드를 업데이트할 수 있습니다.
[Authorize(Roles="admin")]
public ActionResult Create() {
...
}
저녁 식사를 만들 때 User.Identity.Name 속성 사용
Controller 기본 클래스에 노출된 User.Identity.Name 속성을 사용하여 현재 로그인한 요청 사용자의 사용자 이름을 검색할 수 있습니다.
이전에 Create() 작업 메서드의 HTTP-POST 버전을 구현할 때 Dinner의 "HostedBy" 속성을 정적 문자열로 하드 코딩했습니다. 이제 User.Identity.Name 속성을 대신 사용하도록 이 코드를 업데이트하고 Dinner를 만드는 호스트에 대한 RSVP를 자동으로 추가할 수 있습니다.
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(Dinner dinner) {
if (ModelState.IsValid) {
try {
dinner.HostedBy = User.Identity.Name;
RSVP rsvp = new RSVP();
rsvp.AttendeeName = User.Identity.Name;
dinner.RSVPs.Add(rsvp);
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
ModelState.AddModelErrors(dinner.GetRuleViolations());
}
}
return View(new DinnerFormViewModel(dinner));
}
Create() 메서드에 [Authorize] 특성을 추가했으므로 ASP.NET MVC는 /Dinners/Create URL을 방문하는 사용자가 사이트에 로그인한 경우에만 작업 메서드가 실행되도록 합니다. 따라서 User.Identity.Name 속성 값에는 항상 유효한 사용자 이름이 포함됩니다.
저녁 식사를 편집할 때 User.Identity.Name 속성 사용
이제 사용자가 직접 호스팅하는 저녁 식사의 속성만 편집할 수 있도록 사용자를 제한하는 권한 부여 논리를 추가해 보겠습니다.
이를 위해 먼저 Dinner 개체에 "IsHostedBy(username)" 도우미 메서드를 추가합니다(앞에서 빌드한 Dinner.cs 부분 클래스 내). 이 도우미 메서드는 제공된 사용자 이름이 Dinner HostedBy 속성과 일치하는지 여부에 따라 true 또는 false를 반환하고 대/소문자를 구분하지 않는 문자열 비교를 수행하는 데 필요한 논리를 캡슐화합니다.
public partial class Dinner {
public bool IsHostedBy(string userName) {
return HostedBy.Equals(userName, StringComparison.InvariantCultureIgnoreCase);
}
}
그런 다음 DinnersController 클래스 내의 Edit() 작업 메서드에 [Authorize] 특성을 추가합니다. 이렇게 하면 사용자가 로그인하여 /Dinners/Edit/[id] URL을 요청해야 합니다.
그런 다음 Dinner.IsHostedBy(username) 도우미 메서드를 사용하는 Edit 메서드에 코드를 추가하여 로그인한 사용자가 Dinner 호스트와 일치하는지 확인할 수 있습니다. 사용자가 호스트가 아닌 경우 "InvalidOwner" 보기를 표시하고 요청을 종료합니다. 이 작업을 수행하는 코드는 다음과 같습니다.
//
// GET: /Dinners/Edit/5
[Authorize]
public ActionResult Edit(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (!dinner.IsHostedBy(User.Identity.Name))
return View("InvalidOwner");
return View(new DinnerFormViewModel(dinner));
}
//
// POST: /Dinners/Edit/5
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Edit(int id, FormCollection collection) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (!dinner.IsHostedBy(User.Identity.Name))
return View("InvalidOwner");
try {
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new {id = dinner.DinnerID});
}
catch {
ModelState.AddModelErrors(dinnerToEdit.GetRuleViolations());
return View(new DinnerFormViewModel(dinner));
}
}
그런 다음 \Views\Dinners 디렉터리를 마우스 오른쪽 단추로 클릭하고 추가> 보기 메뉴 명령을 선택하여 새 "InvalidOwner" 보기를 만들 수 있습니다. 아래 오류 메시지로 채웁니다.
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
You Don't Own This Dinner
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">
<h2>Error Accessing Dinner</h2>
<p>Sorry - but only the host of a Dinner can edit or delete it.</p>
</asp:Content>
이제 사용자가 소유하지 않은 저녁 식사를 편집하려고 하면 오류 메시지가 표시됩니다.
컨트롤러 내의 Delete() 작업 메서드에 대해 동일한 단계를 반복하여 Dinners를 삭제할 수 있는 권한을 잠그고 Dinner 호스트만 삭제할 수 있는지 확인할 수 있습니다.
링크 편집 및 삭제 표시/숨기기
세부 정보 URL에서 DinnersController 클래스의 편집 및 삭제 작업 메서드에 연결합니다.
현재 세부 정보 URL에 대한 방문자가 저녁 식사의 호스트인지 여부에 관계없이 편집 및 삭제 작업 링크가 표시됩니다. 방문한 사용자가 저녁 식사의 소유자인 경우에만 링크가 표시되도록 변경해 보겠습니다.
DinnersController 내의 Details() 작업 메서드는 Dinner 개체를 검색한 다음, 모델 개체로 뷰 템플릿에 전달합니다.
//
// GET: /Dinners/Details/5
public ActionResult Details(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (dinner == null)
return View("NotFound");
return View(dinner);
}
아래와 같이 Dinner.IsHostedBy() 도우미 메서드를 사용하여 편집 및 삭제 링크를 조건부로 표시/숨기도록 보기 템플릿을 업데이트할 수 있습니다.
<% if (Model.IsHostedBy(Context.User.Identity.Name)) { %>
<%= Html.ActionLink("Edit Dinner", "Edit", new { id=Model.DinnerID }) %> |
<%= Html.ActionLink("Delete Dinner", "Delete", new {id=Model.DinnerID}) %>
<% } %>
다음 단계
이제 AJAX를 사용하여 저녁 식사를 위해 인증된 사용자를 RSVP에 사용하도록 설정하는 방법을 살펴보겠습니다.