保護使用驗證和授權的應用程式

Microsoft提供

下載 PDF

這是免費 「NerdDinner」 應用程式教學 課程的步驟 9,逐步解說如何使用 ASP.NET MVC 1 建置小型但完整的 Web 應用程式。

步驟 9 示範如何新增驗證和授權來保護 NerdDinner 應用程式的安全,讓使用者需要註冊並登入網站以建立新的晚餐,而且只有裝載晚餐的使用者稍後才能編輯。

如果您使用 ASP.NET MVC 3,建議您遵循使用MVC 3MVC 音樂市集教學課程消費者入門。

NerdDinner 步驟 9:驗證和授權

現在,我們的 NerdDinner 應用程式會授與任何造訪網站的任何人建立及編輯任何晚餐的詳細資料。 讓我們變更這一點,讓使用者需要註冊並登入網站以建立新的晚餐,並新增限制,讓只有裝載晚餐的使用者稍後才能編輯。

為了啟用此功能,我們將使用驗證和授權來保護應用程式的安全。

瞭解驗證和授權

驗證 是識別及驗證存取應用程式之用戶端身分識別的程式。 更簡單地說,這是關於識別使用者造訪網站時的「誰」。 ASP.NET 支援多種方式來驗證瀏覽器使用者。 對於網際網路 Web 應用程式,最常使用的驗證方法稱為「表單驗證」。 表單驗證可讓開發人員在其應用程式中撰寫 HTML 登入表單,然後驗證使用者針對資料庫或其他密碼認證存放區提交的使用者名稱/密碼。 如果使用者名稱/密碼組合正確,開發人員接著可以要求 ASP.NET 發出加密的 HTTP Cookie,以識別未來要求的使用者。 我們將使用表單驗證搭配 NerdDinner 應用程式。

授權 是判斷已驗證使用者是否有權存取特定 URL/資源或執行某些動作的程式。 例如,在我們的 NerdDinner 應用程式中,我們想要授權只有登入的使用者才能存取 /Dinners/Create URL,並建立新的 Dinners。 我們也想要新增授權邏輯,以便只有裝載晚餐的使用者可以編輯它,並拒絕對所有其他使用者的編輯存取。

表單驗證和 AccountController

ASP.NET MVC 的預設 Visual Studio 專案範本會在建立新的 ASP.NET MVC 應用程式時自動啟用表單驗證。 它也會自動將預先建置的帳戶登入頁面實作新增至專案,這可讓您輕鬆地在網站內整合安全性。

當使用者存取網站時,預設的 Site.master 主版頁面會在網站右上方顯示「登入」連結:

Nerd Dinner Host a Dinner 頁面的螢幕擷取畫面。登入會在右上角反白顯示。

按一下 [登入] 連結會將使用者帶至 /Account/LogOn URL:

[Nerd Dinner Log On] 頁面的螢幕擷取畫面。

尚未註冊的訪客可以按一下 [註冊] 連結來執行這項操作,這會將它們帶至 /Account/Register URL,並允許他們輸入帳戶詳細資料:

[Nerd Dinner Create a New Account] 頁面的螢幕擷取畫面。

按一下 [註冊] 按鈕會在 ASP.NET 成員資格系統中建立新的使用者,並使用表單驗證在月臺上驗證使用者。

當使用者登入時,Site.master 會將頁面右上方變更為輸出「歡迎使用 [使用者名稱]!「 訊息,並轉譯「登出」連結,而不是「登入」連結。 按一下 [登出] 連結會登出使用者:

Nerd Dinner Host a Dinner form page 的螢幕擷取畫面。[歡迎] 和 [登出] 按鈕會在右上角反白顯示。

上述登入、登出和註冊功能是在建立專案時由 Visual Studio 新增至專案的 AccountController 類別內實作。 AccountController 的 UI 是使用 \Views\Account 目錄中的檢視範本來實作:

Nerd Dinner 導覽樹狀結構的螢幕擷取畫面。帳戶控制器點 c s 已反白顯示。[帳戶資料夾] 和功能表項目也會反白顯示。

AccountController 類別會使用 ASP.NET Forms 驗證系統發出加密的驗證 Cookie,以及 ASP.NET 成員資格 API 來儲存及驗證使用者名稱/密碼。 ASP.NET 成員資格 API 是可延伸的,並且允許使用任何密碼認證存放區。 ASP.NET 隨附內建成員資格提供者實作,可儲存 SQL 資料庫內或 Active Directory 內的使用者名稱/密碼。

我們可以設定 NerdDinner 應用程式應該使用的成員資格提供者,方法是在專案的根目錄開啟 「web.config」 檔案,並尋找 < 其中的成員資格 > 區段。 建立專案時新增的預設web.config會註冊 SQL 成員資格提供者,並將它設定為使用名為 「ApplicationServices」 的連接字串來指定資料庫位置。

web.config檔案的 connectionStrings > 區段內 < 指定的預設 「ApplicationServices」 連接字串) (會設定為使用 SQL Express。 它會指向名為 「ASPNETDB」 的 SQL Express 資料庫。應用程式 「App_Data」 目錄下的 MDF。 如果第一次在應用程式內使用成員資格 API 時,此資料庫不存在,ASP.NET 會自動建立資料庫,並在其中布建適當的成員資格資料庫架構:

Nerd Dinner 導覽樹狀結構的螢幕擷取畫面。應用程式資料已展開,並已選取 S P NET D B 點 M D F。

如果我們想要使用完整SQL Server實例 (,或連線到遠端資料庫) ,我們只需要更新web.config檔案內的 「ApplicationServices」 連接字串,並確定已將適當的成員資格架構新增至它所指向的資料庫。 您可以在 \Windows\Microsoft.NET\Framework\v2.0.50727\ 目錄中執行 「aspnet_regsql.exe」 公用程式,將成員資格和其他 ASP.NET 應用程式服務新增至資料庫的適當架構。

使用 [授權] 篩選來授權 /Dinners/Create URL

我們不需要撰寫任何程式碼,以啟用 NerdDinner 應用程式的安全驗證和帳戶管理實作。 使用者可以向我們的應用程式註冊新帳戶,以及網站的登入/登出。

現在,我們可以將授權邏輯新增至應用程式,並使用訪客的驗證狀態和使用者名稱來控制其可在網站內執行和無法執行的動作。 讓我們從將授權邏輯新增至 DinnersController 類別的「Create」 動作方法開始。 具體而言,我們將要求存取 /Dinners/Create URL 的使用者必須登入。 如果未登入,我們會將它們重新導向至登入頁面,讓他們可以登入。

實作此邏輯相當簡單。 我們只需要將 [授權] 篩選屬性新增至我們的 Create 動作方法,如下所示:

//
// GET: /Dinners/Create

[Authorize]
public ActionResult Create() {
   ...
} 

//
// POST: /Dinners/Create

[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(Dinner dinnerToCreate) {
   ...
}

ASP.NET MVC 支援建立「動作篩選」的能力,可用來實作可宣告方式套用至動作方法的可重複使用邏輯。 [授權] 篩選是 ASP.NET MVC 所提供的其中一個內建動作篩選器,可讓開發人員以宣告方式將授權規則套用至動作方法和控制器類別。

若未套用任何參數, (如上述) [授權] 篩選準則,則會強制使用者提出動作方法要求時必須登入,而且如果不是,它會自動將瀏覽器重新導向至登入 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() {
   ...
}

建立 Dinners 時使用 User.Identity.Name 屬性

我們可以使用控制器基類上公開的 User.Identity.Name 屬性,擷取要求目前登入使用者的使用者名稱。

稍早在我們實作 Create () 動作方法的 HTTP-POST 版本時,我們已將 Dinner 的 「HostedBy」 屬性硬式編碼為靜態字串。 我們現在可以更新此程式碼來改用 User.Identity.Name 屬性,並自動新增 RSVP 給建立 Dinner 的主機:

//
// 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));
}

因為我們已將 [Authorize] 屬性新增至 Create () 方法,所以 ASP.NET MVC 可確保只有在流覽 /Dinners/Create URL 的使用者登入網站時,才會執行動作方法。 因此,User.Identity.Name 屬性值一律會包含有效的使用者名稱。

編輯 Dinners 時使用 User.Identity.Name 屬性

現在,讓我們新增一些授權邏輯來限制使用者,以便他們只能編輯自己裝載的晚餐屬性。

為了協助解決此問題,我們會先將 「IsHostedBy (username) 」 協助程式方法新增至 Dinner (.cs 部分類別內我們稍早建置) 的 Dinner.cs 部分類別中。 這個協助程式方法會根據提供的使用者名稱是否符合 Dinner HostedBy 屬性,傳回 true 或 false,並封裝執行不區分大小寫字串比較所需的邏輯:

public partial class Dinner {

    public bool IsHostedBy(string userName) {
        return HostedBy.Equals(userName, StringComparison.InvariantCultureIgnoreCase);
    }
}

然後,我們會在 DinnersController 類別中,將 [Authorize] 屬性新增至 Edit () 巨集指令方法。 這可確保使用者必須登入以要求 /Dinners/Edit/[id] URL。

然後,我們可以將程式碼新增至使用 Dinner.IsHostedBy 的 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>

現在,當使用者嘗試編輯他們不擁有的晚餐時,他們會收到錯誤訊息:

Nerd Dinner 網頁上錯誤訊息的螢幕擷取畫面。

我們可以針對控制器內的 Delete () 動作方法重複相同的步驟,以鎖定刪除 Dinners 的許可權,並確保只有 Dinner 的主機可以刪除它。

我們正在從詳細資料 URL 連結到 DinnersController 類別的 Edit and Delete 動作方法:

Nerd Dinner 頁面的螢幕擷取畫面。[編輯] 和 [刪除] 按鈕會圓圈在底部。詳細資料 U R L 會圓圈在頂端。

目前我們顯示 [編輯] 和 [刪除] 動作連結,不論詳細資料 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 進行晚餐。