.NET을 이용한 모듈 개발

등록일시: 2008-03-06 16:59,  수정일시: 2013-11-29 12:05
조회수: 7,441
이 문서는 IIS 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.

서론

다음과 같은 두 가지 방식으로 모듈을 개발해서 IIS 7을 확장할 수 있습니다:

  1. 관리되는 코드와 ASP.NET 서버 확장 API를 사용하는 방식.
  2. 네이티브 코드와 IIS 7 네이티브 서버 확장 API를 사용하는 방식.

지금까지는 ASP.NET 요청 처리 파이프라인과 서버의 메인 요청 파이프라인이 별개로 분리되어 있었기 때문에 ASP.NET 모듈의 기능이 제한적일 수 밖에 없었습니다. IIS 7의 통합 파이프라인 아키텍처는 사실상 관리되는 모듈의 기능을 네이티브 모듈과 비슷한 수준까지 강력하게 끌어올렸습니다. 가장 중요한 점은, 관리되는 모듈에 의해 제공되는 서비스들이 ASPX 페이지 같은 ASP.NET 콘텐츠에 대한 요청뿐만 아니라 서버에 대한 모든 요청에 적용된다는 사실입니다. 관리되는 모듈은 네이티브 모듈과 동일한 방식으로 구성 및 관리되며, 동일한 처리 단계와 순서로 실행됩니다. 마지막으로 관리되는 모듈은 ASP.NET의 기본 기능을 향상시키고 확장하기 위해 요청 처리를 제어하는 폭넓은 작업을 수행할 수 있습니다.

본문에서는 ASP.NET 2.0 멤버쉽 시스템이 제공해주는 강력한 공급자-기반 신원 증명 기반구조 같이, 임의의 신원 증명 저장소를 사용해서 기본 인증을 수행하는 기능을 구현한 관리되는 모듈로 서버를 확장하는 구체적인 방법을 살펴봅니다. 이 모듈은 IIS 7이 기본으로 제공해주는 윈도우 신원 증명 저장소와 밀접하게 결합된 내장 기본 인증 기능을 대체해서, 임의의 신원 증명 저장소를 지원하거나 ASP.NET 2.0과 함께 제공되는 SQL 서버나 SQL 익스프레스, 또는 액티브 디렉터리 등의 멤버쉽 공급자를 지원하는 기본 인증을 사용할 수 있게 해줍니다. *

본문에서는 다음과 같은 내용들을 살펴봅니다:

  • ASP.NET API를 사용해서 관리되는 모듈을 개발해봅니다.
  • 관리되는 모듈을 서버에 배포해봅니다.

만약, IIS 7 모듈이나 헨들러 개발에 관한 기본적인 내용들을 살펴보고 싶다면 .NET 프레임워크를 사용해서 IIS 7 모듈 및 헨들러 개발하기를 살펴보시기 바랍니다.

그리고, 필자의 블로그(http://www.mvolo.com/)에서 IIS 7 모듈 개발에 대한 다양한 자료와 팁을 살펴보거나, 응용 프로그램에 적용할 수 있는 여러 가지 IIS 7 모듈들을 다운로드 받을 수 있습니다. 가령, HttpRedirection 모듈을 사용해서 응용 프로그램에 대한 요청 리디렉션하기, DirectoryListingModule 모듈을 사용해서 세련된 IIS 웹 사이트 디렉터리 목록 출력하기, IconHandler를 사용해서 세련된 ASP.NET 응용 프로그램 파일 아이콘 출력하기, 그리고 IIS 와 ASP.NET을 사용해서 핫-링크 막기 등을 살펴보시기 바랍니다. **

노트: 본문에서는 C#으로 작성된 코드가 제공됩니다.

* 간단히 정리해보면 이렇습니다. 이미 알고 있는 것처럼 IIS 7에서도 물론 기본 인증 모듈은 제공됩니다만, 아쉬운 점은 이 기본 인증 모듈은 항상 윈도우 사용자 계정을 기반으로 사용자를 인증한다는 사실입니다. 근본적으로, SQL 서버에 저장된 아이디와 비밀번호 같은 다른 사용자 신원 정보를 사용할 수 있는 방법이 없는 것입니다. 본문에서는 아예 자신의 입맛에 맞게 모듈을 하나 만든 다음, 이 새로운 모듈로 기존의 기본 인증 모듈을 대체하는 방법을 보여줍니다. 이런 기능은 IIS 7 이전에는 ISAPI 필터를 사용해야만 구현이 가능했던 기능입니다.

** 여기에서 말하는 필자란 마이크로소프트 IIS 개발팀의 프로그램 메니저인 Mike Volodarsky를 말하는 것입니다. 제가 아니므로 오해 없으시기 바랍니다.

전제조건

본문의 모든 과정을 따라해보려면 다음과 같은 IIS 기능들이 설치되어 있어야 합니다.

ASP.NET

윈도우 비스타에서는 제어판을 통해서 ASP.NET을 설치합니다. 제어판을 열고 "프로그램" - "Windows 기능 사용/사용 안 함"을 실행합니다. 그리고, "인터넷 정보 서비스" - "World Wide Web 서비스" - "응용 프로그램 개발 기능" 노드를 확장하고 "ASP.NET" 항목을 선택합니다.

롱혼 서버를 사용중이라면 서버 관리자를 실행하고 "Manage Roles" 노드를 열어서 "Web Server (IIS)" 노드를 선택합니다. "Add role services" 링크 버튼을 클릭한 다음, "Application Development" 노드의 "ASP.NET" 항목을 선택합니다.

기본 인증에 대한 배경 지식

기본 인증은 HTTP1.1 프로토콜(RFC 2617)에 정의된 인증 체계의 하나입니다. 기본 인증은 다음과 같이 고수준에서 이루어지는 표준 챌린지 기반 메커니즘을 사용합니다: *

  1. 브라우저가 특정 URL에 대한 신원 정보가 존재하지 않는 요청을 생성합니다.
  2. 만약, 지정한 URL이 인증을 요구한다면 서버는 헤더에 401 접근 제한 메시지와 기본 인증 체계를 지원한다는 지시자가 추가된 응답을 생성합니다.
  3. 브라우저가 이 응답을 받으면 설정 상태에 따라 사용자 이름과 비밀번호를 입력하는 프롬프트를 출력하고, 이 신원 정보는 다음 요청시에 요청 헤더 내부에 평문으로 포함됩니다.
  4. 마지막으로 서버는 헤더에 포함된 사용자 이름과 비밀번호를 이용해서 인증을 시도합니다.

노트: 인증 프로토콜에 대한 복잡한 설명은 본문의 범위를 벗어나는 일이기는 하지만, 기본 인증 체계가 사용자 이름과 비밀번호를 평문으로 전송하기 때문에 보안을 위해서는 SSL을 필요로 한다는 점은 한 번 깊이 생각해볼만한 가치가 있습니다. **

기본적으로 IIS 7 및 그 하위 버전에는 로컬 계정 저장소에 저장된 윈도우 계정이나, 액티브 디렉터리에 저장된 윈도우 계정을 지원하는 기본 인증 기능이 포함되어 있습니다. 본문에서 구현하게 될 모듈은 기본 인증을 사용해서 사용자 인증을 처리하기는 하지만 윈도우 계정이 아니라 ASP.NET 2.0 멤버쉽 서비스를 사용하게 될 것입니다. 이 기능은 사용자 정보를 윈도우 계정에 얽매이지 않고 SQL 서버 등의 기존 멤버쉽 공급자에 저장할 수 있는 자유를 줄 것입니다.

* 본문을 번역하면서 가장 고심했던 단어가 바로 이 "챌린지"라는 단어입니다. 번역을 하다보면 읽을 순간에는 문장의 행간을 파악해서 의미를 이해할 수 있지만, 한글로 번역하기에는 미묘하게 곤란한 경우가 종종 있습니다. 이 "챌린지"라는 단어가 바로 그런데, 단어만 번역한다면 "시도"라고 번역될 수 있겠으나 본문에서는 "챌린지"라는 발음을 그대로를 사용합니다.

** 기본 인증으로 전송되는 사용자 이름과 비밀번호는 암호화되지 않습니다. 단지 Base64로 인코딩 될 뿐인데 프로그래머가 아니더라도 약간의 요령만 있으면 쉽게 해석이 가능합니다. 제가 근무 중인 회사의 주력 솔루션인 전자입찰이나 전자계약 등의 미션 크리티컬한 업무 시스템에는 결코 적당하지 않은 인증 방식입니다. 이 문제점을 보완하기 위해서는 반드시 SSL을 적용하거나 다른 대안을 강구해야만 합니다.

같은 문제점이 FTP 서비스에도 존재하는데 IIS에서 기본적으로 제공되는 FTP 서비스는 기본 인증 방식을 통해서 인증이 이뤄집니다. 그런데, 주지한 바처럼 이 경우 사용자의 신원 정보는 거의 보호를 받을 수 없습니다. 이를 보안하기 위한 기능이 FTP 7에 비로소 추가됐으며 SSL에 대한 지원이 바로 그것입니다. 이에 대한 더 자세한 정보는 FTP 7 따라하기: FTP over SSL 구성하기를 참고하시기 바랍니다.

작업 1: .NET 을 사용한 모듈 개발

이번 작업에서는 HTTP1.1 기본 인증 체계를 지원하는 기본 인증 모듈을 개발해보려고 합니다. 이 모듈은 ASP.NET v1.0 시절부터 ASP.NET 모듈 작성을 위해서 사용해왔던 패턴과 동일한 표준 ASP.NET 모듈 패턴을 통해서 개발될 것이며 결과적으로는 IIS 7 서버를 확장하게 됩니다. 또한, 구버전의 IIS를 대상으로 작성된 기존 모듈도 IIS 7에서 사용 가능할뿐만 아니라, 해당 모듈을 사용하는 응용 프로그램도 향상된 ASP.NET 통합 모드가 제공하는 강력한 이점들을 누릴 수 있습니다.

노트: 모듈의 전체 코드는 부록 A에서 제공됩니다.

관리되는 모듈은 System.Web.IHttpModule 인터페이스를 구현한 .NET 클래스입니다. 이 클래스의 가장 중요한 기능은 IIS 요청 처리 파이프라인에서 발생되는 이벤트들 중 하나 이상의 이벤트에 이벤트 헨들러를 등록하고, IIS가 해당 이벤트에 연결된 모듈의 이벤트 헨들러를 실행할 때 일련의 의미있는 작업들을 처리하는 것입니다.

그러면, "BasicAuthenticationModule.cs"라는 이름으로 새로운 소스 파일을 생성하고 모듈 클래스를 생성합니다. (전체 소스 코드는 부록 A에서 제공됩니다):

public class BasicAuthenticationModule : System.Web.IHttpModule
{
    void Init(HttpApplication context)
    {
    }

    void Dispose()
    {
    }
}

먼저, Init 메서드의 가장 중요한 기능은 모듈의 이벤트 헨들러를 적절한 요청 파이프라인 이벤트와 연결하는 것입니다. 각각의 이벤트 헨들러 메서드들은 모듈 클래스로부터 제공되고 모듈이 제공하고자 하는 실질적인 기능들을 구현하게 됩니다. 이 부분은 뒤에서 더 자세히 살펴보도록 하겠습니다.

그리고, Dispose 메서드는 모듈의 인스턴스가 제거될 때 모듈의 각종 상태를 정리하기 위해 사용됩니다. 그러나, 일반적으로 모듈에서 별도로 해제가 필요한 특정 리소스가 사용되지 않는 한 이 메서드는 구현되지 않는 경우가 많습니다. *

Init()

모듈 클래스를 생성했다면 먼저 Init 메서드를 구현해야 합니다. 이 메서드가 수행하게 될 유일한 작업은 하나 이상의 요청 파이프라인 이벤트를 등록하는 것입니다. 이 작업은 System.EventHandler 델리게이트의 시그니처를 구현한 모듈 메서드들을 System.Web.HttpApplication의 인스턴스가 노출하는 이벤트들 중에서 적절한 이벤트에 연결함으로서 이뤄집니다:

public void Init(HttpApplication context)      
{
    //          
    // Subscribe to the authenticate event to perform the 
    // authentication. 
    // 
    context.AuthenticateRequest += new EventHandler(this.AuthenticateUser);
    
    // 
    // Subscribe to the EndRequest event to issue the 
    // challenge if necessary. 
    // 
    context.EndRequest += new EventHandler(this.IssueAuthenticationChallenge);
}

여기에서 AuthenticateUser 메서드는 모든 요청에 대해 AuthenticateRequest 이벤트가 발생할 때마다 실행됩니다. 요청에 포함된 신원 정보를 사용해서 사용자를 인증하기 위해 이 메서드를 사용할 것입니다.

그리고, IssueAuthenticationChallenge 메서드는 모든 요청에 대해 EndRequest 이벤트가 발생할 때마다 실행됩니다. 이 메서드는 인증 모듈에 의해 요청이 거부되어 인증이 필요할 때마다 클라이언트에게 기본 인증 챌린지를 발급하는 작업을 담당하게 됩니다.

AuthenticateUser()

그러면, AuthenticateUser 메서드를 구현해보겠습니다. 이 메서드는 다음과 같은 작업을 수행합니다:

  1. 전송된 요청의 헤더에 기본 인증 정보가 존재하는 경우 해당 정보를 추출해냅니다. 이 작업의 실제 구현은 ExtractBasicAuthenticationCredentials 메서드를 참고하십시오.
  2. 멤버쉽(기본 멤버쉽 공급자로 설정된)을 이용해서 제공된 신원 정보의 유효성을 검증합니다. 이 작업의 실제 구현은 ValidateCredentials 메서드를 참고하십시오.
  3. 인증에 성공하면 사용자를 증명하는 사용자 보안 주체를 생성하고 이를 요청과 연결합니다.

만약, 기본 인증 모듈이 성공적으로 사용자의 신원 정보를 얻고 유효성을 검증하면, 처리 과정의 마지막 부분에서 추후에 다른 모듈이나 응용 프로그램 코드가 접근 제어에 대한 결정을 내릴때 참고할 수 있는 인증된 사용자의 보안 주체를 생성합니다. 가령, 파이프라인의 다음 이벤트로 설정되어 있는 URL 인증 모듈은 응용 프로그램에 의해 설정된 인증 규칙을 적용하기 위해 여기에서 생성된 사용자 보안 주체를 사용할 수 있습니다.

IssueAuthenticationChallenge()

이번에는 IssueAuthenticationChallenge 메서드를 구현합니다. 이 메서드는 다음과 같은 작업을 수행합니다:

  1. 요청이 거부되었는지를 파악하기 위해 응답 상태 코드를 점검합니다.
  2. 만약, 요청이 거부되었다면 응답에 기본 인증 챌린지 헤더를 기록하여 클라이언트측 인증 과정을 유도합니다.

유틸리티 메서드

마지막으로 모듈 내부에서 사용되는 다음과 같은 유틸리티 메서드들을 구현해봅니다:

  • ExtractBasicCredentials 메서드. 이 메서드는 인증 요청 헤더로부터 기본 인증 체계에 따라 기본 인증 신원 정보를 추출합니다.
  • ValidateCredentials 메서드. 이 메서드는 멤버쉽을 사용해서 사용자의 신원 정보 유효성을 검증합니다. 멤버쉽 API는 기반 신원 증명 저장소를 추상화해주고 구성 설정을 통해 멤버쉽 공급자를 추가하거나 제거해서 신원 증명 저장소 구현을 설정할 수 있게 해줍니다.

노트: 본문에서는 멤버쉽을 통한 유효성 검사에 대해서는 설명하지 않으며 모듈에서는 단지 사용자 이름과 비밀번호가 모두 "test"인지 여부만 점검합니다. 이는 본문이 다루는 핵심적인 내용에만 집중하기 위한 것으로 실제 제품 배포시에는 이렇게 하면 안됩니다. 응용 프로그램의 멤버쉽 공급자를 구성하고 주석처리 되어 있는 ValidateCredentials 메서드 내의 멤버쉽 관련 코드에서 주석처리를 제거하기만 하면 간단하게 멤버쉽-기반 신원 정보 검증을 활성화할 수 있습니다. 이 방법에 대한 보다 자세한 설명은 부록 C에서 제공됩니다. **

* 모듈이라고 해서 Dispose 메서드를 구현할때 적용되는 특별한 규칙같은 것은 없습니다. 일반적인 클래스를 구현할 때와 마찮가지로 관리되지 않는 리소스나 사용이 끝난 즉시 해제할 필요가 있는 리소스가 모듈에서 사용되는 경우에만 Dispose 메서드를 구현하면 됩니다.

** 본문의 주제는 어디까지나 기본 인증이 아닌 .NET으로 개발한 모듈로 IIS 7의 기능을 확장하는 방법이라는 점을 기억하시기 바랍니다.

작업 2: 응용 프로그램에 모듈 배포하기

이번에는 이전 단계에서 작성한 모듈을 응용 프로그램에 배포해 보겠습니다.

응용 프로그램에 배포하기

응용 프로그램에 모듈을 배포합니다. 다음은 배포시 선택할 수 있는 옵션들입니다:

  • 모듈을 구현한 소스 파일을 응용 프로그램의 /App_Code 디렉터리에 복사합니다. 이 방식은 모듈을 컴파일할 필요가 없으며 응용 프로그램이 시작될 때 ASP.NET이 자동적으로 모듈을 컴파일하고 로드합니다. 별다른 처리없이 단지 소스 코드를 BasicAuthenticationModule.cs라는 이름으로 응용 프로그램의 /App_Code 디렉터리에 저장합니다. 다른 방식에 익숙하지 않다면 이 방식을 선택하면 됩니다.
  • 모듈을 별도의 어셈블리로 컴파일하고 응용 프로그램의 /BIN 폴더에 복사합니다. 이 방식은 응용 프로그램에서 모듈을 사용하고는 싶지만 모듈의 소스 코드는 포함시키고 싶지 않을 때 선택할 수 있는 가장 일반적인 방법입니다. 다음 명령어를 명령 프롬프트에서 실행시키면 모듈 소스 파일을 컴파일 할 수 있습니다:
    <PATH_TO_FX_SDK>csc.exe /out:BasicAuthenticationModule.dll /target:library BasicAuthenticationModule.cs
    여기서 <PATH_TO_FX_SDK>는 CSC.EXE 컴파일러가 위치한 .NET 프레임워크 SDK의 경로입니다.
  • 모듈을 강력한 이름의 어셈블리로 컴파일하고 GAC에 등록합니다. 이 방식은 하나의 머신에서 모듈을 사용하는 복수의 응용 프로그램을 운영하고자 하는 경우 유용합니다. 강력한 이름의 어셈블리를 구축하는 방법에 대한 보다 자세한 내용은 MSDN 문서를 참고하시기 바랍니다.

그리고, 응용 프로그램의 Web.config 파일 구성 설정을 변경해서 모듈을 구성하기 전에, 먼저 기본적으로 서버 수준에서 잠겨져 있는 일부 구성 설정 섹션의 잠금을 해제해야 합니다. 권한이 상승된 명령 프롬프트에서 다음의 명령어를 실행합니다. (시작 메뉴에서 명령 프롬프트를 마우스 오른쪽 버튼으로 클릭하고 "관리자 권한으로 실행" 을 선택합니다): *

%windir%\system32\inetsrv\APPCMD.EXE unlock config /section:windowsAuthentication
%windir%\system32\inetsrv\APPCMD.EXE unlock config /section:anonymousAuthentication

이 명령어를 실행하면 응용 프로그램의 Web.config 파일에 해당 구성 설정 섹션을 정의할 수 있게 됩니다.

이제 모듈이 실행되도록 응용 프로그램에 모듈을 구성합니다. 새로운 모듈을 활성화시키고 사용하기 위해 필요한 구성 설정을 포함하고 있는 새로운 Web.config 파일을 생성합니다. 이 파일에 다음의 내용들을 추가하고 응용 프로그램의 루트에 저장합니다. (만약, 기본 웹 사이트의 루트 응용 프로그램을 테스트에 사용중이라면 그 경로는 %systemdrive%\inetpub\wwwroot\web.config 파일이 됩니다.)

<configuration> 
  <system.webServer> 
    <modules> 
    </modules> 
    <security> 
      <authentication> 
        <windowsAuthentication enabled="false"/> 
        <anonymousAuthentication enabled="false"/> 
      </authentication> 
    </security> 
  </system.webServer> 
</configuration>

그리고, 새로운 기본 인증 모듈을 활성화시키기 전에 다른 IIS 인증 모듈들을 모두 비활성화시키는 것이 좋습니다. 기본적으로는 윈도우 인증과 익명 인증만 활성화되어 있습니다. 여기에서는 웹 브라우저가 윈도우 신원 정보와 익명 사용자로 인증을 시도하는 상황을 원하지 않기 때문에 윈도우 인증 모듈과 익명 인증 모듈을 모두 비활성화시킬 것입니다.

이제, 응용 프로그램에 의해서 로딩될 모듈 목록에 새로운 모듈을 추가함으로서 모듈을 활성화시킬 수 있습니다. 이를 위해서, 다시 한 번 Web.config 파일을 열고 <modules> 태그에 다음 항목을 추가합니다:

<add name="MyBasicAuthenticationModule" type="IIS7Demos.BasicAuthenticationModule" /> 

아니면, IIS 7 관리자 도구나 APPCMD.EXE 명령 프롬프트 도구를 사용해서 응용 프로그램에 모듈을 배포할 수도 있습니다. 지금까지 설명한 내용들이 반영된 응용 프로그램의 Web.config 파일의 최종 내용은 부록 B 에 제공됩니다. 축하합니다, 이제 사용자 정의 기본 인증 모듈의 구성이 모두 끝났습니다.

이제 테스트를 해보겠습니다! 인터넷 익스플로러를 열고 다음과 같이 응용 프로그램에 대한 요청을 작성합니다: **

http://localhost/

그러면, 아마도 기본 인증 로그인 대화 상자가 나타날 것입니다. 로그인을 해보려면 "사용자 이름:" 필드와 "암호:" 필드에 "test"를 입력하십시오. 만약, 응용 프로그램에 HTML이나 JPG, 또는 다른 어떤 종류의 컨텐츠들이 추가되더라도 새로운 기본 인증 모듈에 의해 보호된다는 점을 기억하시기 바랍니다.

* IIS 7에서 구성 설정 섹션이 서버 수준에서 잠겨 있다는 얘기는 간단하게 말해서 해당 구성 설정 섹션은 applicationHost.config 파일에서만 설정이 가능하다는 의미입니다. 기본적으로 서버 수준에서 잠겨 있는 구성 설정 섹션은 관리자만 설정 가능하다고 보면 됩니다. 다만, 관리자가 본문에서 설명하는 명령어를 실행하는 등의 방법으로 해당 구성 설정 섹션의 잠금을 풀어주면 web.Config 파일에서도 설정이 가능해집니다.

이런 기능을 기능 위임이라고 하는데 IIS 7에서 도입된 분산 구성 설정 시스템의 가장 중요한 특징 중 하나입니다. 잠금 관련 설정은 본문에서 설명하고 있는 명령어를 실행하는 방법 외에도 IIS 관리자를 통해서도 설정이 가능합니다. IIS 관리자를 실행하고 트리뷰에서 루트 노드를 선택한 다음, 관리 영역의 기능 위임 아이콘을 더블 클릭해보면 위의 명령어를 실행시키기 전에는 윈도우 인증과 익명 인증이 다음과 같이 읽기 전용으로 설정되어 있을 것입니다.

그러나, 명령어를 실행하고 새로 고침을 해보면 다음과 같이 읽기/쓰기로 설정이 변경됩니다.

당연한 얘기겠지만, 방금 얘기한 것처럼 이 화면에서 직접 기능 위임 설정을 변경할 수도 있는데, 설정을 변경하고자 하는 기능을 선택하고 작업 영역에서 원하는 링크 버튼을 클릭하기만 하면 됩니다.

** 이 주소는 각자 테스트에 사용한 웹 응용 프로그램에 따라 달라질 것입니다. 제가 비스타에서 테스트 해본 바에 따르면 다음과 같은 로그인 대화 상자가 나타났으며 모든 동작이 정상적으로 이루어졌습니다.

요약

본문에서는 응용 프로그램을 위해 사용자 정의 관리되는 모듈을 작성하고 배포하는 방법과 해당 모듈이 응용 프로그램의 모든 요청에 대해 서비스되도록 활성화시키는 방법을 살펴봤습니다.

그리고, 관리되는 코드를 사용한 서버 컴포넌트 개발의 강력함을 직접 목격했습니다. 이는 윈도우 신원 증명 저장소에 구애받지 않는 기본 인증 서비스를 개발할 수 있도록 해줍니다.

만약, 더욱 다양한 시도를 해보고 싶다면 이 모듈을 구성 설정에 기반한 신원 증명 저장소를 지원하는 ASP.NET 2.0 멤버쉽 응용 프로그램 서비스를 이용하도록 구성할 수도 있습니다. 보다 자세한 정보는 부록 C를 참고하십시오. *

그리고, 필자의 블로그(http://www.mvolo.com/)에서 IIS 7 모듈 개발에 대한 다양한 자료와 팁을 살펴보거나, 응용 프로그램에 적용할 수 있는 여러 가지 IIS 7 모듈들을 다운로드 받을 수 있습니다. 가령, HttpRedirection 모듈을 사용해서 응용 프로그램에 대한 요청 리디렉션하기, DirectoryListingModule 모듈을 사용해서 세련된 IIS 웹 사이트 디렉터리 목록 출력하기, IconHandler를 사용해서 세련된 ASP.NET 응용 프로그램 파일 아이콘 출력하기, 그리고 IIS 와 ASP.NET을 사용해서 핫-링크 막기 등을 살펴보시기 바랍니다.

* 본문은 마이크로소프트의 IIS 개발팀에 의해서 작성된 문서이기 때문에 반복해서 ASP.NET 2.0 멤버쉽 서비스를 강조하고 있습니다. 그러나, 멤버쉽 서비스를 사용하지 않고 각자 개발한 코드를 사용한다고 해서 문제가 될 것은 전혀 없습니다. 오히려 국내 사정을 감안해본다면 멤버쉽 서비스가 사용되는 경우가 더 드물 것으로 생각됩니다.

부록 A: 기본 인증 모듈 소스 코드

간단한 배포를 원하신다면 다음의 소스 코드를 BasicAuthenticationModule.cs라는 이름으로 응용 프로그램의 /App_Code 디렉터리에 저장하십시오.

노트: 만약 메모장을 사용중이라면 파일명이 BasicAuthenticationModule.cs.txt 등과 같이 저장되는 것을 피하기 위해 "다른 이름으로 저장" 메뉴를 사용하시기 바랍니다.

#region Using directives
using System;
using System.Collections;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Security.Principal;
using System.IO;
#endregion
 
namespace IIS7Demos
{
    /// 
    /// This module performs basic authentication. 
    /// For details on basic authentication see RFC 2617. 
    /// 
    /// The basic operational flow is: 
    /// 
    ///     On AuthenticateRequest: 
    ///         extract the basic authentication credentials 
    ///         verify the credentials 
    ///         if succesfull, create the user principal with these credentials 
    /// 
    ///     On SendResponseHeaders: 
    ///         if the request is being rejected with an unauthorized status code (401), 
    ///         add the basic authentication challenge to trigger basic authentication. 
    ///       
    /// 
    
    public class BasicAuthenticationModule : IHttpModule
    {
        #region member declarations
        public const String     HttpAuthorizationHeader = "Authorization";  // HTTP1.1 Authorization header 
        public const String     HttpBasicSchemeName = "Basic"; // HTTP1.1 Basic Challenge Scheme Name 
        public const Char       HttpCredentialSeparator = ':'; // HTTP1.1 Credential username and password separator 
        public const int        HttpNotAuthorizedStatusCode = 401; // HTTP1.1 Not authorized response status code 
        public const String     HttpWWWAuthenticateHeader = "WWW-Authenticate"; // HTTP1.1 Basic Challenge Scheme Name 
        public const String     Realm = "demo"; // HTTP.1.1 Basic Challenge Realm 
        #endregion
    
        #region Main Event Processing Callbacks
        public void AuthenticateUser(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;
            String userName = null;
            String password = null;
            String realm = null;
            String authorizationHeader = context.Request.Headers[HttpAuthorizationHeader];
    
            // 
            //  Extract the basic authentication credentials from the request 
            // 
            if (!ExtractBasicCredentials(authorizationHeader, ref userName, ref password))
                return;
            // 
            // Validate the user credentials 
            // 
            if (!ValidateCredentials(userName, password, realm))
               return;
    
            // 
            // Create the user principal and associate it with the request 
            // 
            context.User = new GenericPrincipal(new GenericIdentity(userName), null);
        }
    
        public void IssueAuthenticationChallenge(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;
    
            // 
            // Issue a basic challenge if necessary 
            // 
            if (context.Response.StatusCode == HttpNotAuthorizedStatusCode)
            {
                context.Response.AddHeader(HttpWWWAuthenticateHeader, "Basic realm =\"" + Realm + "\"");
            }
        }
        #endregion
    
        #region Utility Methods
        protected virtual bool ValidateCredentials(String userName, String password, String realm)
        {
            // 
            //  Validate the credentials using Membership (refault provider) 
            // 
            // NOTE: Membership is commented out for clarity reasons.   
            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
            // WARNING: DO NOT USE THE CODE BELOW IN PRODUCTION 
            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
            // return Membership.ValidateUser(userName, password); 
            if (userName.Equals("test") && password.Equals("test"))
            {
                return true;
            }
            else 
            {
                return false;
            }    
        }

        protected virtual bool ExtractBasicCredentials(String authorizationHeader, ref String username, ref String password)
        {
            if ((authorizationHeader == null) || (authorizationHeader.Equals(String.Empty)))
               return false;

            String verifiedAuthorizationHeader = authorizationHeader.Trim();
            if (verifiedAuthorizationHeader.IndexOf(HttpBasicSchemeName) != 0)     
                return false;
    
            // get the credential payload 
            verifiedAuthorizationHeader = verifiedAuthorizationHeader.Substring(HttpBasicSchemeName.Length, verifiedAuthorizationHeader.Length - HttpBasicSchemeName.Length).Trim();

            // decode the base 64 encoded credential payload 
            byte[] credentialBase64DecodedArray = Convert.FromBase64String(verifiedAuthorizationHeader);
            UTF8Encoding encoding = new UTF8Encoding();
            String decodedAuthorizationHeader = encoding.GetString(credentialBase64DecodedArray, 0, credentialBase64DecodedArray.Length);
    
            // get the username, password, and realm 
            int separatorPosition = decodedAuthorizationHeader.IndexOf(HttpCredentialSeparator);
    
            if (separatorPosition <= 0)
                return false;
            username = decodedAuthorizationHeader.Substring(0, separatorPosition).Trim();
            password = decodedAuthorizationHeader.Substring(separatorPosition + 1, (decodedAuthorizationHeader.Length - separatorPosition - 1)).Trim();
    
            if (username.Equals(String.Empty) || password.Equals(String.Empty))
                 return false;
    
            return true;
        }
        #endregion
    
        #region IHttpModule Members
        public void Init(HttpApplication context)
        {
            // 
            // Subscribe to the authenticate event to perform the 
            // authentication. 
            // 
            context.AuthenticateRequest += new  EventHandler(this.AuthenticateUser);

            // 
            // Subscribe to the EndRequest event to issue the 
            // challenge if necessary. 
            // 
            context.EndRequest += new EventHandler(this.IssueAuthenticationChallenge);
        }

        public void Dispose()
        {
            // 
            // Do nothing here 
            // 
        }
        #endregion
    
    }
}

부록 B: 기본 인증 모듈을 위한 Web.config

다음 구성 설정을 여러분들의 응용 프로그램 루트의 Web.config 파일에 저장합니다:

<configuration> 
  <system.webServer> 
    <modules> 
      <add name="MyBasicAuthenticationModule" type="IIS7Demos.BasicAuthenticationModule" /> 
    </modules> 
    <security> 
      <authentication> 
        <windowsAuthentication enabled="false"/> 
        <anonymousAuthentication enabled="false"/> 
      </authentication> 
    </security> 
  </system.webServer> 
</configuration> 

부록 C: 멤버쉽 구성

ASP.NET 2.0 멤버쉽 서비스는 대부분의 인증 및 접근 제어 체계에서 요구되는 신원 증명 유효성 검증과 사용자 관리 기능을 응용 프로그램이 신속하게 구현할 수 있도록 도와줍니다. 멤버쉽은 응용 프로그램의 코드를 실질적인 신원 증명 저장소 구현과 격리시켜주며 기존 신원 증명 저장소와의 통합을 위한 몇 가지 옵션을 제공해줍니다.

본문의 모듈 예제에서 멤버쉽을 이용하려면 주석을 제거하기만 하면 됩니다. 즉, ValidateCredentials 메서드에서 ValidateUser 메서드를 호출하는 부분의 주석을 제거하고 응용 프로그램의 멤버쉽 공급자를 구성하면 됩니다. 멤버쉽을 구성하는 방법에 대한 보다 자세한 정보는 이 MSDN 문서를 참고하시기 바랍니다.