共用方式為


使用 .NET 開發模組

作者 :Mike Volodarsky

簡介

IIS 7.0 和更新版本允許透過兩種方式開發的模組擴充伺服器:

  • 使用 Managed 程式碼和 ASP.NET 伺服器擴充性 API
  • 使用機器碼和 IIS 原生伺服器擴充性 API

在過去,ASP.NET 模組的功能有限,因為 ASP.NET 要求處理管線與主伺服器要求管線不同。

在 IIS 中,受控模組幾乎就像使用整合管線架構的原生模組一樣強大。 最重要的是,受管理模組所提供的服務現在可以套用至伺服器的所有要求,而不只是對 ASPX 頁面等內容的 ASP.NET 要求。 受控模組會以與原生模組一致的方式設定及管理,而且可以在與原生模組相同的處理階段和排序中執行。 最後,受控模組可以執行更廣泛的作業集,以透過數個新增和增強的 ASP.NET API 來管理要求處理。

本文說明使用 Managed 模組擴充伺服器,以新增對任意認證存放區執行基本驗證的能力,例如 ASP.NET 2.0 成員資格系統中的提供者型認證基礎結構。

這允許取代 IIS 中內建的基本驗證支援,其系結至 Windows 認證存放區、支援任意認證存放區,或任何隨附于 ASP.NET 2.0 的現有成員資格提供者,例如 SQL Server、SQL Express 或 Active Directory。

本文會檢查下列工作:

  • 使用 ASP.NET API 開發受控模組
  • 在伺服器上部署受控模組

若要深入瞭解開發 IIS 模組和處理常式的基本概念,請參閱 使用 .NET Framework 開發 IIS7 模組和處理常式

您也可以在部落格上找到撰寫 IIS 模組的大量資源和秘訣, http://www.mvolo.com/ 以及為您的應用程式下載現有的 IIS 模組。 如需一些範例,請參閱 使用 HttpRedirection 模組將要求重新導向至您的應用程式使用 DirectoryListingModule 為 IIS 網站顯示美觀的目錄清單,以及 使用 IconHandler 在 ASP.NET 應用程式中顯示美觀的檔案圖示

注意

本文中提供的程式碼是以 C# 撰寫。

必要條件

若要遵循本檔中的步驟,您必須安裝下列 IIS 功能:

ASP.NET

透過 Windows Vista 主控台安裝 ASP.NET。 選取 [程式] - [開啟或關閉 Windows 功能]。 然後開啟 「Internet Information Services」 - 「World Wide Web Services」 - 「Application Development Features」,並檢查 「ASP.NET」。

如果您有 Windows Server® 2008 組建,請開啟 [伺服器管理員] - [角色],然後選取 [Web Server (IIS) ]。 按一下 [新增角色服務]。 在 [應用程式開發] 底下,檢查 [ASP.NET]。

基本驗證的背景資訊

基本驗證是在 HTTP.1 通訊協定中定義的驗證配置, (RFC 2617) 。 它會使用標準挑戰式機制,其運作方式如下:高階:

  • Browser 對沒有認證的 URL 提出要求
  • 如果伺服器需要該 URL 的驗證,它會以 401 拒絕存取訊息回應,並包含標頭,指出支援基本驗證配置
  • Browser 會收到回應,如果已設定,則會提示使用者輸入使用者名稱/密碼,該使用者會在要求標頭中包含該使用者名稱/密碼,以取得 URL 下一個要求的下一個要求
  • 伺服器會在標頭內接收使用者名稱/密碼,並使用它們進行驗證

注意

雖然此驗證通訊協定的詳細討論超出本文的範圍,但值得一提的是,基本驗證配置需要 SSL 才能安全,因為它會以純文字傳送使用者名稱/密碼。

IIS 包含針對儲存在本機帳戶存放區或網域帳戶 Active Directory 中的 Windows 帳戶進行基本驗證的支援。 我們想要讓使用者使用基本驗證進行驗證,但改用 ASP.NET 2.0 成員資格 服務來驗證認證。 這可讓您自由地將使用者資訊儲存在各種現有的成員資格提供者中,例如 SQL Server,而不會系結至 Windows 帳戶。

工作 1:使用 .NET 開發模組

在這項工作中,我們會檢查支援 HTTP.1 基本驗證配置的驗證模組開發。 此課程模組是使用自 ASP.NET v1.0 起可用的標準 ASP.NET 模組模式所開發。 這個相同的模式可用來建置擴充 IIS 伺服器的 ASP.NET 模組。 事實上,針對舊版 IIS 撰寫的現有 ASP.NET 模組可以在 IIS 上使用,並利用更好的 ASP.NET 整合,為使用這些模組的 Web 應用程式提供更多功能。

注意

模組的完整程式碼會在附錄 A 中提供。

Managed 模組是實作 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);
}

在 AuthenticateRequest事件期間,會在每個要求上叫用AuthenticateUser方法。 我們會利用它根據要求中存在的認證資訊來驗證使用者。

IssueAuthenticationChallenge方法會在EndRequest事件期間在每個要求上叫用。 它負責在授權模組拒絕要求時,向用戶端發出基本驗證挑戰,並需要驗證。

AuthenticateUser ()

實作 AuthenticateUser 方法。 此方法會執行下列動作:

  • 如果來自傳入要求標頭,請擷取基本認證。 若要查看此步驟的實作,請參閱 ExtractBasicAuthenticationCredentials 公用程式方法。
  • 嘗試使用設定) 的預設成員資格提供者,透過成員資格 (驗證提供的認證。 若要查看此步驟的實作,請參閱 ValidateCredentials 公用程式方法。
  • 建立使用者主體,識別驗證是否成功,並將它與要求產生關聯。

在此處理結束時,如果模組成功取得並驗證使用者認證,它會產生已驗證的使用者主體,以供其他模組和應用程式程式碼稍後用於存取控制決策。 例如,URL 授權模組會檢查下一個管線事件中的使用者,以強制執行應用程式所設定的授權規則。

IssueAuthenticationChallenge ()

實作 IssueAuthenticationChallenge 方法。 此方法會執行下列動作:

  • 檢查回應狀態碼,以判斷此要求是否遭到拒絕。
  • 如果是,請對回應發出基本驗證挑戰標頭,以觸發用戶端進行驗證。

公用程式方法

實作模組使用的公用程式方法,包括:

  • ExtractBasicAuthenticationCredentials。 這個方法會從授權要求標頭擷取基本驗證認證,如基本驗證配置中所指定。
  • ValidateCredentials。 此方法會嘗試使用成員資格來驗證使用者認證。 成員資格 API 會抽象化基礎認證存放區,並允許透過組態新增/移除成員資格提供者來設定認證存放區實作。

注意

在此範例中,成員資格驗證已批註化,而模組只會檢查使用者名稱和密碼是否都等於字串 「test」。 這是為了清楚起見,不適合用于生產環境部署。 您只要取消批註 ValidateCredentials 內的成員資格代碼,並設定應用程式的成員資格提供者,即可啟用成員資格型認證驗證。 如需詳細資訊,請參閱附錄 C。

工作 2:將模組部署至應用程式

在第一個工作中建立模組之後,我們會接著將它新增至應用程式。

部署至應用程式

首先,將模組部署至應用程式。 在這裡,您有數個選項:

  • 將包含模組的來源檔案複製到應用程式的 /App_Code 目錄中。 這不需要編譯模組 - ASP.NET 會在應用程式啟動時自動編譯並載入模組類型。 只要將此原始程式碼儲存為應用程式 /App_Code 目錄中的 BasicAuthenticationModule.cs 即可。 如果您不熟悉其他步驟,請執行此動作。

  • 將模組編譯成元件,並將此元件卸載至應用程式的 /BIN 目錄中。 如果您只想要讓此模組可供此應用程式使用,而且您不想將模組的來源寄送到您的應用程式,這是最典型的選項。 從命令列提示字元執行下列命令,以編譯模組來源檔案:

    <PATH_TO_FX_SDK>csc.exe /out:BasicAuthenticationModule.dll /target:library BasicAuthenticationModule.cs

    其中 <PATH_TO_FX_SDK> 是包含CSC.EXE編譯器之.NET Framework SDK 的路徑。

  • 將模組編譯成強式名稱元件,並在 GAC 中註冊此元件。 如果您想要讓電腦上的多個應用程式使用此模組,這是不錯的選擇。 若要深入瞭解如何建置強式名稱元件,請參閱 建立和使用強式名稱元件

在應用程式的web.config檔案中進行組態變更之前,我們必須解除鎖定伺服器層級預設鎖定的一些組態區段。 從提升許可權的命令提示字元執行下列命令, (> 以滑鼠右鍵按一下Cmd.exe,然後選擇 [以系統管理員身分執行] ) :

%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 驗證模組。 根據預設,只會啟用Windows 驗證和匿名驗證。 因為我們不想讓瀏覽器嘗試使用您的 Windows 認證進行驗證或允許匿名使用者,所以我們停用 Windows 驗證模組和匿名驗證模組。

現在,將模組新增至應用程式載入的模組清單,以啟用模組。 再次開啟web.config,並將專案新增至 <modules> 標籤

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

您也可以使用 IIS 管理工具或APPCMD.EXE命令列工具來部署模組。

在附錄 B 中提供這些變更之後,應用程式web.config檔案的最終內容。

恭喜,您已完成自訂基本驗證模組的設定。

讓我們來試試看! 開啟 Internet Explorer,並在下列 URL 對應用程式提出要求:

http://localhost/

您應該會看到 [基本驗證登入] 對話方塊。 在 [使用者名稱:] 欄位中輸入 「test」,然後在 [密碼:] 欄位中輸入 「test」,以取得存取權。 請注意,如果您將 HTML、JPG 或任何其他內容複寫到您的應用程式,它們也會受到新的 BasicAuthenticationModule 保護。

總結

在本文中,您已瞭解如何開發及部署應用程式的自訂受控模組,並讓該模組為應用程式的所有要求提供服務。

您也見證了在 Managed 程式碼中開發伺服器元件的強大功能。 這允許開發與 Windows 認證儲存體分離的基本驗證服務。

如果您是問世,請將此課程模組設定為利用 ASP.NET 2.0 成員資格應用程式服務的強大功能來支援可插式認證存放區。 如需詳細資訊,請參閱附錄 C。

在部落格中尋找許多有關撰寫 IIS 模組的資源和秘訣, http://www.mvolo.com/ 以及下載您應用程式的現有 IIS 模組。 如需一些範例,請參閱 使用 HttpRedirection 模組將要求重新導向至您的應用程式使用 DirectoryListingModule 為 IIS 網站提供美觀的目錄清單,以及 使用 IconHandler 在 ASP.NET 應用程式中顯示美觀的檔案圖示

附錄 A:基本驗證模組原始程式碼

將此原始程式碼儲存為 /App_Code 目錄中的 BasicAuthenticationModule.cs,以快速將它部署到您的應用程式。

注意

如果您使用 [記事本],請務必設定 [另存新檔:所有檔案] 以避免將檔案儲存為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 方法內取消批註 Membership.ValidateUser 的呼叫,並為您的應用程式設定成員資格提供者。 如需設定成員資格的詳細資訊,請參閱 將 ASP.NET 應用程式設定為使用成員資格