저자: 윌무브 && 히스 스튜어트
홈페이지: http://www.amuhouse.com
이메일: [email protected]
참고: 저는 두 달 전에 ASP.NET을 배웠습니다. codeproject.com에서 양식 인증을 사용한 역할 기반 보안이라는 제목의 기사를 보고 매우 도움이 되었습니다. 그 당시 나는 그것을 중국어로 번역하고 싶었습니다. 그런데 직역은 정말 지루합니다. 지난 이틀 동안 히스 스튜어트가 쓴 이 글을 참고해서 제 나름의 이해를 바탕으로 제 생각과 표현에 맞춰 중국어로 썼습니다. 첨부된 내용은 이 기사를 위해 만든 데모 웹 애플리케이션입니다.
혹시 오해가 있는 부분이 있으면 글을 써서 지적하거나 댓글을 남겨주세요.
P.S. 스팸은 정말 짜증나는 일이니 존중해주세요.
원본 기사는 http://www.codeproject.com/aspnet/formsroleauth.asp 에 있습니다.
원저자Heath Stewart
요약:
ASP.NET은 역할 기반 인증 메커니즘을 제공하지만 역할에 대한 지원은 불완전합니다. 이 문서에서는 몇 가지 예를 통해 역할 기반 인증 메커니즘을 구현하고 사용하는 방법을 설명합니다.
소개:
ASP.NET의 폼 인증은 간단한 플랫폼 독립적 보안 인증 시스템을 구현하는 데 약간의 코드만 필요한 매우 강력한 기능입니다.
그러나 보다 복잡하고 효율적인 인증 메커니즘이 필요한 경우 많은 사용자를 사용자 그룹으로 나누어 유연성을 활용해야 합니다. Windows 통합 인증은 이 인증 메커니즘을 제공하지만 Windows NT LAN Manager인 NTLM을 사용하므로 크로스 플랫폼이 아닙니다. 이제 점점 더 많은 사람들이 Linux 시스템을 사용하고 있으며 Mozilla Forefox 브라우저 사용자도 점점 더 많아지고 있습니다. 우리는 확실히 이러한 사람들을 막을 수 없으므로 다른 인증 메커니즘을 찾고 있습니다. 두 가지 옵션이 있습니다. 하나는 웹 사이트를 여러 영역으로 나누고 여러 로그인 페이지를 제공하여 사용자가 하나씩 등록하고 로그인하도록 하는 것입니다. 다른 하나는 사용자를 그룹화하고 특정 사용자 그룹의 액세스 권한을 특정 페이지로 제한하는 것입니다. 또는 지역. 후자가 확실히 더 나은 선택입니다. 개별 사용자에게 역할을 할당하여 이 기능을 달성할 수 있습니다.
Microsoft는 .NET 플랫폼의 양식 인증에 역할 기반 인증 메커니즘을 남겨두었지만 이를 직접 구현해야 합니다. 이 기사에서는 개념, 구현, 웹 애플리케이션에 적용하는 방법 등과 같은 양식 인증의 역할 기반 인증 메커니즘에 대한 몇 가지 기본 사항을 다루려고 노력합니다.
필요한 준비:
먼저 데이터베이스, 웹 응용 프로그램 프로젝트, 보안 수준이 서로 다른 여러 기밀 디렉터리 및 여러 ASP.NET 페이지를 만들어야 합니다. 물론 이를 기존 웹 애플리케이션 프로젝트에 추가할 수도 있습니다.
1. 데이터베이스를 생성하려면
먼저 사용할 데이터베이스 관리 시스템인 DBMS를 선택해야 합니다. 이 문서에서는 SQL Server 2000을 사용합니다.
실제 애플리케이션 프로젝트의 데이터베이스에는 일반적으로 사용자 데이터 테이블인 Users가 있으며, 여기에는 사용자의 고유 태그: UserID, 사용자 이름: UserName, 비밀번호: 비밀번호, 사용자 이메일 주소: 이메일, 사용자 도시: 도시 및 번호가 포함될 수 있습니다. 사용자 로그인 LoginCount 등 UserInRoles 데이터 테이블(일반적으로 두 개의 필드, 사용자 이름: UserName, 사용자 역할: UserRoles 포함)을 생성하여 사용자에게 역할을 할당할 수 있습니다.
단순화를 위해 사용자 이름 UserName, 비밀번호 Password 및 사용자 역할 UserRoles의 3개 필드가 있는 사용자 데이터 테이블만 생성합니다. 테이블을 생성하기 전에 데이터베이스를 선택하거나 새 데이터베이스를 생성해야 합니다. WebSolution이라는 새 데이터베이스를 생성하려면 간단한 SQL 문만 필요합니다.
프로그램 코드
데이터베이스 웹솔루션 생성
GO에서
msdb라는 데이터베이스를 선택하려면
다음 SQL 문을 사용할 수 있습니다.
프로그램 코드
msdb 사용
가다
다음으로 방금 언급한 사용자 데이터 테이블을 생성합니다. SQL 스크립트는 다음과 같습니다.
프로그램 코드
TABLE 사용자 생성
(
사용자 이름 nvarchar(100) CONSTRAINT PK_UserName PRIMARY KEY,
비밀번호 nvarchar(150),
사용자 역할 nvarchar(100)
)은
이 테이블에 대한 인덱스 자격 증명을 생성할 수 있습니다. SQL 문은 다음과 같습니다.
프로그램 코드
Create INDEX Credentials ON Users.
(
사용자 이름,
비밀번호
)
색인 생성은 선택 사항이며 귀하에게 달려 있습니다. 인덱싱의 장점과 단점은 관련 정보를 참조하세요.
그런 다음 이 사용자 데이터베이스에 데이터를 추가합니다. 캐릭터 이름은 본인이 선택하되, 다음과 같이 의미 있는 이름을 사용하는 것이 가장 좋습니다.
"Administrator"(최상위 관리자), "Manager"(관리자), "Member"(가입 회원), "User"(일반 사용자) 등 예:
사용자 이름|비밀번호|역할
"willmove"|"pwd123"|"관리자,사용자"
"amuhouse"|"pwd123"|"User"
다음과 같습니다.
프로그램 코드
--'45CB41B32DCFB917CCD8614F1536D6DA'는 'pwd123'을 사용하여 md5로 암호화된 문자열입니다.
INTO 사용자 삽입(사용자 이름,비밀번호,사용자 역할) VALUES ('willmove','45CB41B32DCFB917CCD8614F1536D6DA','관리자,사용자')
가다
INTO Users(UserName,Password,UserRoles) VALUES ('amuhouse','45CB41B32DCFB917CCD8614F1536D6DA','User') 삽입
가다
역할은 Web.config 파일에서 대소문자를 구분하므로 대소문자를 구분합니다. 이제 이 보안 인증 메커니즘을 구현하는 데 필요한 여러 페이지를 만듭니다.
첫 번째는 사용자 로그인 페이지 Login.aspx입니다.
아직 웹 애플리케이션을 생성하지 않았다면 지금 생성하세요. 물론 기존 웹 애플리케이션에서 이 페이지를 만들 수도 있습니다. 여기에서는 RolebasedAuth라는 웹 애플리케이션이 생성되었다고 가정합니다(예: Visual Studio .Net의 프로젝트). 이 Login.aspx를 http://localhost/RolebasedAuth/Login.aspx를 통해 액세스할 수 있는 루트 디렉터리에 넣었습니다.
이 Login.aspx가 어디에 있는지는 중요하지 않지만 대중이 액세스할 수 있어야 합니다.
애플리케이션 루트 경로 아래에 Admin 및 User라는 두 개의 비밀 하위 디렉터리를 만듭니다.
다음으로 역할 인증을 지원하는 양식 인증 로그인 시스템을 만듭니다. Microsoft는 간단한 구현 메커니즘을 제공하지 않기 때문에 인증 티켓을 직접 만드는 데 시간을 투자해야 합니다. 소량의 정보를 저장해야 합니다. 물론 일부 이름은 Web.config에 구성된 이름과 동일해야 합니다. 그렇지 않으면 ASP.NET은 인증 티켓이 유효하지 않다고 판단하여 로그인 페이지로 리디렉션하도록 합니다. VS.NET의 Login.aspx에 두 개의 TextBox 컨트롤을 추가하고 이름을 UserNameTextBox 및 PasswordTextBox로 지정합니다. 또한 Button을 추가하고 LoginButton이라는 이름을 지정합니다. LoginButton_Click 메서드에 필수 코드를 추가합니다. 다음과 같이:
프로그램 코드
개인 무효 LoginButton_Click(개체 보낸 사람, System.EventArgs e)
{
//FormsAuthentication 초기화
// System.Web.Security 네임스페이스에 있음을 참고하세요.
// 따라서 코드 시작 부분에 System.Web.Security를 사용하여 추가합니다.
FormsAuthentication.Initialize ();
// 데이터베이스 연결 및 데이터베이스 작업 명령어 객체 생성
// System.Data.SqlClient 네임스페이스에 있음을 참고하세요.
// 따라서 코드 시작 부분에 System.Data.SqlClient를 사용하여 추가합니다.
SqlConnection 연결 =
new SqlConnection("데이터 소스=sun-willmove;통합 보안=SSPI;초기 카탈로그=웹 솔루션;");
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = " UserName=@username인 사용자로부터 UserRoles 선택" +
"AND 비밀번호=@password ";
// 각 매개변수를 입력합니다.
cmd.Parameters.Add("@username", SqlDbType.NVarChar, 100).Value =
사용자이름텍스트박스.텍스트;
cmd.Parameters.Add("@password", SqlDbType.NVarChar, 150).Value =
FormsAuthentication.HashPasswordForStoringInConfigFile(
PasswordTextBox.Text, "md5") // 또는 "sha1"
// 데이터베이스 작업 명령 실행
conn.Open();
SqlDataReader 리더 = cmd.ExecuteReader();
if (리더.읽기())
{
// 인증을 구현하려면 새 티켓을 만듭니다.
FormsAuthenticationTicket 티켓 = 새로운 FormsAuthenticationTicket(
1, // 티켓 버전 번호
UserNameTextBox.Text, // 티켓 소지자
DateTime.Now, //티켓 할당 시간
DateTime.Now.AddMinutes(30), //만료 시간
true, //사용자의 쿠키가 필요합니다.
reader.GetString(0), // 사용자 데이터, 실제로는 사용자의 역할입니다.
FormsAuthentication.FormsCookiePath);//유효한 쿠키 경로
//안전한 전송을 위해 기계 코드 기계 키를 사용하여 쿠키를 암호화합니다.
문자열 해시 = FormsAuthentication.Encrypt(티켓);
HttpCookie 쿠키 = 새로운 HttpCookie(
FormsAuthentication.FormsCookieName, // 인증 쿠키의 이름
hash); //암호화된 쿠키
//쿠키 만료 시간을 티켓 만료 시간과 일치하도록 설정
if (ticket.IsPertant) cookie.Expires = ticket.Expiration
//페이지 요청 응답에 쿠키를 추가합니다.
Response.Cookies.Add(cookie);
//이전에 요청한 페이지로 사용자를 리디렉션합니다.
// 이전에 요청한 페이지가 없으면 홈페이지로 리디렉션합니다.
string returnUrl = Request.QueryString["ReturnUrl"];
if (returnUrl == null) returnUrl = "./";
// FormsAuthentication.RedirectFromLoginPage 메서드를 호출하지 마세요.
// 방금 추가한 티켓(쿠키)을 대체하기 때문에
Response.Redirect(returnUrl);
}
또 다른
{
// 사용자에게 "비밀번호가 잘못되었습니다"라고 말하지 마세요. 이는 침입자에게 기회를 주는 것과 같습니다.
// 입력한 사용자 이름이 존재한다는 것을 알고 있기 때문입니다.
//
ErrorLabel.Text = "사용자 이름 또는 비밀번호가 잘못되었습니다. 다시 시도하십시오!";
ErrorLabel.Visible = true;
}
리더.닫기();
conn.Close();
}
프런트 엔드 aspx 페이지 코드는 다음과 같습니다.
프로그램 코드
<%@ 페이지 언어="c#" Codebehind="Login.aspx.cs" AutoEventWireup="false" Inherits="RolebasedAuth.Login" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<헤드>
<title>로그인</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema" content=" http://schemas.microsoft.com/intellisense/ie5 ">
</HEAD>
<본문>
<form id="Form1" method="post" runat="서버">
<피>
<asp:Label id="Label1" runat="server">사용자 이름:</asp:Label>
<asp:TextBox id="UserNameTextBox" runat="server"></asp:TextBox></P>
<P><FONT Face="宋体"> </FONT>
<asp:Label id="Label2" runat="server">비밀번호:</asp:Label>
<asp:TextBox id="PasswordTextBox" runat="server" TextMode="Password"></asp:TextBox></P>
<피>
<asp:Label id="ErrorLabel" runat="server" Visible="False"></asp:Label></P>
<피>
<asp:Button id="LoginButton" runat="server" Text="Login"></asp:Button></P>
</form>
</body>
</HTML>
위의 비밀번호를 사용하여 해싱한 작업을 알 수 있습니다. 해시 암호화는 고유한 문자 배열을 생성하는 단방향 알고리즘(되돌릴 수 없음)입니다. 따라서 비밀번호에서 한 글자라도 대소문자를 변경하면 완전히 다른 해시 열이 생성됩니다. 우리는 이러한 암호화된 비밀번호를 데이터베이스에 저장하므로 더욱 안전합니다. 실제 애플리케이션에서는 사용자의 잊어버린 비밀번호를 검색할 수 있습니다. 하지만 해싱은 되돌릴 수 없으므로 원래 비밀번호를 복구할 수 없습니다. 하지만 사용자의 비밀번호를 변경하고 변경된 비밀번호를 알려줄 수 있습니다. 웹사이트에서 이전 비밀번호를 제공할 수 있다면 사용자 데이터는 안전하지 않다는 점을 명확하게 생각해야 합니다. 실제로 국내 대부분의 웹사이트는 사용자 비밀번호를 암호화하지 않고 데이터베이스에 직접 저장하고 있다. 해커가 성공하면 해당 사용자 계정이 위험해집니다!
SSL이 없으면 귀하의 비밀번호는 네트워크를 통해 일반 텍스트로 전송됩니다. 전송 중에 도난당할 수 있습니다. 서버 측에서 비밀번호를 암호화하면 비밀번호 저장의 보안만 보장됩니다. SSL 관련 정보는 http://www.versign.com 또는 http://www.thewte.com 에서 확인할 수 있습니다.
암호화된 데이터베이스에 비밀번호를 저장하지 않으려면 위 코드를 다음과 같이 변경할 수 있습니다.
FormsAuthentication.HashPasswordForStoringInConfigFile(PasswordTextBox.Text, "md5")을 PasswordTextBox.Text로 변경할 수 있습니다.
다음으로 Global.asax 파일을 수정해야 합니다. 웹 애플리케이션에 이 파일이 없으면 웹 애플리케이션 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 "추가->새 항목 추가...->글로벌 애플리케이션 클래스"를 선택하십시오. Global.asax 또는 Global.asax.cs에서 Application_AuthenticationRequest라는 메서드(함수)를 찾으세요. 먼저 System.Security.Principal 및 System.Web.Security 네임스페이스가 포함되었거나 사용되었는지 확인한 후 수정된 코드를 수정합니다.
프로그램 코드
protected void Application_AuthenticateRequest(객체 전송자, EventArgs e)
{
if(HttpContext.Current.User != null)
{
if(HttpContext.Current.User.Identity.IsAuthenticated)
{
if(HttpContext.Current.User.Identity가 FormsIdentity임)
{
FormsIdentity ID =
(FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
// 여기서는 실제로 사용자의 역할인 티켓에 저장된 사용자 데이터를 가져옵니다.
문자열 userData = ticket.UserData;
string[] 역할 = userData.Split(',');
HttpContext.Current.User = new GenericPrincipal(id, 역할);
}
}
}
}
인증 티켓(사용자 이름 및 비밀번호)은 쿠키의 일부로 저장되지 않으며 사용자가 쿠키를 수정할 수 있으므로 저장될 수 없습니다.
실제로 FormsAuthentication은 컴퓨터 키(일반적으로 machine.config에 있음)를 사용하여 티켓(FormsAuthenticationTicket)을 암호화합니다. UserData를 사용하여 사용자 역할을 저장하고 새 자격 증명을 생성합니다. 자격 증명이 생성되면 현재 컨텍스트(예: HttpContext)에 추가되어 사용자의 역할을 검색하는 데 사용할 수 있습니다.
다음으로 비밀 디렉터리(즉, 관리자 등 특정 사용자만 접근 권한을 갖는 디렉터리인 "보안 디렉터리")를 설정합니다. 먼저 웹 애플리케이션의 루트 디렉터리에 Web.config 파일이 있는지 확인하세요. 없으면 새로 만드세요. 하위 디렉터리에 Web.config 파일을 생성할 수도 있습니다. 물론 이 Web.config 파일은 제한되어 있습니다(일부 매개 변수는 설정할 수 없음).
보안 인증을 구현하려면웹 응용 프로그램 루트 디렉터리에 있는 Web.config 파일의 <system.web> 노드에서
프로그램 코드를
찾으세요.<authentication mode="Forms">
로 변경하세요
.
<양식 이름="AMUHOUSE.ASPXAUTH"
loginUrl="로그인.aspx"
보호="모두"
경로="./" />
</인증>
<인가>
<사용자 허용="*"/>
</authorization>
위의 name="AMUHOUSE.ASPXAUTH"에서 AMUHOUSE.ASPXAUTH라는 이름은 임의적입니다. 사용자 또는 사용자 그룹의 권한을 제어하려면 두 가지 방법이 있습니다. 하나는 응용 프로그램 루트 디렉터리에 Web.config 파일을 구성하는 것이고, 다른 하나는 비밀 디렉터리에 독립적인 Web.config 파일을 만드는 것입니다. (후자가 더 나을 수도 있습니다.) 전자인 경우 Web.config에는 다음 콘텐츠(또는 유사한 콘텐츠)가 포함되어야 합니다.
프로그램 코드
<구성>
<시스템.웹>
<인증 모드="양식">
<양식 이름="AMUHOUSE.ASPXAUTH"
loginUrl="login.aspx"
보호="모두"
경로="/"/>
</인증>
<인가>
<사용자 허용="*"/>
</인증>
</system.web>
<위치 경로="./Admin">
<시스템.웹>
<인가>
<!-- 주의! 다음 줄의 순서와 대소문자는 매우 중요합니다! -->
<역할 허용="관리자"/>
<사용자 거부="*"/>
</인증>
</system.web>
</위치>
<위치 경로="./사용자">
<시스템.웹>
<인가>
<!-- 주의! 다음 줄의 순서와 대소문자는 매우 중요합니다! -->
<역할 허용="사용자"/>
<사용자 거부="*"/>
</인증>
</system.web>
</위치>
</구성>
이전에 웹 응용 프로그램의 디렉터리가 서로 종속되지 않도록 하고 이름을 바꾸거나 이동하기 쉽게 하려면 각 보안 하위 디렉터리에 별도의 Web.config 파일을 구성하도록 선택할 수 있습니다. 다음과 같이 <authorization/> 노드만 구성하면 됩니다.
프로그램 코드
<구성>
<시스템.웹>
<인가>
<!-- 주의! 다음 줄의 순서와 대소문자는 매우 중요합니다! -->
<역할 허용="관리자"/>
<사용자 거부="*"/>
</인증>
</system.web>
</구성>
위의 역할은 대소문자를 구분하므로 위의 내용을 다음과 같이 수정할 수도 있습니다.
<허용 역할="관리자,관리자" />
이 디렉터리에 대한 여러 역할의 액세스를 허용하거나 거부하려면 다음과 같이 쉼표로 구분하면 됩니다.
<허용 역할="관리자,구성원,사용자" />
<deny users="*" />
이제 웹사이트에 대한 역할 기반 보안 인증 메커니즘을 구성했습니다. 먼저 프로그램을 컴파일한 다음 http://localhost/RolebasedAuth/Admin 과 같은 비밀 디렉토리에 액세스하려고 시도하면 사용자 로그인 페이지로 리디렉션됩니다. 성공적으로 로그인하고 역할에 이 디렉터리에 대한 액세스 권한이 있으면 이 디렉터리로 돌아갑니다. 기밀 디렉터리에 진입하려는 사용자(또는 침입자)가 있을 수 있습니다. 세션을 사용하여 사용자가 로그인한 횟수를 저장할 수 있습니다. 횟수가 특정 횟수를 초과하면 사용자는 로그인이 허용되지 않습니다. 그리고 "시스템이 귀하의 로그인 요청을 거부했습니다!"가 표시됩니다.
아래에서는 웹 컨트롤이 사용자 역할에 따라 다양한 콘텐츠를 표시하도록 만드는 방법에 대해 설명합니다.
때로는 사용자의 역할에 따라 콘텐츠를 표시하는 것이 더 나을 수도 있습니다. 왜냐하면 매우 다양한 역할(사용자 그룹)에 대해 중복된 콘텐츠가 많은 페이지를 만들고 싶지 않을 것이기 때문입니다. 이러한 사이트에는 다양한 사용자 계정이 공존할 수 있으며, 유료 사용자 계정은 추가적인 유료 콘텐츠에 접근할 수 있습니다. 또 다른 예는 현재 사용자가 "관리자" 역할에 있는 경우 관리 페이지로 연결되는 "관리자 입력" 버튼을 표시하는 페이지입니다. 이제 이 페이지를 구현하겠습니다.
위에서 사용한 GenericPrincipal 클래스는 IPincipal 인터페이스를 구현합니다. 이 인터페이스에는 IsInRole()이라는 메서드가 있으며 해당 매개 변수는 문자열입니다. 역할이 "관리자"인 로그인된 사용자에게 콘텐츠를 표시하려면 Page_Load에 다음 코드를 추가할 수 있습니다.
프로그램 코드
if (User.IsInRole("관리자"))
AdminLink.Visible = true;
전체 페이지 코드는 다음과 같습니다(간단하게 하기 위해 배경 코드도 aspx 페이지에 작성됩니다).
프로그램 코드
<html>
<머리>
<제목>환영합니다! </title>
<스크립트 runat="서버">
protected void Page_Load(객체 전송자, EventArgs e)
{
if (User.IsInRole("관리자"))
AdminLink.Visible = true;
또 다른
AdminLink.Visible = 거짓;
}
</script>
</head>
<본문>
<h2>환영합니다! </h2>
<p>아뮤하우스 http://amuhouse.com/ 에 오신 것을 환영합니다 ^_^</p>
<asp:HyperLink id="AdminLink" runat="서버"
Text="관리 홈 페이지" NavigateUrl="./Admin"/>
</body>
</html>
이러한 방식으로 Admin 디렉터리에 연결된 HyperLink 컨트롤은 역할이 관리자인 사용자에게만 표시됩니다. 로그인하지 않은 사용자에게 다음과 같은 로그인 페이지 링크를 제공할 수도 있습니다.
프로그램 코드
protected void Page_Load(개체 전송자, System.EventArgs e)
{
if (User.IsInRole("관리자"))
{
AdminLink.Text = "관리자님, 들어오세요";
AdminLink.NavigateUrl="./Admin";
}
else if(User.IsInRole("사용자"))
{
AdminLink.Text = "등록된 사용자를 입력하십시오";
AdminLink.NavigateUrl="./사용자"
}
또 다른
{
AdminLink.Text = "로그인하세요";
AdminLink.NavigateUrl="Login.aspx?ReturnUrl=" + 요청.경로;
}
}
여기서 ReturnUrl이라는 QueryString 변수를 설정하면 로그인 성공 후 사용자를 현재 페이지로 되돌릴 수 있습니다.
요약:
이 문서는 역할 기반 보안 메커니즘의 중요성과 실용성을 이해하는 데 도움이 되며 ASP.NET을 사용하여 역할 기반 보안 메커니즘을 구현합니다. 구현하기 어려운 메커니즘은 아니지만 사용자 자격 증명이 무엇인지, 사용자를 인증하는 방법, 인증된 사용자를 인증하는 방법에 대한 지식이 필요할 수 있습니다. 도움이 되셨다면 정말 기쁘겠습니다. 귀하의 웹 사이트에서 역할 기반 양식 보안 인증을 구현하는 데 도움이 되기를 바랍니다.
첨부된:
이 기사의 샘플 프로젝트 소스 코드: