共用方式為


表單驗證概觀(C#)

斯科特·米切爾

備註

自本文撰寫以來,ASP.NET 成員資格提供者已被 ASP.NET 身分識別取代。 強烈建議更新應用程式以使用 ASP.NET 身分識別 平臺,而不是本文撰寫時精選的成員資格提供者。 ASP.NET 身分識別在 ASP.NET 成員資格系統中有許多優點,包括:

  • 更好的效能
  • 改善擴充性和可測試性
  • 支援 OAuth、OpenID Connect 和雙因素驗證
  • 宣告型身分識別支援
  • 提升與 ASP.Net Core 的互操作性

下載程式代碼下載 PDF

在本教學課程中,我們將從單純的討論轉向實作,特別是,我們將探討如何實作表單驗證。 我們在本教學課程中開始構建的 Web 應用程式會在後續的教學中繼續拓展,從基本的驗證逐步擴展到成員資格和角色。

若要了解更多有關此主題的資訊,請參閱此影片:在 ASP.NET 中使用基本窗體驗證

簡介

上述教學課程中 ,我們討論了 ASP.NET 所提供的各種驗證、授權和用戶帳戶選項。 在本教學課程中,我們將從單純的討論轉向實作,特別是,我們將探討如何實作表單驗證。 我們在本教學課程中開始構建的 Web 應用程式會在後續的教學中繼續拓展,從基本的驗證逐步擴展到成員資格和角色。

本教學課程從深入了解表單驗證工作流程開始,這是在上一個教學課程中已經討論過的主題。 然後,我們將會建立一個 ASP.NET 網站,來展示表單驗證的概念。 接下來,我們會將網站設定為使用表單驗證,建立一個簡單的登入頁面,並且查看如何在代碼中確定使用者是否已通過驗證,如果是的話,確認其登入的用戶名稱。

理解 Forms 驗證工作流程、在 Web 應用程式中啟用驗證,以及建立登入和登出頁面,都是建置支援使用者帳戶並透過網頁驗證使用者的 ASP.NET 應用程式的重要步驟。 正因如此,且這些教學課程是互相建構的,即使您過去曾有設置表單驗證的經驗,我仍建議您在完成本教程之前,不要急於進行下一個教程。

瞭解表單驗證工作流程

當 ASP.NET 運行時間處理 ASP.NET 資源的要求時,例如 ASP.NET 頁面或 ASP.NET Web 服務,要求會在其生命週期中引發一些事件。 在要求開頭和非常結束時引發事件、在驗證和授權要求時引發的事件、在未處理的例外狀況中引發的事件等等。 若要查看事件的完整清單,請參閱 HttpApplication 物件的事件

HTTP 模組 是受管理的類別,其程式碼會在要求生命週期中的特定事件發生時執行。 ASP.NET 隨附許多 HTTP 模組,這些模組會在幕後執行基本工作。 兩個特別與討論相關的內建 HTTP 模組如下:

  • FormsAuthenticationModule – 藉由檢查用戶驗證憑證來驗證使用者,這通常包含在使用者的 Cookie 集中。 如果沒有表單驗證憑證,則使用者為匿名。
  • UrlAuthorizationModule – 判斷目前使用者是否獲得存取要求 URL 的授權。 此模組會根據應用程式配置檔中指定的授權規則來判斷權限。 ASP.NET 也包含 FileAuthorizationModule,藉由查核所要求檔案的 ACL 來判斷授權。

FormsAuthenticationModule 嘗試在執行 UrlAuthorizationModuleFileAuthorizationModule 之前驗證使用者。 如果提出要求的用戶無權存取要求的資源,授權模組就會終止要求並傳回 HTTP 401 未經授權 狀態。 在 Windows 驗證案例中,HTTP 401 狀態會傳回至瀏覽器。 此狀態代碼會導致瀏覽器透過強制回應對話框提示使用者輸入其認證。 不過,使用窗體驗證時,HTTP 401 未經授權狀態永遠不會傳送至瀏覽器,因為 FormsAuthenticationModule 會偵測到此狀態並修改它,以重定向使用者至登入頁面(透過 HTTP 302 重定向 狀態)。

登入頁面的責任是判定用戶的憑證是否有效,如果有效,就建立表單驗證票證,並將使用者重新導向回到他們嘗試瀏覽的頁面。 驗證票證會包含在網站頁面的後续请求中,FormsAuthenticationModule 用來識別使用者。

表單驗證工作流程

圖 1:窗體驗證工作流程

記住跨頁面訪問時的認證憑證

登入之後,表單驗證票證必須在每個要求上傳送回網頁伺服器,讓使用者在瀏覽網站時仍會保持登入狀態。 這通常是藉由將驗證票證放在使用者的Cookie集合中來完成。 Cookie 是位在用戶電腦上的小型文本檔,而且會在每個要求傳送至建立 Cookie 的網站的 HTTP 標頭中。 因此,一旦表單驗證票證建立並儲存在瀏覽器的 Cookie 中,每次後續造訪該網站時,都會連同要求一同傳送驗證票證,以識別使用者身份。

Cookie 的其中一個層面是其到期日,也就是瀏覽器捨棄 Cookie 的日期和時間。 當表單驗證 Cookie 過期時,使用者將無法再進行驗證,因而成為匿名使用者。 當使用者從公用終端機流覽時,他們有可能希望其驗證票證在關閉瀏覽器時到期。 不過,從家瀏覽時,相同的使用者可能會想要在瀏覽器重新啟動時記住驗證票證,如此一來,他們就不需要在每次瀏覽網站時重新登入。 此決定通常是由使用者以登入頁面上的 [記住我] 複選框的形式進行。 在步驟 3 中,我們將檢查如何在登入頁面中實作 [記住我] 複選框。 下列教學指南詳細說明驗證票證時效設定。

備註

使用者代理程式可能用來登入網站,可能不支援 Cookie。 在這種情況下,ASP.NET 可以使用不使用 Cookie 的表單驗證票證。 在此模式中,驗證票證會編碼到URL中。 我們將探討何時使用無 Cookie 驗證票證,以及如何在下一個教學課程中建立和管理這些票證。

表單驗證的範圍

FormsAuthenticationModule是屬於 ASP.NET 執行階段一部分的受管理的程式碼。 在Microsoft的 Internet Information Services (IIS) 網頁伺服器第 7 版之前,IIS 的 HTTP 管線與 ASP.NET 運行時間管線之間有明顯的障礙。 簡而言之,在 IIS 6 及更早的版本中,FormsAuthenticationModule 僅在請求被從 IIS 委派至 ASP.NET 執行環境時才會執行。 根據預設,IIS 會處理靜態內容本身,例如 HTML 頁面和 CSS 和圖像檔,而且只有在要求擴展名為 .aspx、.asmx 或 .ashx 的頁面時,才會將要求傳送給 ASP.NET 運行時間。

不過,IIS 7 允許整合式 IIS 和 ASP.NET 管線。 透過一些組態設定,您可以設定 IIS 7 調用 FormsAuthenticationModule,以處理所有要求。 此外,使用 IIS 7,您可以為任何類型的檔案定義 URL 授權規則。 如需詳細資訊,請參閱 IIS6 與 IIS7 安全性之間的變更您的 Web 平台安全性,以及 瞭解 IIS7 URL 授權

簡而言之,在 IIS 7 之前的版本中,您只能使用表單驗證來保護 ASP.NET 執行階段處理的資源。 同樣地,URL 授權規則只會套用至 ASP.NET 運行時間所處理的資源。 但是,使用 IIS 7,可以將 FormsAuthenticationModule 和 UrlAuthorizationModule 整合到 IIS 的 HTTP 管線中,藉此將此功能擴充至所有要求。

步驟 1:建立本教學課程系列 ASP.NET 網站

為了達到最廣泛的受眾,我們將在此系列中建置的 ASP.NET 網站,將透過Microsoft的免費Visual Studio 2008 版本Visual Web Developer 2008 來建立。 我們將在 SqlMembershipProviderMicrosoft SQL Server 2005 Express Edition 資料庫中 實作使用者儲存區。 如果您使用 Visual Studio 2005 或不同版本的 Visual Studio 2008 或 SQL Server,別擔心 - 這些步驟會幾乎完全相同,而且會指出任何非簡單差異。

備註

每個教學課程中使用的示範 Web 應用程式都可下載。 此可下載的應用程式是以 .NET Framework 3.5 版為目標的Visual Web Developer 2008 所建立。 由於應用程式是以 .NET 3.5 為目標,因此其 Web.config 檔案包含額外的 3.5 特定組態元素。 總而言之,如果您尚未在電腦上安裝 .NET 3.5,那麼可下載的 Web 應用程式將無法運作,必須先從 Web.config移除 3.5 特定標記。

在我們能夠設定窗體驗證之前,首先需要建立一個 ASP.NET 網站。 從建立新的文件系統型 ASP.NET 網站開始。 若要達成此目的,請啟動Visual Web Developer,然後移至 [檔案] 選單,然後選擇 [新增網站],顯示 [新增網站] 對話框。 選擇 [ASP.NET 網站範本]、將 [位置] 下拉式清單設定為 [檔案系統]、選擇要放置網站的資料夾,以及將語言設定為 C# 。 這會建立具有Default.aspx ASP.NET 頁面、App_Data資料夾和 Web.config 檔案的新網站。

備註

Visual Studio 支援兩種專案管理模式:網站專案和 Web 應用程式專案。 網站專案缺少項目檔,而 Web 應用程式專案會模擬 Visual Studio .NET 2002/2003 中的項目架構 –它們包含項目檔,並將專案的原始程式碼編譯成單一元件,而該專案會放在 /bin 資料夾中。 Visual Studio 2005 最初只支援網站專案,不過 Web 應用程式專案模型是使用 Service Pack 1 重新引進;Visual Studio 2008 提供這兩個專案模型。 不過,Visual Web Developer 2005 和 2008 版本僅支援網站專案。 我將使用網站專案模型。 如果您使用非 Express 版本,而想要改用 Web 應用程式專案模型 ,請放心這麼做,但請注意,您在畫面上看到的內容與您必須採取的步驟與這些教學課程中提供的指示之間可能有一些差異。

建立新檔案 System-Based 網站

圖 2:建立新檔案 System-Based 網站 (按兩下以檢視完整大小的影像

新增主版頁面

接下來,將新的主版頁面新增至名為 Site.master 的根目錄中的網站。 主版頁面 可讓頁面開發人員定義可套用至 ASP.NET 頁面的全網站範本。 主版頁面的主要優點是,網站的整體外觀可以定義在單一位置,以便輕鬆地更新或調整網站的版面配置。

將名為 Site.master 的主版頁面新增至網站

圖 3:將名為 Site.master 的主版頁面新增至網站 (按兩下以檢視完整大小的影像

在主版頁面中定義全網站版面配置。 您可以使用 [設計] 檢視,並新增您需要的任何版面配置或 Web 控件,也可以手動在 [來源] 檢視中手動新增標記。 我已結構化主版頁面的版面配置,以模擬我在 ASP.NET 2.0 教學課程系列中使用的版面配置(請參閱圖 4)。 母版頁面使用 級聯樣式表 用於定位和設定樣式,並且在檔案 Style.css 中定義的 CSS 設定(本教學課程的相關下載中附有)。 雖然您無法從下面顯示的標記中得知,但會定義 CSS 規則,讓導覽 <div> 的內容絕對定位,使其出現在左側,且寬度固定為 200 像素。

<%@ Master Language="C#" AutoEventWireup="true" CodeFile="Site.master.cs" Inherits="Site" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Forms Authentication, Authorization, and User Accounts</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div id="wrapper">
        <form id="form1" runat="server">
        
            <div id="header">
                <span class="title">User Account Tutorials</span>
            </div>
        
            <div id="content">
                <asp:contentplaceholder id="MainContent" runat="server">
                  <!-- Page-specific content will go here... -->
                </asp:contentplaceholder>
            </div>
            
            <div id="navigation">
                TODO: Menu will go here...
            </div>
        </form>
    </div>
</body>
</html>

主版頁面會定義靜態頁面配置,以及可以使用主版頁面的 ASP.NET 頁面編輯的區域。 這些內容可編輯的區域由 ContentPlaceHolder 控件指出,可以在內容 <div> 內看到。 我們的主版頁面有單一的ContentPlaceHolder(MainContent),但主版頁面的可能有多個 ContentPlaceHolders。

輸入上述標記後,切換至設計檢視會顯示主版頁面的配置。 使用此主版頁面的任何 ASP.NET 頁面都會有此統一版面配置,且能夠指定區域的標記 MainContent

透過設計檢視查看的主版頁面

圖 4:主版頁面,透過設計檢視檢視時(點擊以檢視完整大小的影像

建立內容頁面

此時,我們在網站中有一個Default.aspx頁面,但它不會使用我們剛才建立的主版頁面。 雖然可以作網頁的宣告式標記來使用主版頁面,但如果頁面未包含任何內容,則只要刪除頁面並重新新增至專案會比較容易,請指定要使用的主版頁面。 因此,從從專案刪除Default.aspx開始。

接下來,以滑鼠右鍵按兩下 [方案總管] 中的專案名稱,然後選擇新增名為 Default.aspx 的新Web窗體。 這次,核取 [選取主版頁面] 複選框,然後從清單中選擇 Site.master 主版頁面。

新增Default.aspx頁面選擇選取主版頁面

圖 5:新增Default.aspx頁面選擇選取主版頁面 (按兩下以檢視完整大小的影像

使用 Site.master 主版頁面

圖 6:使用 Site.master 主版頁面

備註

如果您使用 Web 應用程式專案模型,[新增專案] 對話框不包含 [選取主版頁面] 複選框。 相反地,您需要新增一個類型為「Web 內容表單」的項目。在選擇「Web 內容表單」選項並點擊「新增」後,Visual Studio 會顯示如圖 6 所示的「選擇主版」對話方塊。

新的 Default.aspx 頁面的宣告式標記包含一個指令,指定至主版頁面檔案的路徑,以及一個內容控制項,用於主版頁面的 MainContent ContentPlaceHolder。

<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server">
</asp:Content>

目前,請將Default.aspx保留空白。 我們稍後會在本教學課程中返回它以新增內容。

備註

我們的主版頁面包含功能表或其他瀏覽介面的區段。 我們將在未來的教學課程中建立這類介面。

步驟 2:啟用表單身份驗證

建立 ASP.NET 網站後,接下來我們要啟用表單驗證。 應用程式的驗證組態是透過 <authentication> Web.config中的專案來指定。元素<authentication>包含單一屬性命名模式,指定應用程式所使用的驗證模型。 此屬性可以有下列四個值之一:

  • Windows – 如上一個教學課程所述,當應用程式使用 Windows 驗證時,Web 伺服器有責任驗證訪客,這通常是透過基本、摘要或整合式 Windows 驗證來完成。
  • 表單 – 用戶會透過網頁上的表單進行驗證。
  • Passport – 使用者會使用 Microsoft 的 Passport 網路進行驗證。
  • – 未使用驗證模型;所有訪客都是匿名的。

根據預設,ASP.NET 應用程式會使用 Windows 驗證。 若要將驗證類型變更為窗體驗證,我們需要將 <authentication> 元素的 mode 屬性修改為 Forms。

如果您的專案尚未包含 Web.config 檔案,請在 [方案總管] 中滑鼠右鍵點擊專案名稱,選擇 [新增項目],然後新增 Web 設定檔。

如果您的專案尚未包含 Web.config,請立即新增

圖 7:如果您的專案尚未包含 Web.config,請立即新增它(按兩下以檢視完整大小的影像

接下來,找出<authentication>元素,並將其更新為使用表單驗證。 這項變更之後,您的 Web.config 檔案標記看起來應該如下所示:

<configuration>
    <system.web>
        ... Unrelated configuration settings and comments removed for brevity ...
        <!--
            The <authentication> section enables configuration 
            of the security authentication mode used by 
            ASP.NET to identify an incoming user. 
        -->
        <authentication mode="Forms" />
    </system.web>
</configuration>

備註

由於 Web.config 是 XML 檔案,因此大小寫很重要。 請確定您已使用大寫 「F」 將 mode 屬性設定為 Forms。 如果您使用不同的大小寫,例如「表單」,則透過瀏覽器瀏覽網站時會收到配置錯誤。

元素 <authentication> 可以選擇性地包含一個子元素 <forms>,其中包含表單驗證的特定設定。 現在,讓我們先使用預設的表單驗證設定。 我們將在下一個教學課程中更詳細地探索 <forms> 子元素。

步驟 3:建置登入頁面

為了支援表單驗證,我們的網站需要一個登入頁面。 如一節所述,如果用戶嘗試存取未獲授權檢視的頁面, FormsAuthenticationModule 就會自動將使用者重新導向至登入頁面。 也有 ASP.NET Web 控制件會顯示登入頁面的連結給匿名使用者。 這表示「登入頁面的 URL 為何?」

根據預設,表單驗證系統會預期登入頁面會命名為 Login.aspx,並放在這個 Web 應用程式的根目錄中。 如果您想要使用不同的登入頁面 URL,您可以在 Web.config中指定它。我們將在後續教學課程中瞭解如何執行這項作。

登入頁面有三個責任:

  1. 提供介面,可讓訪客輸入其認證。
  2. 判斷提交的認證是否有效。
  3. 建立表單驗證票證來登入使用者。

建立登入頁面的使用者介面

讓我們開始第一個任務。 將新的 ASP.NET 頁面新增至名為 Login.aspx 的網站根目錄,並將它與 Site.master 主版頁面產生關聯。

新增名為 Login.aspx 的新 ASP.NET 頁面

圖 8:新增名為 Login.aspx 的新 ASP.NET 頁面(按兩下以檢視完整大小的影像

一般的登入頁面介面包含兩個文本框,一個用於使用者名稱,一個用於密碼,還有一個提交表單用的按鈕。 網站通常會包含 [記住我] 複選框,如果核取,則會在瀏覽器重新啟動時保存產生的驗證票證。

將兩個 TextBox 新增至Login.aspx,並將其屬性分別設定 ID 為 UserName 和 Password。 同時將Password的 TextMode 屬性設定為Password。 接下來,新增 CheckBox 控件,將其 ID 屬性設定為 RememberMe,並將其 Text 屬性設定為 “Remember Me”。 接下來,新增名為 LoginButton 的 Button,其 Text 屬性設定為 “Login”。 最後,新增 Label Web 控件,並將其屬性設定 ID 為 InvalidCredentialsMessage,其 Text 屬性會設為「您的使用者名稱或密碼無效。 請再試一次。」,其 ForeColor 屬性為 Red,而其 Visible 屬性為 False。

此時,您的畫面看起來應該類似圖 9 中的螢幕快照,而頁面的宣告式語法應該如下所示:

<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="Login.aspx.cs" Inherits="Login" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server">
    <h1>
        Login</h1>
    <p>
        Username:
        <asp:TextBox ID="UserName" runat="server"></asp:TextBox></p>
    <p>
        Password:
        <asp:TextBox ID="Password" runat="server" TextMode="Password"></asp:TextBox></p>
    <p>
        <asp:CheckBox ID="RememberMe" runat="server" Text="Remember Me" /> </p>
    <p>
        <asp:Button ID="LoginButton" runat="server" Text="Login" OnClick="LoginButton_Click" /> </p>
    <p>
        <asp:Label ID="InvalidCredentialsMessage" runat="server" ForeColor="Red" Text="Your username or password is invalid. Please try again."
            Visible="False"></asp:Label> </p>
</asp:Content>

登入頁面包含兩個 TextBox、CheckBox、按鈕和標籤

圖 9:登入頁面包含兩個 TextBox、CheckBox、按鈕和標籤(按兩下以檢視完整大小的影像

最後,為 LoginButton 的 Click 事件建立事件處理程式。 從設計工具中,只要按兩下Button控制項即可建立此事件處理程式。

判斷提供的認證是否有效

我們現在需要在 Button 的 Click 事件處理程式中實作工作 2 – 判斷提供的認證是否有效。 若要這樣做,必須有一個使用者存放區來保存所有用戶的認證,以便我們可以判斷提供的認證是否符合任何已知的認證。

ASP.NET 2.0 之前,開發人員必須負責實作自己的使用者存放區,並撰寫程式代碼來驗證針對存放區提供的認證。 大部分的開發人員都會在資料庫中實作使用者存放區,建立名為Users的數據表,其中包含UserName、Password、Email、LastLoginDate等數據行。 然後,此數據表會為每個用戶帳戶有一筆記錄。 確認使用者提供的認證會牽涉到查詢資料庫是否有相符的使用者名稱,然後確保資料庫中的密碼對應至所提供的密碼。

使用 ASP.NET 2.0,開發人員應該使用其中一個成員資格提供者來管理使用者存放區。 在本教學課程系列中,我們將使用 SqlMembershipProvider,其會針對使用者存放區使用 SQL Server 資料庫。 使用 SqlMembershipProvider 時,我們需要實作特定的資料庫架構,其中包含提供者所預期的數據表、檢視和預存程式。 我們將檢查如何在 在 SQL Server 中建立成員資格架構 教學課程中實作此架構。 有了成員資格提供者,驗證使用者的認證就如同呼叫 Membership 類別ValidateUser(usernamepassword) 方法一樣簡單,它會傳回 Boolean 值,指出 使用者 名稱和 密碼 組合的有效性。 當我們尚未實作 SqlMembershipProvider 的使用者存放區時,目前無法使用 Membership 類別的 ValidateUser 方法。

我們不需要花時間建立自己的自定義 Users 資料庫資料表,而是在實作 SqlMembershipProvider 之後,直接將有效的認證資訊硬編碼到登入頁面本身。 在 LoginButton 的 Click 事件處理程式中,新增下列程式代碼:

protected void LoginButton_Click(object sender, EventArgs e)
{
    // Three valid username/password pairs: Scott/password, Jisun/password, and Sam/password.
    string[] users = { "Scott", "Jisun", "Sam" };
    string[] passwords = { "password", "password", "password" };
    for (int i = 0; i < users.Length; i++)
    {
        bool validUsername = (string.Compare(UserName.Text, users[i], true) == 0);
        bool validPassword = (string.Compare(Password.Text, passwords[i], false) == 0);
        if (validUsername && validPassword)
        {
            // TODO: Log in the user...
            // TODO: Redirect them to the appropriate page
        }
    }
    // If we reach here, the user's credentials were invalid
    InvalidCredentialsMessage.Visible = true;
}

如您所見,有三個有效的用戶帳戶 -Scott、Jisun 和 Sam ,這三個都有相同的密碼(“password”)。 程式碼會對使用者名稱和密碼的陣列進行迴圈搜尋,以找到有效的使用者名稱和密碼組合。 如果使用者名稱和密碼都有效,我們需要登入用戶,然後將他們重新導向至適當的頁面。 如果認證無效,則會顯示 InvalidCredentialsMessage 標籤。

當使用者輸入有效的認證時,我提到他們接著會重新導向至「適當的頁面」。不過,適當的頁面為何? 請注意,當使用者瀏覽未被授權檢視的頁面時,FormsAuthenticationModule 會自動將使用者重新導向至登入頁面。 這樣做時,它會透過 ReturnUrl 參數在 querystring 中包含要求的 URL。 也就是說,如果使用者嘗試瀏覽ProtectedPage.aspx,而且他們未獲授權這樣做,FormsAuthenticationModule 會將他們重新導向至:

Login.aspx?ReturnUrl=ProtectedPage.aspx

成功登入時,用戶應該重新導向回ProtectedPage.aspx。 或者,用戶可以自行流覽登入頁面。 在此情況下,登入後,應該將使用者傳送至根資料夾中的 Default.aspx 頁面。

登入使用者

如果所提供的認證有效,我們需要建立表單驗證票證,讓用戶登入網站。 System.Web.Security 命名空間中的 FormsAuthentication 類別提供各種方法,可透過表單驗證系統登入和註銷使用者。 雖然 FormsAuthentication 類別中有數種方法,但我們在此階段感興趣的三種方法如下:

  • GetAuthCookie(usernamepersistCookie – 為提供的 username 建立表單驗證憑證。 接下來,此方法會建立並傳回保存驗證票證內容的 HttpCookie 物件。 如果 persistCookie 為 true,則會建立持續性Cookie。
  • SetAuthCookie(usernamepersistCookie) – 呼叫 GetAuthCookie(usernamepersistCookie)方法來產生表單驗證 Cookie。 此方法接著會將 GetAuthCookie 傳回的 Cookie 新增至 Cookies 集合(假設正在使用 Cookie 型窗體驗證;否則,此方法會呼叫處理無 Cookie 票證邏輯的內部類別)。
  • RedirectFromLoginPage(usernamepersistCookie – 此方法會呼叫 SetAuthCookie(username,persistCookie),然後將使用者重新導向至適當的頁面。

當您需要在將 Cookie 寫入 Cookies 集合之前,修改身份驗證票證時,GetAuthCookie 是一個有用的工具。 如果您想要建立窗體驗證票證並將其新增至 Cookies 集合,但不想將使用者重新導向至適當的頁面,SetAuthCookie 會很有用。 也許您想要將它們保留在登入頁面上,或將它們傳送至某些替代頁面。

由於我們想要登入使用者並將其重新導向至適當的頁面,因此讓我們使用 RedirectFromLoginPage。 更新 LoginButton 的 Click 事件處理程式,將兩個批注的 TODO 行取代為下列程式代碼行:

FormsAuthentication.RedirectFromLoginPage(UserName.Text, RememberMe.Checked):

在建立表單驗證票證時,我們使用 UserName TextBox 的 Text 屬性作為表單驗證票證的 使用者名稱 參數,並使用 RememberMe CheckBox 的已勾選狀態作為 persistCookie 參數。

若要測試登入頁面,請在瀏覽器中流覽。 首先輸入無效的認證,例如 「Nope」 的用戶名稱,以及 「wrong」 的密碼。 點擊 [登入] 按鈕後,會進行回傳,並顯示無效憑證訊息標籤。

輸入無效認證時,會顯示 InvalidCredentialsMessage 標籤

圖 10:輸入無效認證時會顯示 InvalidCredentialsMessage 標籤 (按兩下以檢視完整大小的影像

接下來,輸入有效的認證,然後按兩下 [登入] 按鈕。 當回傳發生時,系統會建立表單驗證票證,並自動將您重新導向回Default.aspx。 此時您已登入網站,不過沒有視覺提示可指出您目前已登入。 在步驟 4 中,我們將瞭解如何以程式設計方式判斷使用者是否已登入,以及如何識別瀏覽頁面的使用者。

步驟 5 會檢視將用戶登出網站的技術。

保護登入頁面

當使用者輸入她的凭证並提交登入頁面的表單時,凭证,包括她的密碼,會以不加密的形式透過因特網傳輸至網頁伺服器。 這表示任何窺探網路流量的駭客都可以看到用戶名稱和密碼。 若要避免這種情況,請務必使用 安全套接字層 (SSL) 加密網路流量。 這可確保認證(以及整個頁面的 HTML 標記)從離開瀏覽器的那一刻起加密,直到網頁伺服器收到認證為止。

除非您的網站包含敏感資訊,否則您只需要在登入頁面和其他會傳送用戶密碼的頁面上使用SSL,以防止密碼以純文本形式透過網路傳輸。 您不需要擔心保護表單驗證票證,因為在預設情況下,它會被加密並且有數位簽章(以防止竄改)。 下列教學將更深入地探討表單驗證票券的安全性問題。

備註

許多財務和醫療網站都設定為在所有可供已驗證使用者存取 頁面上使用 SSL。 如果您要建置此類網站,您可以設定表單驗證系統,使表單驗證票據僅透過安全連線傳輸。

步驟 4:偵測已驗證的訪客並判斷其身分識別

此時,我們已啟用表單驗證並建立了一個簡單的登入頁面,但尚未研究如何判斷使用者是已驗證還是屬於匿名狀態。 在某些情況下,我們可能會想要根據已驗證或匿名使用者正在瀏覽頁面來顯示不同的數據或資訊。 此外,我們通常需要知道已驗證使用者的身分識別。

讓我們增強現有的Default.aspx頁面,以說明這些技術。 在 Default.aspx新增兩個 Panel 控件,一個名為 AuthenticatedMessagePanel,另一個名為 AnonymousMessagePanel。 在第一個面板中新增名為 WelcomeBackMessage 的標籤控制件。 在第二個 Panel 中新增 HyperLink 控制件,將其 Text 屬性設定為 「Log In」,並將其 NavigateUrl 屬性設定為 “~/Login.aspx”。 此時,Default.aspx的宣告式標記看起來應該如下所示:

<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server">
    <asp:Panel runat="server" ID="AuthenticatedMessagePanel">
        <asp:Label runat="server" ID="WelcomeBackMessage"></asp:Label>
    </asp:Panel>
    
    <asp:Panel runat="Server" ID="AnonymousMessagePanel">
        <asp:HyperLink runat="server" ID="lnkLogin" Text="Log In" NavigateUrl="~/Login.aspx"></asp:HyperLink>
    </asp:Panel>
</asp:Content>

正如您目前可能猜到的,這裡的想法是只向已驗證的訪客顯示 AuthenticatedMessagePanel,而只向匿名訪客顯示 AnonymousMessagePanel。 若要達成此目的,我們需要根據使用者是否登入來設定這些面板的Visible屬性。

Request.IsAuthenticated 屬性會傳回布爾值,指出是否已驗證要求。 在Page_Load事件處理程式程式代碼中輸入下列程式代碼:

protected void Page_Load(object sender, EventArgs e)
{
    if (Request.IsAuthenticated)
    {
        WelcomeBackMessage.Text = "Welcome back!";
    
        AuthenticatedMessagePanel.Visible = true;
        AnonymousMessagePanel.Visible = false;
    }
    else
    {
        AuthenticatedMessagePanel.Visible = false;
        AnonymousMessagePanel.Visible = true;
    }
}

在此程式碼準備好後,請透過瀏覽器訪問Default.aspx。 假設您尚未登入,您會看到登入頁面的連結(請參閱圖 11)。 按兩下此連結並登入網站。 如我們在步驟 3 中所見,輸入您的認證之後,您將會返回Default.aspx,但這次頁面會顯示「歡迎回來!」訊息(請參閱圖 12)。

以匿名方式瀏覽時,會顯示登入連結

圖 11:以匿名方式瀏覽時,會顯示登入連結

向已驗證的用戶顯示

圖 12:已驗證的用戶會顯示「歡迎回來!消息

我們可以透過 HttpContext 物件的User 屬性來判斷目前登入使用者的身分識別。 HttpContext 物件代表目前要求的相關信息,而且是回應、要求和會話等常見 ASP.NET 物件的首頁。 User 屬性代表目前 HTTP 要求的安全性內容,並實作 IPrincipal 介面

User 屬性是由 FormsAuthenticationModule 所設定。 具體而言,當 FormsAuthenticationModule 在傳入要求中找到窗體驗證票證時,它會建立新的 GenericPrincipal 物件,並將它指派給 User 屬性。

主體物件 (例如 GenericPrincipal)提供使用者身分識別及其所屬角色的相關信息。 IPrincipal 介面會定義兩個成員:

我們可以使用下列程式代碼來判斷目前訪客的名稱:

string currentUsersName = User.Identity.Name; // 目前使用者的名稱

當使用窗體驗證時,會為 GenericPrincipal 的 Identity 屬性創建一個 FormsIdentity 物件。 FormsIdentity 類別一律會傳回其 AuthenticationType 屬性的字串 “Forms”,而其 IsAuthenticated 屬性則傳回 true。 Name 屬性會傳回在建立表單驗證通行證時所設定的用戶名稱。 除了這三個屬性之外,FormsIdentity 還包含透過其 Ticket 屬性存取底層驗證票證。 Ticket 屬性會傳回 FormsAuthenticationTicket 類型的物件,其具有 Expiration、IsPersistent、IssueDate、Name 等屬性。

此處要帶走的重點是,在 FormsAuthentication.GetAuthCookie(username, persistCookie)、FormsAuthentication.SetAuthCookie(username, persistCookie),以及 FormsAuthentication.RedirectFromLoginPage(username, persistCookie) 方法中指定的 username 參數與 User.Identity.Name 返回的值相同。 此外,透過將User.Identity轉換成 FormsIdentity 對象,然後存取 Ticket 屬性,即可取得這些方法所建立的驗證票證:

FormsIdentity ident = User.Identity as FormsIdentity;
FormsAuthenticationTicket authTicket = ident.Ticket;

讓我們在 Default.aspx 中提供更個人化的訊息。 更新Page_Load事件處理程式,以便將 WelcomeBackMessage Label 的 Text 屬性指派給字串 “Welcome back, username!”

WelcomeBackMessage.Text = “Welcome back, ” + User.Identity.Name + “!”;

圖 13 顯示這項修改的效果(以使用者 Scott 身分登入時)。

歡迎訊息包含目前登入的用戶名稱

圖 13:歡迎訊息包含目前登入的用戶名稱

使用 LoginView 和 LoginName 控件

向已驗證和匿名用戶顯示不同的內容是常見的需求;因此,會顯示目前登入用戶的名稱。 因此,ASP.NET 包含兩個 Web 控制件,這些控件提供圖 13 中顯示的相同功能,但不需要撰寫單行程式代碼。

LoginView 控件是以範本為基礎的 Web 控件,可讓您輕鬆地向已驗證和匿名的用戶顯示不同的數據。 LoginView 包含兩個預先定義的範本:

  • AnonymousTemplate – 新增至此範本的任何標記只會向匿名訪客顯示。
  • LoggedInTemplate – 此範本的標記只會對已驗證的用戶顯示。

讓我們將 LoginView 控件新增至網站的主版頁面 Site.master。 不過,我們不要只新增 LoginView 控件,而是新增 ContentPlaceHolder 控件,然後將 LoginView 控件放在該新的 ContentPlaceHolder 內。 這一決定的理由很快就會顯現出來。

備註

除了 AnonymousTemplate 和 LoggedInTemplate 之外,LoginView 控制項還可以包含角色特定的範本。 角色特定的範本只會向屬於指定角色的用戶顯示標記。 我們將在未來的教學課程中檢查 LoginView 控件的角色型功能。

首先,將 ContentPlaceHolder 新增至主版頁面導覽 <div> 元素中,並命名為 LoginContent。 您只要將 ContentPlaceHolder 控件從 [工具箱] 拖曳到 [來源] 檢視,將產生的標記放在 "TODO:此處將放置選單…" 文本正上方。

<div id="navigation">
    <asp:ContentPlaceHolder ID="LoginContent" runat="server">
    </asp:ContentPlaceHolder>
   
    TODO: Menu will go here...
</div>

接下來,在 LoginContent ContentPlaceHolder 中新增 LoginView 控件。 放入主版頁面 ContentPlaceHolder 控件的內容會被視為 ContentPlaceHolder 的默認內容 。 也就是說,ASP.NET 使用此主版頁面的頁面可以為每個 ContentPlaceHolder 指定自己的內容,或使用主版頁面的默認內容。

LoginView 和其他登入相關控件位於工具箱的 [登入] 索引卷標中。

工具箱中的LoginView控件

圖 14:工具箱中的 LoginView 控件

接下來,在 LoginView 控件後面立即新增兩個 <br /> 元素,但仍在 ContentPlaceHolder 內。 此時,navigation <div> 元素的標記看起來應該如下所示:

<div id="navigation">
    <asp:ContentPlaceHolder ID="LoginContent" runat="server">
        <asp:LoginView ID="LoginView1" runat="server">
        </asp:LoginView>
        <br /><br />
    </asp:ContentPlaceHolder>
   
    TODO: Menu will go here...
</div>

LoginView 的範本可以從設計工具或宣告式標記定義。 從 Visual Studio 的設計工具,展開 LoginView 的智慧標記,這會在下拉式清單中列出已設定的範本。 在 AnonymousTemplate 中輸入文字 “Hello, stranger” ;接下來,新增 HyperLink 控件,並將其 Text 和 NavigateUrl 屬性分別設定為 “Log In” 和 “~/Login.aspx”。

設定 AnonymousTemplate 之後,切換至 LoggedInTemplate 並輸入「歡迎回來」文字。 然後將 LoginName 控件從工具箱拖曳到 LoggedInTemplate 中,將它放在 [歡迎返回] 文字之後。 LoginName 控制件,如其名稱所示,會顯示目前登入用戶的名稱。 在內部,LoginName 控制項只會輸出 User.Identity.Name 屬性

將這些增補內容新增至 LoginView 的範本之後,標記應該會類似以下內容:

<div id="navigation">
    <asp:ContentPlaceHolder ID="LoginContent" runat="server">
        <asp:LoginView ID="LoginView1" runat="server">
            <LoggedInTemplate>
                Welcome back,
                <asp:LoginName ID="LoginName1" runat="server" />.
            </LoggedInTemplate>
            <AnonymousTemplate>
                Hello, stranger.
                <asp:HyperLink ID="lnkLogin" runat="server" NavigateUrl="~/Login.aspx">Log In</asp:HyperLink>
            </AnonymousTemplate>
        </asp:LoginView>
        
        <br /><br />
    </asp:ContentPlaceHolder>
   
    TODO: Menu will go here...
</div>

除了 Site.master 主版頁面之外,網站中的每個頁面都會根據使用者是否已通過驗證來顯示不同的訊息。 圖 15 顯示使用者 Jisun 透過瀏覽器瀏覽時Default.aspx頁面。 「歡迎回來,Jisun」訊息重複兩次:一次是在左側主版頁面的瀏覽區段中(透過我們剛才新增的LoginView控件),一次是在Default.aspx的內容區域中(透過面板控件和程式設計邏輯)。

LoginView 控制項顯示

圖 15:LoginView 控件會顯示「歡迎回來,Jisun」。

因為我們已將LoginView新增至主版頁面,因此它可以出現在我們網站上的每一個頁面中。 不過,可能會有網頁不想要顯示此訊息。 其中一個這類頁面是登入頁面,因為登入頁面的連結似乎在那裡不合適。 由於我們在主版頁面中將 LoginView 控件放在 ContentPlaceHolder 中,因此我們可以覆寫內容頁面中的這個預設標記。 開啟Login.aspx,然後移至設計工具。 由於我們在 Login.aspx 中未明確定義主版頁面中 LoginContent ContentPlaceHolder 的 Content 控制項,因此登入頁面會顯示此 ContentPlaceHolder 的主版頁面預設標記。 您可以透過設計工具查看此專案 – LoginContent ContentPlaceHolder 會顯示預設標記(LoginView 控件)。

登入頁面顯示主版頁面的LoginContent ContentPlaceHolder的預設內容

圖 16:登入頁面顯示主版頁面的 LoginContent ContentPlaceHolder 的預設內容(按兩下以檢視完整大小的影像

若要覆寫 LoginContent ContentPlaceHolder 的預設標記,只需在設計工具區域上按滑鼠右鍵,然後從快顯功能表選擇 [建立自定義內容] 選項。 (使用 Visual Studio 2008 時,ContentPlaceHolder 包含智慧標記,當選取時,會提供相同的選項。這會將新的 Content 控件新增至頁面的標記,因此可讓我們定義此頁面的自定義內容。 您可以在這裡新增自定義訊息,例如「請登入...」,但讓我們只保留此空白。

備註

在 Visual Studio 2005 中,建立自定義內容會在 ASP.NET 頁面中建立空白的 Content 控制件。 不過,在Visual Studio 2008中,建立自定義內容會將主版頁面的預設內容複製到新建立的內容控件中。 如果您使用 Visual Studio 2008,則在建立新的內容控件之後,請務必清除從主版頁面複製的內容。

此變更之後,圖 17 顯示從瀏覽器訪視時的 Login.aspx 頁面。 請注意,在左側導覽div<中沒有「Hello, stranger」或「Welcome back, >」訊息,就像訪問Default.aspx時一樣。

登入頁面會隱藏預設LoginContent ContentPlaceHolder的標記

圖 17:登入頁面隱藏預設 LoginContent ContentPlaceHolder 的標記 (按兩下以檢視完整大小的影像

步驟 5:註銷

在步驟 3 中,我們探討如何建置登入頁面來將使用者登入網站,但我們尚未瞭解如何將用戶註銷。除了記錄使用者的方法之外,FormsAuthentication 類別也提供 SignOut 方法。 SignOut 方法只會刪除窗體驗證票證,讓使用者登出網站。

提供註銷連結是這類常見的功能,ASP.NET 包含專為將用戶註銷而設計的控件。 LoginStatus 控件 會根據使用者的驗證狀態顯示 “Login” LinkButton 或 “Logout” LinkButton。 匿名使用者會看到「登入」LinkButton,而已驗證的使用者則會顯示「登出」LinkButton。 您可以透過LoginStatus的LoginText和LogoutText屬性來設定 “Login” 和 “Logout” LinkButtons 的文字。

按一下 [登入] LinkButton 會導致頁面回傳並重新導向至登入頁面。 按兩下 「Logout」 LinkButton 會導致 LoginStatus 控制項叫用 FormsAuthentication.SignOff 方法,然後將使用者重新導向至頁面。 註銷使用者重新導向至的頁面取決於LogoutAction屬性,其可指派給下列三個值之一:

  • 重新整理 – 預設值;會將使用者重新導向至他們剛瀏覽的頁面。 如果他們剛瀏覽的頁面不允許匿名使用者,則 FormsAuthenticationModule 會自動將使用者重新導向至登入頁面。

您可能會好奇為什麼在這裡執行重新導向。 如果使用者想要維持在相同的頁面上,為什麼需要明確重新導向? 原因是當單擊「登出」連結按鈕時,使用者的 Cookie 集合中仍然保留著表單驗證票證。 因此,回傳要求是已驗證的要求。 LoginStatus 控件會呼叫 SignOut 方法,但會在 FormsAuthenticationModule 驗證使用者之後發生。 因此,明確重新導向會導致瀏覽器重新要求頁面。 當瀏覽器重新請求頁面時,由於表單驗證票證已被移除,因此傳入的請求是匿名的。

  • 重新導向 – 用戶會重新導向至 LoginStatus 的 LogoutPageUrl 屬性所指定的 URL。
  • RedirectToLoginPage – 使用者會重新導向至登入頁面。

讓我們將 LoginStatus 控制項新增至主版頁面,並將其設定為使用 [重新導向] 選項,將使用者傳送至顯示確認已註銷訊息的頁面。首先,在名為 Logout.aspx 的根目錄中建立頁面。 別忘了將此頁面與 Site.master 主版頁面產生關聯。 接下來,在頁面標記中輸入訊息,向使用者說明他們已註銷。

接下來,返回 Site.master 主版頁面,然後在 LoginContent ContentPlaceHolder 的 LoginView 底下新增 LoginStatus 控件。 將 LoginStatus 控件的 LogoutAction 屬性設定為 Redirect,並將其 LogoutPageUrl 屬性設定為 “~/Logout.aspx”。

<div id="navigation">
    <asp:ContentPlaceHolder ID="LoginContent" runat="server">
        <asp:LoginView ID="LoginView1" runat="server">
            <LoggedInTemplate>
                Welcome back,
                <asp:LoginName ID="LoginName1" runat="server" />.
            </LoggedInTemplate>
            <AnonymousTemplate>
                Hello, stranger.
                <asp:HyperLink ID="lnkLogin" runat="server" NavigateUrl="~/Login.aspx">Log In</asp:HyperLink>
            </AnonymousTemplate>
        </asp:LoginView>
        <br />
        <asp:LoginStatus ID="LoginStatus1" runat="server" LogoutAction="Redirect" LogoutPageUrl="~/Logout.aspx" />
        
        <br /><br />
    </asp:ContentPlaceHolder>
   
    TODO: Menu will go here...
</div>

由於 LoginStatus 不在 LoginView 控件之外,因此會同時針對匿名和已驗證的用戶顯示,但沒關係,因為 LoginStatus 會正確顯示 “Login” 或 “Logout” LinkButton。 新增 LoginStatus 控件時,AnonymousTemplate 中的 “Log In” HyperLink 是多餘的,因此請將其移除。

圖 18 顯示 Jisun 訪問時的 Default.aspx。 請注意,左側數據行會顯示訊息「歡迎回來,Jisun」以及註銷的連結。單擊註銷 LinkButton 會導致回傳、將 Jisun 註銷系統,然後將她重新導向至Logout.aspx。 如圖 19 所示,當 Jisun 到達 Logout.aspx 時,她已經註銷,因此處於匿名狀態。 因此,左側數據行會顯示「歡迎、陌生人」文字,以及登入頁面的連結。

Default.aspx顯示

圖 18: Default.aspx顯示「歡迎回來,Jisun」以及「登出」LinkButton(按一下以查看完整大小的圖片

Logout.aspx顯示

圖 19:Logout.aspx顯示「歡迎、陌生人」以及「登入」LinkButton(按兩下以檢視完整大小的影像

備註

我鼓勵您自定義Logout.aspx頁面,以隱藏主版頁面的LoginContent ContentPlaceHolder(就像我們在步驟4中為Login.aspx所做的一樣)。 原因是 LoginStatus 控件所轉譯的 “Login” LinkButton (在 “Hello, stranger” 底下)會將用戶傳送至在 ReturnUrl querystring 參數中傳遞目前 URL 的登入頁面。 簡言之,如果已註銷的用戶點擊這個 LoginStatus 的 [登入] LinkButton,然後登入,他們會被重新導向到 Logout.aspx,這可能會讓使用者感到困惑。

總結

在本教學中,我們從檢視表單驗證的工作流程開始,接著在 ASP.NET 應用程式中實作表單驗證。 表單驗證是由 FormsAuthenticationModule 所提供,其有兩項責任:根據表單驗證票證識別使用者,並將未經授權的使用者導引至登入頁面。

.NET Framework 的 FormsAuthentication 類別包含用於建立、檢查和移除表單驗證票的方法。 Request.IsAuthenticated 屬性和 User 物件提供額外的程式設計支援,以判斷要求是否已通過驗證,以及使用者身分識別的相關信息。 另外還有 LoginView、LoginStatus 和 LoginName Web 控件,可讓開發人員快速、無程式碼的方式執行許多常見的登入相關工作。 我們將在未來的教學課程中更詳細地檢查這些和其他登入相關的 Web 控件。

本教學課程提供表單驗證的簡要概述。 我們沒有檢查各種配置選項、查看不使用 Cookie 的表單身份驗證票證如何運作,或探索 ASP.NET 如何保護表單身份驗證票證的內容。

快樂的程序設計!

進一步閱讀

如需本教學課程中所討論主題的詳細資訊,請參閱下列資源:

本教程中所涉及主題的影片訓練

關於作者

斯科特·米切爾,七本 ASP/ASP.NET 書籍和 4GuysFromRolla.com 創始人的作者,自1998年以來一直與Microsoft Web 技術合作。 斯科特擔任獨立顧問、教練和作家。 他的最新書是《Sams 24小時自學ASP.NET 2.0》。 可以聯絡他 mitchell@4GuysFromRolla.com。

特別感謝...

本教學系列已由許多熱心的評論者審閱。 本教學課程的主要檢閱者是某某。這個教學系列由許多有幫助的檢閱者審閱。 本教學課程的主要檢閱者包括 Alicja Maziarz、John Suru 和 Teresa Murphy。 有興趣檢閱我即將推出的 MSDN 文章嗎? 如果是,請在 mitchell@4GuysFromRolla.com給我留言。