日本电影一区二区_日本va欧美va精品发布_日本黄h兄妹h动漫一区二区三区_日本欧美黄色

使用 ASP.NET Core 構(gòu)建 Web API:9 身份驗證和授權(quán)

本章涵蓋

  • 了解身份驗證和授權(quán)
  • 獲取 ASP.NET 核心標識的概述
  • 通過用戶帳戶和 JSON Web 令牌實現(xiàn)身份驗證
  • 使用 AuthorizeAttribute 和 IAuthorizationFilter 啟用授權(quán)
  • 了解基于角色的訪問控制 (RBAC) 授權(quán)策略

我們在前面幾章中構(gòu)建的 ASP.NET Core Web API 已經(jīng)成型。但是,在發(fā)布它之前,我們必須解決一些我們有意保持打開狀態(tài)的主要安全權(quán)限問題。如果我們仔細看看我們的BoardGamesController,DomainsController和MechanicsController,我們可以看到它們都有一些Post和Delete方法,任何人都可以用它來改變我們的寶貴數(shù)據(jù)。我們不希望這樣,是嗎?

出于這個原因,在考慮通過互聯(lián)網(wǎng)部署我們的 Web API 并使其可公開訪問之前,我們需要找到一種方法將這些方法的使用限制為有限的一組授權(quán)用戶。在本章中,我們將學習如何使用 ASP.NET Core Identity 來執(zhí)行此操作,核心標識是一個內(nèi)置 API,可用于管理用戶、角色、聲明、令牌、策略、與授權(quán)相關(guān)的行為和其他功能。

9.1 基本概念

在深入研究代碼之前,最好先概述一下身份驗證授權(quán)的概念。雖然這兩個術(shù)語經(jīng)常在同一上下文中使用,但它們具有不同、精確的含義。

9.1.1 身份驗證

在信息安全中,身份驗證是指驗證計算機、軟件或用戶正確身份的行為。我們可以說,身份驗證是一種驗證實體(或個人)是它聲稱(或他們聲稱)的機制。

無論出于何種原因,身份驗證過程對于需要唯一標識其用戶的任何 Web 應用或服務都至關(guān)重要 – 限制對部分(或全部)用戶數(shù)據(jù)的訪問、收集個人信息、記錄和/或跟蹤用戶在使用服務時的操作、注意他們是否已登錄、在某個非活動期后斷開它們, 等等。

此外,身份驗證通常在增強 Web 服務(及其背后的組織)的數(shù)據(jù)保護、安全性和監(jiān)視功能方面發(fā)揮重要作用。唯一地驗證關(guān)聯(lián)主體的身份意味著系統(tǒng)內(nèi)執(zhí)行的所有操作都可以合理確定地追溯到其作者,從而促進遵守組織的問責制政策。

問 責

問責制是 ISO/IEC 27001 的關(guān)鍵原則,ISO/IEC <> 是眾所周知的國際標準,為組織內(nèi)設(shè)計、實施和運營信息安全管理系統(tǒng)提供了系統(tǒng)的方法。

大多數(shù)歐盟隱私機構(gòu)也強調(diào)了身份驗證和問責制之間的聯(lián)系。根據(jù)意大利數(shù)據(jù)保護局的說法,“. .共享憑據(jù)可防止在計算機系統(tǒng)中執(zhí)行的操作歸因于特定的負責人,也損害了所有者,剝奪了檢查此類相關(guān)技術(shù)人物工作的可能性“(第4/4/2019條)。

大多數(shù) Web 應用、Web 服務和 IT 設(shè)備都要求其用戶在授予訪問權(quán)限之前完成某種身份驗證過程。此過程可能涉及使用指紋解鎖我們的智能手機,登錄Facebook或LinkedIn帳戶,在Instagram上發(fā)布照片 – 所有形式的身份驗證過程,即使其中一些是在后臺執(zhí)行的,因為用戶同意他們的設(shè)備存儲他們的憑據(jù)并自動使用它們。

現(xiàn)在有幾種身份驗證技術(shù)可用,例如用戶名(或電子郵件)和密碼;發(fā)送到電子郵件或移動設(shè)備的一次性 PIN 碼 (OTP);個人認證應用生成的一次性安全碼;以及指紋、視網(wǎng)膜和/或語音等生物特征掃描。我不會在本章中介紹所有這些技術(shù),但一些在線資源可以提供有關(guān)這些主題的更多信息。

提示有關(guān) ASP.NET Core 應用中身份驗證的更多詳細信息,請查看 http://mng.bz/jm6y。

9.1.2 授權(quán)

一般而言,授權(quán)是指行使、執(zhí)行或行使某些權(quán)利的許可或權(quán)力。在IT領(lǐng)域,授權(quán)被定義為系統(tǒng)能夠?qū)⒃L問權(quán)限(也稱為權(quán)限)分配給單個計算機,軟件或用戶(或組)的過程。這些任務通常通過實現(xiàn)訪問策略、聲明或權(quán)限組來處理,這些策略、聲明或權(quán)限組允許或禁止一組給定邏輯空間(文件系統(tǒng)文件夾、驅(qū)動器網(wǎng)絡、數(shù)據(jù)庫、網(wǎng)站部分、Web API 終結(jié)點等)中的每個相關(guān)操作或活動(讀取、寫入、刪除等)。實際上,通常通過定義一系列訪問控制列表 (ACL) 來提供或拒絕授權(quán),這些列表指定

  • 特定資源允許的訪問類型(讀取、寫入、刪除等)
  • 授予或拒絕哪些計算機、軟件或用戶(或組)訪問權(quán)限

盡管授權(quán)是正交的并且獨立于身份驗證,但這兩個概念本質(zhì)上是交織在一起的。如果系統(tǒng)無法識別其用戶,則無法將其與其ACL正確匹配,從而授予或拒絕對其資源的訪問權(quán)限。因此,大多數(shù)訪問控制機制都設(shè)計為同時要求身份驗證和授權(quán)。更準確地說,它們執(zhí)行以下操作:

  • 將盡可能低的授權(quán)權(quán)限分配給未經(jīng)身份驗證的(匿名)用戶。這些權(quán)限通常包括訪問公共(無限制)內(nèi)容以及登錄頁面、模塊或表單。
  • 對成功執(zhí)行登錄嘗試的用戶進行身份驗證。
  • 檢查其 ACL 以將適當?shù)脑L問權(quán)限(權(quán)限)分配給經(jīng)過身份驗證的用戶。
  • 是否授權(quán)用戶訪問受限制的內(nèi)容,具體取決于授予他們或他們所屬的組的權(quán)限。

圖 9.1 描述了此方案中描述的身份驗證和授權(quán)流。該圖模擬了具有一組只能由授權(quán)用戶訪問的資源的典型 Web 應用程序的行為。

使用 ASP.NET Core 構(gòu)建 Web API:9 身份驗證和授權(quán)

圖9.1 認證授權(quán)流程

在圖中,身份驗證過程應在授權(quán)之前進行,因為后者需要前者來執(zhí)行其工作。但這種情況不一定是真的。如果匿名用戶嘗試訪問受限資源,授權(quán)系統(tǒng)將在身份驗證之前啟動,拒絕對未經(jīng)身份驗證的用戶的訪問,并可能驅(qū)動 Web 應用程序?qū)⒂脩糁囟ㄏ虻降卿涰撁?。在某些邊緣情況下,甚至可能存在只能由匿名用戶(未經(jīng)身份驗證的用戶)訪問的資源。一個典型的示例是登錄頁面,因為在注銷之前,絕不應允許經(jīng)過身份驗證的用戶執(zhí)行其他登錄嘗試。

注意將所有這些點連接起來,我們應該看到身份驗證和授權(quán)是不同的、獨立的和獨立的東西,即使它們最終是為了一起工作。即使授權(quán)可以在不知道連接方身份的情況下工作(只要它為未經(jīng)身份驗證的用戶提供可行的 ACL),它也需要一個身份驗證機制來完成其其余的工作。

現(xiàn)在我們已經(jīng)有了大致的了解,我們需要了解如何在 Web API 中實現(xiàn)可行的身份驗證和授權(quán)機制。正如我們之前所了解的,在典型的 Web 應用程序中,身份驗證過程(通常由登錄階段表示)應該在授權(quán)部分之前發(fā)生。當用戶成功登錄后,我們將了解該用戶的權(quán)限并授權(quán)他們?nèi)ィɑ虿蝗ィ┤魏蔚胤健?/span>

但我們也(可能)知道HTTP協(xié)議是無狀態(tài)的。每個請求都是獨立執(zhí)行的,不知道之前執(zhí)行的請求??蛻舳撕头掌髟谡埱?響應周期內(nèi)執(zhí)行的所有操作(包括發(fā)送和/或接收的所有數(shù)據(jù))都將在響應結(jié)束時丟失,除非客戶端和服務器配備了一些機制來將此數(shù)據(jù)存儲在某個位置。

注意這些機制不是 HTTP 協(xié)議的一部分,但它們通常利用其某些功能;換句話說,它們是建立在它之上的。很好的例子是我們在第8章中看到的緩存技術(shù),它可以在客戶端和/或服務器端實現(xiàn)。這些技術(shù)使用一組特定的 HTTP 標頭(如緩存控制)來指示緩存服務要執(zhí)行的操作。

如果我們將這兩個事實聯(lián)系起來,我們會看到我們遇到了一個問題:如果每個請求都不知道之前發(fā)生了什么,我們?nèi)绾沃烙脩羰欠褚淹ㄟ^身份驗證?我們?nèi)绾胃櫽傻卿洷韱斡|發(fā)的請求/響應周期的結(jié)果,即登錄結(jié)果和(如果成功)用戶的身份?下一節(jié)簡要介紹一些解決此問題的方法。

實現(xiàn)方法

在現(xiàn)代 Web 服務和應用程序中設(shè)置 HTTP 身份驗證的最常用方法是會話/cookie、持有者令牌、API 密鑰、簽名和證書。這些技術(shù)中的大多數(shù)不需要普通Web開發(fā)人員的介紹,但是花一些時間描述它們的工作原理可能是明智的:

  • 會話/Cookie – 此方法依賴于鍵/值存儲服務,通常位于 Web 服務器或外部服務器或集群上。Web 應用程序使用此服務來存儲用戶身份驗證信息(會話),并為其分配自動生成的唯一 sessionId。然后,sessionId 通過 cookie 發(fā)送到瀏覽器,以便在所有后續(xù)請求中重新發(fā)送,并在服務器上用于檢索用戶的會話并以無縫、透明的方式采取相應的行動(執(zhí)行基于授權(quán)的檢查)。
  • 持有者令牌 – 此方法依賴于身份驗證服務器生成并包含相關(guān)授權(quán)信息的加密令牌。此令牌將發(fā)送到客戶端,客戶端可以通過在授權(quán) HTTP 標頭中設(shè)置令牌來使用它來執(zhí)行后續(xù)請求(直到過期),而無需進一步的身份驗證嘗試。
  • API 密鑰 – 運行 Web API 的服務為其用戶提供可用于訪問 API 的 ClientID 和 CLIentSecret 對(或讓他們有機會生成它們)。通常,該對在每個請求時通過授權(quán) HTTP 標頭發(fā)送。但是,與不需要身份驗證的持有者令牌不同(稍后會詳細介紹),ClientID 和 ClientSecret 通常用于每次對請求用戶進行身份驗證,以及授權(quán)該用戶。
  • 簽名和證書 – 這兩種身份驗證方法使用以前共享的私鑰和/或傳輸層安全性 (TLS) 證書執(zhí)行請求的哈希。此技術(shù)可確保沒有入侵者或中間人可以充當請求方,因為他們將無法“簽署”HTTP 請求。這些方法對于安全性非常有用,但對于雙方來說,它們可能很難設(shè)置和實施,這限制了它們對需要特別高的數(shù)據(jù)保護標準的服務。

我們應該為我們的MyBGList Web API使用以下哪種方法?與往常一樣,我們應該考慮每種選擇的利弊。以下是快速細分:

  • 會話/cookie顯然不在圖片之外,因為它們會否定我們的RESTful目的,例如我們自第3章以來就知道的無狀態(tài)約束。
  • 持有者令牌提供了不錯的安全態(tài)勢,并且易于實現(xiàn),特別是考慮到 ASP.NET 核心身份(幾乎)開箱即用地支持它們。
  • API 密鑰提供了更好的安全態(tài)勢,但它們需要大量額外的工作,例如提供專用的管理網(wǎng)站或 API 集,以使用戶能夠正確管理它們。
  • 從安全角度來看,簽名和證書很棒,但它們需要更多的額外工作,這可能會導致我們出現(xiàn)一些延遲和/或增加總體成本。

因為我們處理的是棋盤游戲,而不是敏感數(shù)據(jù),所以至少從成本/收益的角度來看,持有者代幣方法似乎是我們最好的選擇。這種選擇的好處是,它共享了實現(xiàn) API 密鑰方法所需的大部分工作。這是學習 ASP.NET 核心標識基本技術(shù)并通過為大多數(shù) Web API 構(gòu)建可行的身份驗證和授權(quán)機制將其付諸實踐的絕佳機會。下一節(jié)介紹持有者令牌的工作原理。

警告本章及其源代碼示例的主要目的是概述可用于 Web API 的各種身份驗證和授權(quán)機制,并就如何使用 ASP.NET 核心標識實現(xiàn)其中一些機制提供一般指導。但是,了解這些方法是黑客攻擊、拒絕服務 (DoS) 攻擊以及第三方執(zhí)行的其他一些惡意活動的主要目標至關(guān)重要,這些活動可以輕松利用陷阱、實現(xiàn)錯誤、未更新的庫、零日錯誤等。因此,如果你的 Web API 和/或其基礎(chǔ)數(shù)據(jù)源包含個人、敏感或有價值的數(shù)據(jù),請考慮通過使用我隨它們提供的安全相關(guān)超鏈接以及有關(guān)每個主題的其他權(quán)威教程來集成或改進我們的代碼示例來加強安全狀況。

不記名令牌

基于令牌的身份驗證(也稱為持有者身份驗證)是 Web API 最常用的方法之一。如果實施得當,它可以在不破壞無狀態(tài) REST 約束的情況下提供可接受的安全標準。

基于令牌的身份驗證仍要求用戶使用用戶名和密碼對自己進行身份驗證(執(zhí)行登錄)。但是,身份驗證過程成功后,服務器不會創(chuàng)建持久會話,而是生成一個加密的授權(quán)令牌,其中包含有關(guān)結(jié)果的一些相關(guān)信息,例如對用戶標識 (userId) 的引用、有關(guān)連接客戶端的一些信息、令牌到期日期等。此令牌一旦被客戶端檢索,就可以在任何后續(xù)請求的授權(quán) HTTP 標頭中設(shè)置,以獲取對受限(授權(quán))資源的訪問權(quán)限,直到過期。圖9.2總結(jié)了這一過程。

使用 ASP.NET Core 構(gòu)建 Web API:9 身份驗證和授權(quán)

圖9.2 持有者令牌授權(quán)流程

如我們所見,服務器不存儲任何數(shù)據(jù)。至于客戶端,實現(xiàn)可能會有所不同:令牌可以存儲在本地(并重復使用直到過期)或在首次使用后丟棄。持有者令牌的主要優(yōu)點是它們是一種獨立的授權(quán)機制,因為它們的存在會自動意味著身份驗證嘗試成功。單個令牌可用于授權(quán)發(fā)往多個 Web API 和/或服務的受限請求,即使它們托管在其他地方和/或無法訪問用戶登錄數(shù)據(jù),只要它們共享生成它們的身份驗證服務使用的相同頒發(fā)者簽名密鑰。

注意這種多功能性(和性能優(yōu)勢)也是主要安全漏洞的原因:代幣發(fā)行后,它們不能輕易失效(或更新)。如果第三方設(shè)法竊取和使用令牌,他們將能夠執(zhí)行授權(quán)請求,直到令牌過期。此外,開發(fā)人員、系統(tǒng)管理員和用戶無法輕松擺脫該令牌,即使他們知道它已被泄露。即使禁用原始用戶也無法解決問題,因為該令牌是該用戶仍處于活動狀態(tài)時發(fā)生的身份驗證過程的結(jié)果。此安全問題的最佳解決方法是盡可能縮短這些令牌的生命周期(理想情況下,縮短到幾分鐘),以便攻擊者沒有太多時間采取行動。

現(xiàn)在我們已經(jīng)為具體方案選擇了一條路徑并了解了它應該如何工作,是時候熟悉我們將用于實現(xiàn)它的框架了。

9.2 ASP.NET 核心身份

ASP.NET 核心標識 API 提供了一組接口和高級抽象,可用于在任何 ASP.NET 核心應用中管理和存儲用戶帳戶。盡管它可以與任何數(shù)據(jù)庫和/或對象關(guān)系映射/映射器 (ORM) 一起使用,但該框架已經(jīng)提供了多個類、幫助程序和擴展方法,允許我們將其所有功能與實體框架核心 (EF Core) 數(shù)據(jù)模型一起使用,這使其非常適合我們當前的方案。

注意ASP.NET 核心身份源代碼是開源的,可在 GitHub 上找到 http://mng.bz/WAmx。

在以下部分中,我們將學習如何使用 ASP.NET 核心身份為我們現(xiàn)有的 MyBGList Web API 項目提供身份驗證功能。(接下來將進行授權(quán)。為此,我們將執(zhí)行以下步驟:

  1. 安裝所需的 NuGet 包。
  2. 創(chuàng)建一個新的 MyBGListUser 實體類來處理用戶名和密碼等用戶數(shù)據(jù)。
  3. 更新我們現(xiàn)有的 ApplicationDbContext,使其能夠處理新的用戶實體。
  4. 添加并應用新遷移,以使用核心標識所需的數(shù)據(jù)庫表更新基礎(chǔ)數(shù)據(jù)庫 ASP.NET。
  5. 在程序.cs文件中設(shè)置和配置所需的標識服務和中間件。
  6. 實現(xiàn)新控制器來處理注冊過程(創(chuàng)建新用戶)和登錄過程(將臨時訪問令牌分配給現(xiàn)有用戶)。

9.2.1 安裝 NuGet 包

若要將 ASP.NET 核心標識功能添加到項目中,我們需要以下 NuGet 包:

  • Microsoft.Extensions.Identity.Core,包含成員系統(tǒng)以及處理我們需要的各種登錄功能的主要類和服務
  • Microsoft.ASPNetCore.Identity.EntityFrameworkCore,EF Core 的 ASP.NET Core Identity 提供程序
  • Microsoft.AspNetCore.Authentication.JwtBearer,包含使 ASP.NET 核心應用程序能夠處理JSON Web令牌(JWT)的中間件

與往常一樣,我們可以選擇使用 NuGet 包管理器或包管理器控制臺在 Visual Studio 中安裝所需的 NuGet 包,或者使用 .NET Core 命令行界面 (CLI) 從命令行安裝所需的 NuGet 包。若要使用 CLI,請打開命令提示符,導航到項目的根文件夾,然后鍵入以下命令:

> dotnet add package Microsoft.Extensions.Identity.Core --version 6.0.11> dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore --? version 6.0.11> dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --? version 6.0.11

現(xiàn)在我們可以開始編寫一些東西,從我們在第 4 章中創(chuàng)建的 ApplicationDbContext 類開始。

9.2.2 創(chuàng)建用戶實體

現(xiàn)在我們已經(jīng)安裝了標識包,我們需要創(chuàng)建一個新的實體類,表示我們要進行身份驗證和授權(quán)的用戶。此實體的名稱將為 ApiUser。

注意理想情況下,我們可以稱這個實體為User,但該通用名稱會與其他內(nèi)置屬性(如ControllerBase.User)產(chǎn)生一些令人討厭的沖突。為了避免這個問題,我強烈建議選擇一個更獨特的名稱。

因為我們使用的是 ASP.NET 核心身份,所以我們可以實現(xiàn)新實體的最好辦法是擴展框架提供的默認實現(xiàn)來處理由 IdentityUser 類(Microsoft的一部分)表示的身份用戶。AspNetCore.Identity 命名空間)。創(chuàng)建一個新的 /Model/ApiUser.cs 類文件,并使用以下代碼填充該文件:

using Microsoft.AspNetCore.Identity; namespace MyBGList.Models{ public class ApiUser : IdentityUser { }}

就這樣。我們現(xiàn)在不需要實現(xiàn)更多的東西,因為 IdentityUser 類已經(jīng)包含我們需要的所有屬性:用戶名、密碼等。

提示由于篇幅原因,我不會提供對 IdentityUser 默認類的廣泛描述。若要了解有關(guān)它(及其屬性)的詳細信息,請參閱 http://mng.bz/8182 中的定義。

現(xiàn)在我們有一個專用的實體來處理我們的用戶,我們可以更新我們的 ApplicationDbContext 類以充分利用它。

9.2.3 更新應用程序數(shù)據(jù)庫上下文

在第 4 章中,當我們創(chuàng)建 ApplicationDbContext 類時,我們擴展了 DbContext 基類。為了使它能夠處理我們新的 ApiUser 實體,我們需要使用另一個基類來更改它,該基類包含我們需要 ASP.NET 核心標識功能。這個基類的名稱是(你可能猜到的)IdentityDbContext,它是我們之前安裝的Microsoft.AspNetCore.Identity.EntityFrameworkCore NuGet包的一部分。以下是我們?nèi)绾巫龅竭@一點(更新的代碼以粗體顯示):

using Microsoft.AspNetCore.Identity.EntityFrameworkCore; ? // ... existing code public class ApplicationDbContext : IdentityDbContext<ApiUser> ?

? 必需的命名空間

? 新的 IdentityDbContext<TUser> 基類

請注意,新的基類需要一個 TUser 類型的對象,該對象必須是 IdentityUser 類型的類。在此處指定我們的 ApiUser 實體指示由 ASP.NET Core 標識擴展包提供支持的 EF Core 在其上使用其標識功能。

9.2.4 添加和應用新遷移

現(xiàn)在,我們已經(jīng)使應用程序數(shù)據(jù)庫上下文知道了我們的新用戶實體,我們準備添加新的遷移來更新基礎(chǔ) SQL Server 數(shù)據(jù)庫,使用我們在第 4 章中學習的代碼優(yōu)先方法創(chuàng)建 ASP.NET 核心標識所需的數(shù)據(jù)庫表。打開新的命令提示符,導航到 MyBGList 項目的根文件夾,然后鍵入以下內(nèi)容以創(chuàng)建新的遷移:

> dotnet ef migrations add Identity

然后鍵入以下命令以將遷移應用到我們的 MyBGList 數(shù)據(jù)庫:

> dotnet ef database update Identity

如果一切順利,CLI 命令應顯示文本,記錄兩個任務的成功結(jié)果。我們可以通過打開 SQL Server Management StudioSSMS) 來仔細檢查結(jié)果,以查看是否已創(chuàng)建新的 ASP.NET 核心標識表。預期結(jié)果如圖9.3所示。

使用 ASP.NET Core 構(gòu)建 Web API:9 身份驗證和授權(quán)

圖9.3 ASP.NET 核心標識表

根據(jù) ASP.NET 核心標識默認行為,所有標識數(shù)據(jù)庫表都有一個 AspNet 前綴,這通常是一件好事,因為它允許我們輕松地將它們與其他表區(qū)分開來。

管理遷移(以及處理基于遷移的錯誤)

遷移功能是 EF Core 的獨特優(yōu)勢之一,因為它允許開發(fā)人員以增量方式更新數(shù)據(jù)庫架構(gòu),使其與應用程序的數(shù)據(jù)模型保持同步,同時保留數(shù)據(jù)庫中的現(xiàn)有數(shù)據(jù),以及隨時回滾到以前的狀態(tài),就像我們對源代碼管理所做的那樣。但從長遠來看,此功能可能很難維護,特別是如果我們意外刪除了 dotnet-ef 工具生成的增量文件之一。發(fā)生這種情況時,任何使用 CLI 更新現(xiàn)有數(shù)據(jù)庫架構(gòu)的嘗試都可能會返回 SQL 錯誤,例如“表/列/鍵已存在”。避免看到此錯誤消息的唯一方法是保留所有遷移文件。這就是為什么我們遵循在項目內(nèi)部的文件夾中生成它們的良好做法,確保它們與其余代碼一起置于源代碼管理之下。

盡管有這些對策,但在某些邊緣情況下,遷移的增量機制可能會不可挽回地中斷;我們將無法恢復和/或回滾到安全狀態(tài)。每當發(fā)生這種情況時,或者如果我們丟失了遷移文件而無法恢復它,我們能做的最好的事情就是重置所有遷移并創(chuàng)建一個與我們當前數(shù)據(jù)庫架構(gòu)同步的新遷移。這個過程涉及一些手工工作,稱為擠壓,并在 http://mng.bz/Eljl 的Microsoft官方指南中進行了詳細解釋。

如果我們想更改表名,我們可以通過重寫 ApplicationDbContext 的 OnModelCreate 方法中的默認值來實現(xiàn),如下所示(但不要在代碼中執(zhí)行此操作):

modelBuilder.Entity<ApiUser>().ToTable("ApiUsers");modelBuilder.Entity<IdentityRole<string>>().ToTable("ApiRoles");modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("ApiRoleClaims");modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("ApiUserClaims");modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("ApiUserLogins");modelBuilder.Entity<IdentityUserRole<string>>().ToTable("ApiRoles");modelBuilder.Entity<IdentityUserToken<string>>().ToTable("ApiUserTokens");

此代碼會將 AspNet 前綴替換為 Api。但我們不會在代碼示例中執(zhí)行此操作;我們將保留默認前綴。

9.2.5 設(shè)置服務和中間件

現(xiàn)在我們需要在我們的程序.cs文件中設(shè)置和配置一些服務和中間件。我們需要添加以下內(nèi)容:

  • 身份服務 – 執(zhí)行注冊和登錄過程
  • 授權(quán)服務 – 定義頒發(fā)和讀取 JWT 的規(guī)則
  • 身份驗證中間件 – 將 JWT 讀取任務添加到 HTTP 管道

讓我們從標識服務開始。

添加身份服務

以下是我們需要做的:

  1. 將 ASP.NET 核心標識服務添加到服務容器。
  2. 配置用戶密碼的最低安全要求(也稱為密碼強度)。
  3. 添加 ASP.NET 身份驗證中間件。

打開 Program.cs 文件,找到我們將 DbContext 添加到服務容器的部分,并在它下面添加清單 9.1 中的代碼(粗體新行)。

清單 9.1 程序.cs文件:標識服務

using Microsoft.AspNetCore.Identity; ? builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( builder.Configuration.GetConnectionString("DefaultConnection")) ); builder.Services.AddIdentity<ApiUser, IdentityRole>(options => ?{ options.Password.RequireDigit = true; ? options.Password.RequireLowercase = true; ? options.Password.RequireUppercase = true; ? options.Password.RequireNonAlphanumeric = true; ? options.Password.RequiredLength = 12; ?}) .AddEntityFrameworkStores<ApplicationDbContext>();

? 必需的命名空間

? 添加身份服務

? 配置密碼強度要求

如我們所見,我們告訴 ASP.NET 標識僅接受具有以下特征的密碼

  • 至少一個小寫字母
  • 至少一個大寫字母
  • 至少一個數(shù)字字符
  • 至少一個非字母數(shù)字字符
  • 至少 12 個字符

這些安全標準將為我們的用戶提供非數(shù)據(jù)敏感方案的良好級別的身份驗證安全性。下一步是設(shè)置身份驗證服務。

添加身份驗證服務

在我們的方案中,身份驗證服務具有以下用途:

  • 將 JWT 定義為默認身份驗證方法
  • 啟用 JWT 持有者身份驗證方法
  • 設(shè)置 JWT 驗證、頒發(fā)和生存期設(shè)置

下面的清單包含相關(guān)代碼,我們可以將其放在標識服務正下方的程序.cs文件中。

清單 9.2 程序.cs文件:認證服務

using Microsoft.AspNetCore.Authentication.JwtBearer; ?using Microsoft.IdentityModel.Tokens; ? builder.Services.AddAuthentication(options => { ? options.DefaultAuthenticateScheme = options.DefaultChallengeScheme = options.DefaultForbidScheme = options.DefaultScheme = options.DefaultSignInScheme = options.DefaultSignOutScheme = JwtBearerDefaults.AuthenticationScheme; ?}).AddJwtBearer(options => { ? options.TokenValidationParameters = new TokenValidationParameters ? { ValidateIssuer = true, ValidIssuer = builder.Configuration["JWT:Issuer"], ValidateAudience = true, ValidAudience = builder.Configuration["JWT:Audience"], ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey( System.Text.Encoding.UTF8.GetBytes( builder.Configuration["JWT:SigningKey"]) ) };});

? 必需的命名空間

? 添加身份驗證服務

? 設(shè)置默認授權(quán)相關(guān)方案

? 添加 JWT 持有者身份驗證方案

? 配置 JWT 選項和設(shè)置

JWT 持有者選項部分是代碼中最有趣的部分,因為它決定了身份驗證服務應如何驗證令牌。如我們所見,我們要求驗證頒發(fā)者、受眾和頒發(fā)者用于對令牌進行簽名的密鑰 (IssuerSigningKey)。執(zhí)行這些檢查將大大減少惡意第三方頒發(fā)或偽造有效令牌的機會。

請注意,我們沒有直接在代碼中指定這些參數(shù),而是使用了對配置文件的引用。我們現(xiàn)在需要更新這些文件,以便源代碼能夠檢索這些值。

更新 appsettings.json 文件

打開 appsettings.json 文件,并在現(xiàn)有 SeriLog 項的正下方添加以下頂級部分:

"JWT": { "Issuer": "MyBGList", "Audience": "MyBGList", "SigningKey": "MyVeryOwnTestSigningKey123$" }

與往常一樣,如果計劃在可公開訪問的生產(chǎn)環(huán)境中部署 Web API,請務必使用自己的值更改示例值。

提示在 secret.json 文件中移動簽名密鑰將確保更好的安全態(tài)勢。請務必執(zhí)行此操作,除非你正在處理像這樣的示例應用。

現(xiàn)在我們的服務已經(jīng)正確設(shè)置,我們幾乎完成了程序.cs文件?,F(xiàn)在缺少的只是身份驗證中間件。

添加身份驗證中間件

在 Program.cs 文件中,向下滾動到現(xiàn)有行

app.UseAuthorization();

并在其前面添加 ASP.NET Core 身份驗證中間件:

app.UseAuthentication(); ?app.UseAuthorization(); ?

? 新的身份驗證中間件

? 現(xiàn)有授權(quán)中間件

從第2章開始,我們就知道中間件順序很重要,因為中間件按順序影響HTTP請求管道。因此,請確保在 UseAuthorization() 之前調(diào)用 UseAuthentication(),因為我們的應用需要知道使用哪種身份驗證方案和處理程序來授權(quán)請求。現(xiàn)在,我們已經(jīng)設(shè)置并配置了 ASP.NET Core Identity 服務和身份驗證中間件,我們已準備好實現(xiàn)用戶將用于創(chuàng)建其帳戶(注冊)然后對自己進行身份驗證(登錄)的操作方法。

9.2.6 實現(xiàn)帳戶控制器

在本節(jié)中,我們將創(chuàng)建一個新的AccountController,并使用兩種操作方法填充它:注冊(創(chuàng)建新用戶)和登錄(對其進行身份驗證)。這兩種方法都需要一些必需的輸入?yún)?shù)才能執(zhí)行其工作。例如,Register 方法需要想要創(chuàng)建帳戶的用戶的數(shù)據(jù)(用戶名、密碼、電子郵件等),而 Login 方法只需要知道用戶名和密碼。由于帳戶控制器必須處理一些與核心標識相關(guān)的特定 ASP.NET 任務,因此我們將需要以下以前從未使用過的服務:

  • 用戶管理器 – 提供用于管理用戶的 API
  • 登錄管理器 – 提供用于登錄用戶的 API

這兩個服務都是 Microsoft.AspNetCore.Identity 命名空間的一部分。我們將需要第一個用于注冊方法,第二個用于處理登錄。此外,因為我們還需要讀取我們在appsettings.json配置文件中指定的JWT設(shè)置,所以我們也需要IConfiguration接口。與往常一樣,所有這些依賴項都將通過依賴項注入提供。

讓我們從控制器本身中的空樣板開始。在項目的 /Controllers/ 文件夾中創(chuàng)建一個新的 AccountController.cs C# 類文件,并使用以下清單中的代碼填充該文件。

清單 9.3 帳戶控制器樣板

using Microsoft.AspNetCore.Mvc;using Microsoft.EntityFrameworkCore;using MyBGList.DTO;using MyBGList.Models;using System.Linq.Expressions;using System.Linq.Dynamic.Core;using System.ComponentModel.DataAnnotations;using MyBGList.Attributes;using System.Diagnostics;using Microsoft.AspNetCore.Identity; ?using Microsoft.IdentityModel.Tokens; ?using System.IdentityModel.Tokens.Jwt; ?using System.Security.Claims; ? namespace MyBGList.Controllers{ [Route("[controller]/[action]")] ? [ApiController] public class AccountController : ControllerBase { private readonly ApplicationDbContext _context; private readonly ILogger<DomainsController> _logger; private readonly IConfiguration _configuration; private readonly UserManager<ApiUser> _userManager; ? private readonly SignInManager<ApiUser> _signInManager; ? public AccountController( ApplicationDbContext context, ILogger<DomainsController> logger, IConfiguration configuration, UserManager<ApiUser> userManager, ? SignInManager<ApiUser> signInManager) ? { _context = context; _logger = logger; _configuration = configuration; _userManager = userManager; ? _signInManager = signInManager; ? } [HttpPost] [ResponseCache(CacheProfileName = "NoCache")] public async Task<ActionResult> Register() ? { throw new NotImplementedException(); } [HttpPost] [ResponseCache(CacheProfileName = "NoCache")] public async Task<ActionResult> Login() ? { throw new NotImplementedException(); } }}

? ASP.NET 核心身份命名空間

? 路由屬性

? 用戶管理器接口

? 登錄管理器接口

? 注冊方式

? 登錄方式

請注意,我們已經(jīng)使用基于操作的路由規(guī)則(“[控制器]/[操作]”)定義了一個 [Route] 屬性,因為我們必須處理需要區(qū)分的兩個 HTTP POST 方法。由于該規(guī)則,我們的方法將具有以下端點:

/Account/Register/Account/Login

除此之外,我們還為 _userManager、_signInManager 和 _configuration 對象(通過依賴注入)設(shè)置了一個本地實例,并創(chuàng)建了兩個未實現(xiàn)的方法。在以下部分中,我們將從 Register 開始實現(xiàn)這兩種方法(及其 DTO)。

實現(xiàn)寄存器方法

如果我們查看 ASP.NET SQL Server數(shù)據(jù)庫中為我們創(chuàng)建的核心標識的[AspNetUsers]表,我們會看到創(chuàng)建新用戶所需的參數(shù)(圖9.4)。

使用 ASP.NET Core 構(gòu)建 Web API:9 身份驗證和授權(quán)

圖 9.4 AspNetUsers 數(shù)據(jù)庫表

此表用于存儲我們之前創(chuàng)建的 ApiUser 實體的記錄,該實體是 IdentityUser 默認類的擴展。如果我們檢查該實體,我們會看到它對每個表列都有一個公共屬性,這并不奇怪,因為我們首先使用了 EF Core 代碼優(yōu)先方法來創(chuàng)建表。

現(xiàn)在我們知道了我們需要從想要創(chuàng)建新帳戶的用戶那里獲取的數(shù)據(jù),我們可以實現(xiàn) DTO 對象來“傳輸”他們,從而將第 6 章中的課程付諸實踐。在項目的 /DTO/ 文件夾中創(chuàng)建一個新的 RegisterDTO.cs C# 類文件,并用清單 9.4 中所示的代碼填充該文件。為簡單起見,我們將要求注冊用戶向我們發(fā)送三種類型的信息:有效的用戶名、他們想要用于執(zhí)行登錄的密碼以及他們的電子郵件地址。

清單 9.4 注冊DTO類

using System.ComponentModel.DataAnnotations; namespace MyBGList.DTO{ public class RegisterDTO { [Required] public string? UserName { get; set; } [Required] [EmailAddress] public string? Email { get; set; } [Required] public string? Password { get; set; } }}

現(xiàn)在我們有了DTO,我們可以使用它來實現(xiàn)我們的 帳戶控制器 。注冊方法,預期處理以下任務:

  1. 接受寄存器DTO輸入。
  2. 檢查模型狀態(tài)以確保輸入有效。
  3. 如果 ModelState 有效,則創(chuàng)建一個新用戶(記錄結(jié)果),并返回狀態(tài)代碼 201 – 已創(chuàng)建;否則,返回狀態(tài)代碼 400 – 記錄錯誤的錯誤請求。
  4. 如果用戶創(chuàng)建失敗,或者整個過程中出現(xiàn)異常,則返回狀態(tài)代碼 500 – 內(nèi)部服務器錯誤,并返回相關(guān)錯誤消息。

下面的清單顯示了我們?nèi)绾螌崿F(xiàn)這些任務。

清單 9.5 帳戶控制器.注冊方法

[HttpPost][ResponseCache(CacheProfileName = "NoCache")]public async Task<ActionResult> Register(RegisterDTO input){ try { if (ModelState.IsValid) ? { var newUser = new ApiUser(); newUser.UserName = input.UserName; newUser.Email = input.Email; var result = await _userManager.CreateAsync( newUser, input.Password); ? if (result.Succeeded) ? { _logger.LogInformation( "User {userName} ({email}) has been created.", newUser.UserName, newUser.Email); return StatusCode(201, $"User '{newUser.UserName}' has been created."); } else throw new Exception( string.Format("Error: {0}", string.Join(" ", result.Errors.Select(e => e.Description)))); } else { var details = new ValidationProblemDetails(ModelState); details.Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"; details.Status = StatusCodes.Status400BadRequest; return new BadRequestObjectResult(details); } } catch (Exception e) ? { var exceptionDetails = new ProblemDetails(); exceptionDetails.Detail = e.Message; exceptionDetails.Status = StatusCodes.Status500InternalServerError; exceptionDetails.Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1"; return StatusCode( StatusCodes.Status500InternalServerError, exceptionDetails); }}

? 檢查模型狀態(tài)并采取相應措施

? 嘗試創(chuàng)建用戶

? 檢查結(jié)果并采取相應措施

? 捕獲任何異常并返回錯誤

這段代碼應該不難理解。唯一的新東西是使用UserManager服務及其CreateAsync方法,該方法返回IdentityResult類型的對象,其中包含發(fā)生的結(jié)果或錯誤?,F(xiàn)在我們有一個 Register 方法,我們可以通過嘗試創(chuàng)建新用戶來測試它。

創(chuàng)建測試用戶

在調(diào)試模式下啟動項目,并像往常一樣等待 SwaggerUI 起始頁加載。然后,我們應該看到一個新的 POST 帳戶/注冊端點,我們可以擴展它,如圖 9.5 所示。

使用 ASP.NET Core 構(gòu)建 Web API:9 身份驗證和授權(quán)

圖 9.5 SwaggerUI 中的 /帳戶/注冊終結(jié)點

我們可以通過單擊右上角的“試用”按鈕來測試新方法。一旦我們這樣做,我們將能夠使用實際的用戶名、電子郵件和密碼值填充示例 JSON。讓我們使用以下值進行第一次測試:

{ "userName": "TestUser", "email": "TestEmail", "password": "TestPassword"}

請求應返回 HTTP 狀態(tài)代碼 400,并帶有解釋錯誤原因(電子郵件格式無效)的響應正文:

{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "errors": { "Email": [ "The Email field is not a valid e-mail address." ] }}

此響應表示模型狀態(tài)驗證工作正常。目前為止,一切都好?,F(xiàn)在,讓我們修復電子郵件字段并使用以下值執(zhí)行新測試:

{ "userName": "TestUser", "email": "test-user@email.com", "password": "TestPassword"}

現(xiàn)在,請求應返回 HTTP 狀態(tài)代碼 500,并帶有一個響應正文,解釋新的錯誤原因(密碼格式無效):

{ "type": "https://tools.ietf.org/html/rfc7231#section-6.6.1", "status": 500, "detail": "Error: Passwords must have at least one non alphanumeric? character. Passwords must have at least one digit ('0'-'9')."}

該錯誤警告我們密碼不夠強 – 再次確認我們的驗證檢查正在工作。現(xiàn)在我們可以修復最后一個問題,并使用以下值執(zhí)行第三個(理想情況下是最后一個)測試:

{ "userName": "TestUser", "email": "test-user@email.com", "password": "MyVeryOwnTestPassword123$"}

我們應該會收到一條確認消息,指出用戶已創(chuàng)建。

注意隨意將示例中的用戶名和/或密碼替換為您自己的值。但請務必記下它們,尤其是密碼,因為 UserManager.CreateAsync 方法會將其作為不可逆的哈希值存儲在 [AspNetUsers] 中。密碼哈希]列。

現(xiàn)在我們完成了寄存器部分。讓我們繼續(xù)討論登錄方法。

實現(xiàn)登錄方法

我們的任務是創(chuàng)建一個合適的登錄DTO并使用它來實現(xiàn)登錄操作方法。讓我們從 LoginDTO 類開始,它(我們現(xiàn)在應該知道)只需要兩個屬性:用戶名和密碼(請參閱下面的列表)。

清單 9.6 登錄DTO類

using System.ComponentModel.DataAnnotations; namespace MyBGList.DTO{ public class LoginDTO { [Required] [MaxLength(255)] public string? UserName { get; set; } [Required] public string? Password { get; set; } }}

現(xiàn)在我們可以實現(xiàn) AccountController.Login 方法,該方法需要處理以下任務:

  1. 接受登錄DTO輸入。
  2. 檢查模型狀態(tài)以確保輸入有效;否則,返回記錄錯誤的狀態(tài)代碼 400 – 錯誤請求。
  3. 如果用戶存在且密碼匹配,請生成一個新令牌,并將其與狀態(tài)代碼 200 – 確定一起發(fā)送給用戶。
  4. 如果用戶不存在、密碼不匹配和/或在此過程中發(fā)生任何異常,請返回狀態(tài)代碼 401 – 未經(jīng)授權(quán),并返回相關(guān)錯誤消息。

下面的清單包含這些任務的源代碼。

9.7 賬戶控制器的登錄方法

[HttpPost][ResponseCache(CacheProfileName = "NoCache")]public async Task<ActionResult> Login(LoginDTO input){ try { if (ModelState.IsValid) ? { var user = await _userManager.FindByNameAsync(input.UserName); if (user == null || !await _userManager.CheckPasswordAsync( user, input.Password)) throw new Exception("Invalid login attempt."); else { var signingCredentials = new SigningCredentials( ? new SymmetricSecurityKey( System.Text.Encoding.UTF8.GetBytes( _configuration["JWT:SigningKey"])), SecurityAlgorithms.HmacSha256); var claims = new List<Claim>(); ? claims.Add(new Claim( ClaimTypes.Name, user.UserName)); var jwtObject = new JwtSecurityToken( ? issuer: _configuration["JWT:Issuer"], audience: _configuration["JWT:Audience"], claims: claims, expires: DateTime.Now.AddSeconds(300), signingCredentials: signingCredentials); var jwtString = new JwtSecurityTokenHandler() ? .WriteToken(jwtObject); return StatusCode( ? StatusCodes.Status200OK, jwtString); } } else { var details = new ValidationProblemDetails(ModelState); details.Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"; details.Status = StatusCodes.Status400BadRequest; return new BadRequestObjectResult(details); } } catch (Exception e) ? { var exceptionDetails = new ProblemDetails(); exceptionDetails.Detail = e.Message; exceptionDetails.Status = StatusCodes.Status401Unauthorized; exceptionDetails.Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1"; return StatusCode( StatusCodes.Status401Unauthorized, exceptionDetails); }}

? 檢查模型狀態(tài)并采取相應措施

? 生成簽名憑據(jù)

? 設(shè)置用戶聲明

? 實例化 JWT 對象實例

? 生成 JWT 加密字符串

? 將 JWT 返回給調(diào)用方

? 捕獲任何異常并返回錯誤

同樣,此代碼應該易于理解 – 除了 JWT 生成部分,它值得一些額外的解釋。該部分可以分為四個部分,我用空格分隔,每個部分設(shè)置一個變量,該變量在 JWT 創(chuàng)建過程中起著獨特的作用:

  • 簽名憑據(jù) – 此變量存儲使用 HMAC SHA-256 加密算法加密的 JWT 簽名。請注意,簽名密鑰是從配置設(shè)置中檢索的,與前面的程序.cs文件中的授權(quán)服務一樣。此方法可確保寫入和讀取過程將使用相同的值,這意味著簽名密鑰將匹配。
  • 聲明 – 此變量存儲我們要為其生成 JWT 的用戶的聲明列表。授權(quán)過程將使用這些聲明來檢查是否允許用戶訪問每個請求的資源(稍后會詳細介紹)。請注意,現(xiàn)在,我們正在設(shè)置一個與用戶的 UserName 屬性對應的聲明。我們很快就會添加更多聲明。
  • jwtObject – 此變量通過將簽名憑據(jù)、聲明列表、配置文件檢索的頒發(fā)者和受眾值以及合適的過期時間(300 秒)放在一起來存儲 JWT 本身的實例(作為 C# 對象)。
  • jwtString – 此變量存儲 JWT 的加密字符串表示形式。此值是我們需要發(fā)送回客戶端的值,以便他們可以在后續(xù)請求的授權(quán)標頭中設(shè)置它。

注意我們正在使用其他幾個UserManager方法:FindByNameAsync和CheckPasswordAsync。因為他們的名字是不言自明的,所以理解他們做什么應該不難。

使用此方法,我們的帳戶控制器已準備就緒,我們實現(xiàn)的身份驗證部分也已準備就緒?,F(xiàn)在我們需要測試它。

對測試用戶進行身份驗證

要測試帳戶控制器的登錄方法,我們可以使用通過注冊方法創(chuàng)建的測試用戶。在調(diào)試模式下啟動項目,訪問 SwaggerUI 主儀表板,然后選擇新的 POST 帳戶/登錄端點(圖 9.6)。

使用 ASP.NET Core 構(gòu)建 Web API:9 身份驗證和授權(quán)

圖 9.6 SwaggerUI 中的 /帳戶/登錄端點

單擊右上角的試用,并使用我們創(chuàng)建的測試用戶的用戶名和密碼值填充示例 JSON:

{ "userName": "TestUser", "password": " MyVeryOwnTestPassword123$"}

如果我們正確執(zhí)行了所有操作,我們應該收到狀態(tài)代碼 200 – OK 響應,響應正文中帶有 JWT(圖 9.7)。

使用 ASP.NET Core 構(gòu)建 Web API:9 身份驗證和授權(quán)

圖 9.7 /帳戶/使用 JWT 的登錄響應

現(xiàn)在,我們的 Web API 配備了有效的身份驗證機制,包括通過 ASP.NET 核心身份處理的注冊和登錄過程。在下一節(jié)中,我們將基于它定義一些授權(quán)規(guī)則。

9.3 授權(quán)設(shè)置

在本部分中,我們將使用由帳戶控制器的登錄方法生成的 JWT 將我們的某些 API 終結(jié)點限制為授權(quán)用戶。為了獲得這個結(jié)果,我們需要注意兩個不同的方面:

  • 客戶端 – 添加包含 JWT 的授權(quán) HTTP 標頭,以使用我們選擇的測試客戶端 (SwaggerUI) 正確模擬某些“授權(quán)”請求。
  • 服務器端 – 設(shè)置一些授權(quán)規(guī)則,使某些現(xiàn)有控制器(和最小 API)的操作方法僅供具有具有所需聲明的有效 JWT 的調(diào)用方訪問。

9.3.1 添加授權(quán) HTTP 標頭

由于我們的帳戶控制器的登錄方法以純文本形式返回 JWT,因此我們可以做的最有效的事情是更新我們現(xiàn)有的 Swashbuckler SwaggerUI 配置,使其接受任意字符串(如果存在),該字符串將在執(zhí)行請求之前放入授權(quán) HTTP 標頭中。與往常一樣,所需的更新將在程序.cs文件中執(zhí)行。

從客戶端處理授權(quán)標頭

我們將要實現(xiàn)的技術(shù)旨在模擬實際的 REST 客戶端在執(zhí)行請求時會執(zhí)行的操作。JWT 不應手動處理。大多數(shù)客戶端 JavaScript 框架(如 Angular 和 React)提供(或允許使用)HTTP 攔截器,這些攔截器可用于在調(diào)度之前將任意標頭(例如帶有先前獲取的令牌的授權(quán)標頭)附加到所有請求。

有關(guān) HTTP 攔截器的其他信息,請查看以下 URL:

  • 角度(內(nèi)置接口):https://angular.io/api/common/http/HttpInterceptor
  • Axios(用于 React 和其他框架):https://axios-http.com/docs/interceptors

我們需要添加新的安全定義,以告訴 Swagger 我們希望 API 的保護類型,以及全局強制實施的新安全要求。以下清單顯示了如何操作。

示例 9.8 程序.cs文件:Swagger的持有者令牌設(shè)置

using Microsoft.OpenApi.Models; ? // ... existing code builder.Services.AddSwaggerGen(options => { options.ParameterFilter<SortColumnFilter>(); options.ParameterFilter<SortOrderFilter>(); options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme ? { In = ParameterLocation.Header, Description = "Please enter token", Name = "Authorization", Type = SecuritySchemeType.Http, BearerFormat = "JWT", Scheme = "bearer" }); options.AddSecurityRequirement(new OpenApiSecurityRequirement ? { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type=ReferenceType.SecurityScheme, Id="Bearer" } }, Array.Empty<string>() } });});

? 必需的命名空間

? 新的招搖安全定義

? 新的招搖安全要求

由于此更新,帶有掛鎖圖標的新授權(quán)按鈕將出現(xiàn)在 SwaggerUI 的右上角(圖 9.8)。如果我們單擊它,將出現(xiàn)一個彈出窗口,讓我們有機會插入要在授權(quán) HTTP 標頭中使用的持有者令牌。

使用 ASP.NET Core 構(gòu)建 Web API:9 身份驗證和授權(quán)

圖9.8 SwaggerUI授權(quán)按鈕和彈窗

這正是我們需要將 JWT 添加到我們的請求中的內(nèi)容?,F(xiàn)在,作業(yè)的客戶端部分已經(jīng)完成,我們可以切換到服務器端。

9.3.2 設(shè)置 [授權(quán)] 屬性

我們必須選擇哪些 API 端點應該對所有人可用(因為它們已經(jīng)可用),以及限制、限制或阻止哪些端點。典型的方法是允許對只讀終結(jié)點進行公共/匿名訪問,這些終結(jié)點不泄露保留數(shù)據(jù),并將其他所有內(nèi)容限制為經(jīng)過身份驗證(和授權(quán))的用戶。讓我們使用通用邏輯,根據(jù)產(chǎn)品所有者的顯式請求設(shè)計給定的實現(xiàn)方案。假設(shè)我們希望保持對所有操作方法的無限制訪問,但以下方法除外:

  • 棋盤游戲控制器 – 發(fā)布、刪除
  • 域控制器 – 發(fā)布、刪除
  • 機械控制器 – 發(fā)布、刪除
  • 種子控制器 – 放置

我們可以很容易地看到,所有這些方法都是為了將永久更改應用于我們的數(shù)據(jù)庫,因此將它們放在授權(quán)規(guī)則后面很有意義。我們絕對不希望某些匿名用戶刪除、更新或以其他方式更改我們的棋盤游戲數(shù)據(jù)!

為了將我們的計劃付諸實踐,我們可以用 [Authorize] 屬性來修飾這些方法,該屬性是 Microsoft.AspNetCore.Authorization 命名空間的一部分。此屬性可應用于控制器、操作方法和最小 API 方法,以根據(jù)身份驗證方案、策略和/或角色設(shè)置特定的授權(quán)規(guī)則。可以使用屬性的參數(shù)配置這些規(guī)則(我們將在稍后看到)。在沒有參數(shù)的情況下使用時,[Authorize] 屬性以其最基本的形式將限制對經(jīng)過身份驗證的用戶的訪問,無論其權(quán)限如何。

由于我們尚未為用戶定義任何策略或角色,因此可以使用屬性的無參數(shù)行為開始實現(xiàn)過程。打開以下控制器:BoardGamesController、DomainsController、MechanicsController和SeedController。然后將 [Authorize] 屬性添加到其發(fā)布、刪除和放置方法中,如下所示:

using Microsoft.AspNetCore.Authorization; ? // ... existing code [Authorize] ? [HttpPost(Name = "UpdateBoardGame")] [ResponseCache(CacheProfileName = "NoCache")] public async Task<RestDTO<BoardGame?>> Post(BoardGameDTO model)

? 必需的命名空間

? 授權(quán)屬性

現(xiàn)在,我們所有可能更改數(shù)據(jù)的操作方法將只有經(jīng)過身份驗證的用戶才能訪問。

選擇默認訪問行為

請務必了解,通過將 [Authorize] 屬性應用于某些特定操作方法,我們將為未經(jīng)授權(quán)的用戶隱式設(shè)置默認允許、選擇阻止邏輯。換句話說,我們的意思是,除了那些受 [Authorize] 屬性限制的操作方法之外,所有操作方法都允許匿名訪問。我們可以將此邏輯反轉(zhuǎn)為默認阻止,選擇允許,方法是將 [Authorize] 屬性設(shè)置為整個控制器,然后有選擇地將 [AllowAnonymous] 屬性用于我們希望每個人都可以訪問的操作方法。

這兩種行為都是可行的,具體取決于特定的用例。一般而言,限制性更強的方法(默認阻止,選擇允許)被認為更安全,不易發(fā)生人為(開發(fā)人員)錯誤。通常,忘記 [Authorize] 屬性比忘記 [AllowAnonymous] 屬性更糟糕,因為它很容易導致數(shù)據(jù)泄露。

在我們的示例方案中,保護單個操作方法可能是可以接受的,至少對于那些具有混合訪問行為(匿名和受限操作方法)的控制器。例外情況是種子控制器,它旨在僅托管受限制的操作方法。在這種情況下,在控制器級別設(shè)置 [Authorize] 屬性會更合適。讓我們在繼續(xù)之前這樣做。打開 SeedController.cs 文件,并將 [Authorize] 屬性從操作方法移動到控制器:

[Authorize][Route("[controller]")][ApiController]public class SeedController : ControllerBase

由于此更新,我們將添加到此控制器的所有操作方法將自動限制為授權(quán)用戶。我們不必記住做任何其他事情。

啟用最小 API 授權(quán)

在進入測試階段之前,我們應該看看如何在最小 API 中使用 [Authorize] 屬性,而無需額外的努力。讓我們添加一個最小 API 方法來處理新的 /auth/test/1 終結(jié)點,該終結(jié)點僅供授權(quán)用戶訪問。下面的清單包含源代碼。

示例 9.9 程序.cs文件:/auth/test/1 最小 API 端點

using Microsoft.AspNetCore.Authorization; // ... existing code app.MapGet("/auth/test/1", [Authorize] [EnableCors("AnyOrigin")] [ResponseCache(NoStore = true)] () => { return Results.Ok("You are authorized!"); });

現(xiàn)在,我們終于準備好測試到目前為止所做的工作了。

9.3.3 測試授權(quán)流程

在調(diào)試模式下啟動項目,然后訪問 SwaggerUI 儀表板。與往常一樣,此客戶端是我們將用來執(zhí)行測試的客戶端。

我們應該做的第一件事是檢查授權(quán)限制是否正常工作。我們添加的 /auth/test/1 最小 API 端點是該任務的完美候選項,因為它可以通過不會影響我們數(shù)據(jù)的簡單 GET 請求來調(diào)用。我們將使用 SwaggerUI 調(diào)用該終結(jié)點,并確保它返回狀態(tài)代碼 401 – 未經(jīng)授權(quán)的響應,如圖 9.9 所示。

使用 ASP.NET Core 構(gòu)建 Web API:9 身份驗證和授權(quán)

圖 9.9 /auth/test/1 端點返回狀態(tài)代碼 401 – 未授權(quán)

目前為止,一切都好。對于具有 [Authorize] 屬性的任何方法,預計會出現(xiàn)未經(jīng)授權(quán)的響應,因為我們尚未通過身份驗證。讓我們填補這個空白,再試一次。執(zhí)行以下步驟:

  1. 使用測試用戶的用戶名和密碼值調(diào)用帳戶/登錄終結(jié)點,就像我們之前測試它時所做的那樣,以接收有效的 JWT。
  2. 通過選擇它并按 Ctrl C 將 JWT 復制到剪貼板。
  3. 單擊我們添加的授權(quán)按鈕以顯示彈出窗口。
  4. 將 JWT 粘貼到彈出窗口的輸入文本框中,然后單擊授權(quán)以針對下一個請求進行設(shè)置。

現(xiàn)在我們可以調(diào)用 /auth/test/1 端點,看看持有者令牌是否允許我們執(zhí)行此請求。如果一切順利(并且我們在令牌過期之前的 300 秒內(nèi)執(zhí)行測試),我們應該會在響應正文中看到 200 – OK 狀態(tài)代碼和“您已 獲得授權(quán)!”消息,如圖 9.10 所示。

使用 ASP.NET Core 構(gòu)建 Web API:9 身份驗證和授權(quán)

圖 9.10 /auth/test/1 端點返回狀態(tài)代碼 200 – 正常

此結(jié)果表明我們基于 JWT 的身份驗證和授權(quán)流正在工作;我們已成功將某些方法限制為授權(quán)用戶。但是,我們的授權(quán)規(guī)則仍然是基本的。我們只能區(qū)分匿名用戶和經(jīng)過身份驗證的用戶,考慮到后者無需進一步檢查即可獲得授權(quán)。理想情況下,我們應該為我們的 Web API 提供一個更精細的訪問控制系統(tǒng),允許我們設(shè)置其他授權(quán)行為,例如僅授權(quán)某些用戶執(zhí)行某些操作。在下一部分中,我們將通過實現(xiàn)基于角色的聲明機制來實現(xiàn)這一點。

9.4 基于角色的訪問控制

假設(shè)我們要創(chuàng)建不同的 ACL 來支持以下經(jīng)過身份驗證的用戶類型:

  • 基本用戶 – 應允許他們訪問只讀終端節(jié)點,而不能訪問其他任何內(nèi)容,例如匿名(未注冊)用戶。
  • 審閱人 – 他們應有權(quán)訪問只讀端點和更新端點,但不能刪除任何內(nèi)容或為數(shù)據(jù)庫設(shè)定種子。
  • 管理員 – 他們應該能夠執(zhí)行任何操作(讀取、更新、刪除和播種)。

現(xiàn)在的情況是,所有經(jīng)過身份驗證的用戶都被視為管理員:他們可以執(zhí)行任何操作,因為 [Authorize] 屬性僅檢查該基本狀態(tài)。要改變這種行為,我們需要找到一種方法將這些用戶組織到不同的組中,并為每個組分配特定的權(quán)限。在 ASP.NET Core中,我們可以通過使用角色來實現(xiàn)此結(jié)果。

基于角色的訪問控制RBAC) 是一種內(nèi)置的授權(quán)策略,它提供了一種為不同用戶分配不同權(quán)限的便捷方法。每個角色的行為都像一個組,因此我們可以向其添加用戶并為其設(shè)置特定的授權(quán)規(guī)則。定義規(guī)則后,它們將應用于具有該特定角色的所有用戶。

實施 RBAC 策略是處理任務的好方法,因為它允許我們對用戶進行分類。簡而言之,這是我們需要做的:

  • 注冊其他用戶。我們至少需要其中的兩個:TestModerator 和 TestAdministrator,每個都代表我們想要支持的用戶類型。
  • 創(chuàng)建一組預定義的角色。根據(jù)我們的要求,我們需要其中兩個:版主和管理員。我們不需要為基本用戶添加角色,因為他們應具有與匿名用戶相同的權(quán)限,并且無參數(shù) [Authorize] 屬性已經(jīng)處理了這些權(quán)限。
  • 將用戶添加到角色。具體來說,我們需要將測試管理員用戶分配給審閱人角色,將測試管理員用戶分配給管理員角色。
  • 將基于角色的聲明添加到 JWT。由于 JWT 包含經(jīng)過身份驗證的用戶聲明的集合,因此我們需要將用戶的角色放在這些聲明中,以便 ASP.NET Core 授權(quán)中間件能夠確認這些聲明并采取相應的操作。
  • 設(shè)置基于角色的授權(quán)規(guī)則。我們可以通過更新要限制為版主和管理員的操作方法中的 [Authorize] 屬性來執(zhí)行此任務,以便他們要求 JWT 中存在相應的與角色相關(guān)的聲明。

9.4.1 注冊新用戶

要做的第一件事應該很容易完成,因為我們在通過創(chuàng)建 TestUser 帳戶測試帳戶/注冊終結(jié)點時就這樣做了。我們必須執(zhí)行相同的端點兩次才能再添加兩個用戶。以下是我們可用于創(chuàng)建測試審查器帳戶的 JSON 值:

{ "userName": "TestModerator", "email": "test-moderator@email.com", "password": "MyVeryOwnTestPassword123$"}

以下是測試管理員帳戶的值:

{ "userName": "TestAdministrator", "email": "test-administrator@email.com", "password": "MyVeryOwnTestPassword123$"}

注意與往常一樣,請隨時更改用戶名和/或密碼。

用戶就是這樣。讓我們繼續(xù)處理角色。

9.4.2 創(chuàng)建新角色

創(chuàng)建角色的最方便方法是使用 RoleManager API,它是 Microsoft.AspNetCore.Identity 命名空間的一部分。我們將使用其 CreateAsync 方法,該方法接受 IdentityRole 對象作為參數(shù),并使用它來在持久性存儲(在我們的方案中為 [AspNetRoles] 數(shù)據(jù)庫表)中創(chuàng)建新記錄,為其分配唯一 ID。以下是我們?nèi)绾螌崿F(xiàn)它:

await _roleManager.CreateAsync(new IdentityRole("RoleName"));

如我們所見,IdentityRole 對象的構(gòu)造函數(shù)接受表示角色名稱的字符串類型的值。創(chuàng)建角色后,我們需要在代碼中使用此名稱來引用它。因此,將這些名稱定義為常量可能是一個好主意。

添加角色名稱常量

若要將這些名稱定義為常量,請在 /Constants/ 文件夾中創(chuàng)建一個新的 RoleNames.cs 文件,并使用以下清單中的代碼填充該文件。

清單 9.10 /常量/角色名稱.cs文件

namespace MyBGList.Constants{ public static class RoleNames { public const string Moderator = "Moderator"; public const string Administrator = "Administrator"; }}

這些常量將允許我們每次都使用強類型方法而不是文字字符串來引用我們的角色,從而防止人為錯誤?,F(xiàn)在,我們可以編寫代碼來創(chuàng)建這些角色。因為我們談論的是一個可能只執(zhí)行一次的數(shù)據(jù)庫種子任務,所以最好的地方是放在我們的 SeedController 中。但是使用現(xiàn)有的 Put 方法(我們在第 5 章中實現(xiàn)了該方法,將棋盤游戲數(shù)據(jù)插入數(shù)據(jù)庫)將是一種不好的做法,因為它會破壞單一責任原則。相反,我們應該重構(gòu) SeedController 為我們希望它處理的兩個任務創(chuàng)建不同的端點(和操作方法)。我們將重命名現(xiàn)有終結(jié)點 /Seed/BoardGameData,并為新的種子任務創(chuàng)建新的 /Seed/ AuthData。

重構(gòu)種子控制器

若要重構(gòu)種子控制器,請打開 /Controllers/SeedController.cs 文件,然后修改現(xiàn)有代碼,如以下清單所示(更新的行以粗體顯示)。

清單 9.11 /控制器/種子控制器.cs 文件:類重構(gòu)

using Microsoft.AspNetCore.Authorization; ?using Microsoft.AspNetCore.Identity; ? // ... existing codenamespace MyBGList.Controllers{ [Authorize] [Route("[controller]/[action]")] ? [ApiController] public class SeedController : ControllerBase { // ... existing code private readonly RoleManager<IdentityRole> _roleManager; ? private readonly UserManager<ApiUser> _userManager; ? public SeedController( ApplicationDbContext context, IWebHostEnvironment env, ILogger<SeedController> logger, RoleManager<IdentityRole> roleManager, ? UserManager<ApiUser> userManager) ? { _context = context; _env = env; _logger = logger; _roleManager = roleManager; ? _userManager = userManager; ? } [HttpPut] ? [ResponseCache(CacheProfileName = "NoCache")] public async Task<IActionResult> BoardGameData() ? { // ... existing code } [HttpPost] [ResponseCache(NoStore = true)] public async Task<IActionResult> AuthData() ? { throw new NotImplementedException(); } }}

? 必需的命名空間

? 新的基于屬性的路由行為

? 角色管理器接口

? 用戶管理器接口

? 現(xiàn)有看跌期權(quán)操作方法更名為棋盤游戲數(shù)據(jù)

? 新的身份驗證數(shù)據(jù)操作方法

此代碼應該易于理解。我們更改了控制器的路由規(guī)則,使端點與操作名稱匹配;然后,我們注入了創(chuàng)建和分配角色所需的角色管理器和用戶管理器 API。最后,我們重命名了現(xiàn)有的 Put 操作方法 BoardGameData,并添加了一個新的 AuthData 操作方法來處理角色創(chuàng)建任務。

請注意,我們沒有實現(xiàn)新方法,而是專注于 SeedController 的重構(gòu)部分?,F(xiàn)在我們可以繼續(xù)實現(xiàn) AuthData 操作方法,將“未實現(xiàn)”代碼替換為以下列表中的代碼。

示例 9.12 /Controllers/SeedController.cs 文件: AuthData 方法

[HttpPost][ResponseCache(NoStore = true)]public async Task<IActionResult> AuthData(){ int rolesCreated = 0; int usersAddedToRoles = 0; if (!await _roleManager.RoleExistsAsync(RoleNames.Moderator)) { await _roleManager.CreateAsync( new IdentityRole(RoleNames.Moderator)); ? rolesCreated ; } if (!await _roleManager.RoleExistsAsync(RoleNames.Administrator)) { await _roleManager.CreateAsync( new IdentityRole(RoleNames.Administrator)); ? rolesCreated ; } var testModerator = await _userManager .FindByNameAsync("TestModerator"); if (testModerator != null && !await _userManager.IsInRoleAsync( testModerator, RoleNames.Moderator)) { await _userManager.AddToRoleAsync(testModerator,? RoleNames.Moderator); ? usersAddedToRoles ; } var testAdministrator = await _userManager .FindByNameAsync("TestAdministrator"); if (testAdministrator != null && !await _userManager.IsInRoleAsync( testAdministrator, RoleNames.Administrator)) { await _userManager.AddToRoleAsync( testAdministrator, RoleNames.Moderator); ? await _userManager.AddToRoleAsync( testAdministrator, RoleNames.Administrator); ? usersAddedToRoles ; } return new JsonResult(new { RolesCreated = rolesCreated, UsersAddedToRoles = usersAddedToRoles });}

? 創(chuàng)建角色

? 將用戶添加到角色

正如我們所看到的,我們包括了一些檢查以確保

  • 僅當角色尚不存在時,才會創(chuàng)建角色。
  • 僅當用戶存在且尚未加入時,才會將用戶添加到角色中。

如果多次調(diào)用操作方法,這些控件將防止代碼引發(fā)錯誤。

提示Test管理員用戶已添加到多個角色:審閱者和管理員。這對于我們的任務來說完全沒問題,因為我們希望管理員擁有與版主相同的權(quán)限。

9.4.3 為用戶分配角色

由于我們已經(jīng)創(chuàng)建了 TestAdministratorator 和 TestAdministrator 用戶,因此將他們分配給新角色將是一項簡單的任務。我們需要在調(diào)試模式下啟動我們的項目,訪問 SwaggerUI,并執(zhí)行 /Seed/AuthData 端點。

由于我們之前將 [Authorize] 屬性設(shè)置為整個 SeedController,但是,如果我們嘗試在沒有有效 JWT 的情況下調(diào)用該終結(jié)點,我們將收到 401 – 未授權(quán)狀態(tài)代碼。為了避免這種結(jié)果,我們有兩個選擇:

  • 使用 /Account/Login 端點對自己進行身份驗證(任何用戶都可以做到這一點),然后在 SwaggerUI 的授權(quán)彈出窗口中設(shè)置生成的 JWT。
  • 在執(zhí)行項目并調(diào)用 /Seed/AuthData 終結(jié)點之前,請注釋掉 [Authorize] 屬性,然后取消注釋它。

無論我們采用哪種路線,假設(shè)一切順利,我們都應該收到帶有以下 JSON 響應正文的 200 – OK 狀態(tài)代碼:

{ "rolesCreated": 2, "usersAddedToRoles": 2}

我們已經(jīng)成功創(chuàng)建了我們的角色,并將我們的用戶添加到其中。現(xiàn)在,我們需要確保將這些角色放在持有者令牌中,以便授權(quán)中間件可以檢查它們的存在并采取相應的行動。

9.4.4 向 JWT 添加基于角色的聲明

若要將角色添加到持有者令牌,我們需要打開 /Controllers/AccountController.cs 文件,然后更新 Login 操作方法,為成功進行身份驗證的用戶所屬的每個角色添加一個聲明。以下代碼片段演示了如何(粗體換行):

// ... existing code var claims = new List<Claim>();claims.Add(new Claim( ClaimTypes.Name, user.UserName));claims.AddRange( (await _userManager.GetRolesAsync(user)) .Select(r => new Claim(ClaimTypes.Role, r))); // ... existing code

如我們所見,經(jīng)過身份驗證的用戶的 JWT 現(xiàn)在包含零個、一個或多個基于角色的聲明,具體取決于用戶所屬的角色數(shù)量。這些聲明將用于是否授權(quán)該用戶的請求,具體取決于我們?nèi)绾螢槊總€控制器和/或操作方法配置授權(quán)規(guī)則。

9.4.5 設(shè)置基于角色的身份驗證規(guī)則

現(xiàn)在,我們已確保經(jīng)過身份驗證的用戶的 JWT 將包含其每個角色(如果有)的聲明,我們可以更新現(xiàn)有的 [Authorize] 屬性以考慮角色。讓我們從主持人角色開始。打開 BoardGamesController、DomainsController 和 MechanicsController 文件,并按以下方式更改應用于其更新方法的現(xiàn)有 [Authorize] 屬性:

[Authorize(Roles = RoleNames.Moderator)]

此代碼將更改屬性的行為?,F(xiàn)在,該屬性將僅授權(quán)具有審閱人角色的用戶,而不是授權(quán)所有經(jīng)過身份驗證的用戶,而不管其角色如何。由于我們要添加對 RoleNames 靜態(tài)類的引用,因此還需要在每個控制器文件的頂部添加以下命名空間引用:

using MyBGList.Constants;

讓我們對管理員角色重復此過程。通過以下方式更改應用于控制器刪除方法的現(xiàn)有 [Authorize] 屬性:

[Authorize(Roles = RoleNames.Administrator)]

然后打開 SeedController,并使用前面的屬性更新其 [Authorize] 屬性(我們將其應用于控制器本身),因為將該控制器限制為管理員是我們分配的一部分。

9.4.6 測試 RBAC 流

為了執(zhí)行無害測試,我們可以使用要檢查的授權(quán)規(guī)則創(chuàng)建兩個新的最小 API 測試方法,而不是使用現(xiàn)有的端點,這會對我們的數(shù)據(jù)進行一些永久性更改。打開 Program.cs 文件,并在處理我們之前添加的 /auth/test/2 終結(jié)點的方法的正下方添加以下代碼:

app.MapGet("/auth/test/2", [Authorize(Roles = RoleNames.Moderator)] [EnableCors("AnyOrigin")] [ResponseCache(NoStore = true)] () => { return Results.Ok("You are authorized!"); }); app.MapGet("/auth/test/3", [Authorize(Roles = RoleNames.Administrator)] [EnableCors("AnyOrigin")] [ResponseCache(NoStore = true)] () => { return Results.Ok("You are authorized!"); });

現(xiàn)在,我們可以執(zhí)行以下測試周期,這與我們?yōu)榈谝粋€授權(quán)流設(shè)計的測試周期非常相似。在 SwaggerUI 主儀表板中,執(zhí)行以下步驟:

  1. 使用 TestUser 的用戶名和密碼調(diào)用帳戶/登錄端點以接收有效的 JWT。
  2. 此用戶不屬于任何角色。
  3. 將 JWT 復制到剪貼板,單擊 SwaggerUI 的授權(quán)按鈕,將其值復制到彈窗中的輸入文本框中,然后單擊授權(quán)按鈕關(guān)閉彈出窗口。
  4. 調(diào)用 /auth/test/2 和 /auth/test/3 終結(jié)點。
  5. 如果一切按預期工作,我們應該得到一個 401 – 未經(jīng)授權(quán)的狀態(tài)代碼,因為這些端點僅限于版主和管理員,而 TestUser 不是其中之一,因為它沒有相應的角色。
  6. 對測試審查器帳戶重復步驟 1、2 和 3。
  7. 這一次,我們應該收到 /auth/test/200 終結(jié)點的 2 – OK 狀態(tài)代碼和 /auth/test/401 終結(jié)點的 3 – 未授權(quán)狀態(tài)代碼。前者僅限于版主(我們是),后者僅適用于管理員(我們不是)。
  8. 對 TestAdministrator 帳戶重復步驟 1、2 和 3。
  9. 這一次,我們應該為兩個終結(jié)點獲取 200 – OK 狀態(tài)代碼,因為該帳戶屬于審閱者和管理員角色。

9.4.7 使用其他授權(quán)方法

正如我們在處理它時所看到的,我們實現(xiàn)的 RBAC 方法依賴于角色類型聲明 (ClaimTypes.Role) 來執(zhí)行其授權(quán)檢查。如果 JWT 令牌包含此類聲明,并且聲明的內(nèi)容與 [Authorize] 屬性的要求匹配,則用戶已獲得授權(quán)。

但是,可以分配和檢查許多其他聲明類型,以確定用戶是否獲得授權(quán)。我們只能授權(quán)擁有手機號碼的用戶,例如,通過使用 ClaimTypes.MobilePhone,我們可以執(zhí)行這樣的檢查,而不是用戶給定的角色,或者除了用戶給定的角色之外。

基于聲明的訪問控制

此方法稱為基于聲明的訪問控制CBAC),包括 RBAC 提供的相同功能以及更多功能,因為它可用于同時檢查任何聲明(或聲明集)。

注意我們可以說,RBAC 只不過是基于 ClaimTypes.Role 的單個特定聲明的 CBAC 的高級抽象。

與 RBAC 不同,RBAC 由于 [Authorize] 屬性的 Role 屬性而可以輕松快速地實現(xiàn),聲明要求是基于策略的,因此必須通過在 Program.cs 文件中定義和注冊策略來顯式聲明它們。下面介紹了如何添加“主持人使用移動電話”策略,該策略將檢查主持人角色和移動電話號碼是否存在:

builder.Services.AddAuthorization(options => { options.AddPolicy("ModeratorWithMobilePhone", policy => policy .RequireClaim(ClaimTypes.Role, RoleNames.Moderator) ? .RequireClaim(ClaimTypes.MobilePhone)); ?});

? 檢查具有給定值的索賠

? 僅檢查聲明是否存在

注意此技術(shù)與我們在第 3 章中用于注冊 CORS 策略的技術(shù)大致相同。

將上述代碼片段粘貼到 builder.service AddAuthentication 行下方的程序.cs文件中,以配置身份驗證服務。然后我們可以通過以下方式將策略設(shè)置為 [Authorize] 屬性的參數(shù):

[Authorize(Policy = "ModeratorWithMobilePhone")]

此策略需要對現(xiàn)有代碼的以下部分進行一些修改:

  • RegisterDTO 類,允許注冊用戶添加其手機號碼
  • 帳戶控制器的注冊操作方法,用于將移動電話值保存在數(shù)據(jù)庫中(如果存在)
  • 帳戶控制器的登錄操作方法,用于有條件地將 ClaimTypes.MobilePhone 的聲明添加到包含用戶移動電話號碼(如果存在)的 JWT 令牌

我不打算在本書中使用這種方法。我簡要展示它只是因為它對于實現(xiàn)某些特定的授權(quán)要求很有用。

基于策略的訪問控制

盡管CBAC比RBAC更通用,但它允許我們僅檢查是否存在多個聲明中的一個和/或其特定值。如果聲明值不是單個值,或者我們需要更復雜的檢查,該怎么辦?我們可能希望定義一個策略,以僅授權(quán)年齡等于或大于 18 歲的用戶,并且我們無法通過檢查是否存在 ClaimTypes.DateOfBirth 聲明或特定出生日期值來執(zhí)行此操作。

每當我們需要執(zhí)行此類檢查時,我們都可以使用基于策略的訪問控制PBAC) 方法,這是 Microsoft.AspNetCore.Authorization 命名空間提供的最復雜和最通用的授權(quán)方法。此技術(shù)類似于 CBAC,因為它還需要聲明性方法,即在 Program.cs 文件中聲明策略。但是,它不是僅僅檢查一個或多個聲明是否存在(以及可選的值),而是使用由一個或多個需求(IAuthorizationRequire)和需求處理程序(IAuthorizationHandler)組成的更通用的接口。

注意此接口也由 CBAC 的 RequireClaim 方法在后臺使用。我們可以說,RBAC 和 CBAC 都是基于預配置策略的 PBAC 的簡化實現(xiàn)。

我不打算在本書中使用 PBAC,因為它需要實現(xiàn)一些示例需求和需求處理程序類。但我將簡要介紹 RequireAssertion 方法,這是一種使用匿名函數(shù)配置和構(gòu)建基于策略的授權(quán)檢查的便捷方法。以下是我們?nèi)绾问褂么朔椒ǘx“等于或大于 18”策略的方法:

options.AddPolicy("MinAge18", policy => policy .RequireAssertion(ctx => ctx.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth) && DateTime.ParseExact( "yyyyMMdd", ctx.User.Claims.First(c => c.Type == ClaimTypes.DateOfBirth).Value, System.Globalization.CultureInfo.InvariantCulture) >= DateTime.Now.AddYears(-18)));

添加的值是由 RequireAssertion 方法公開的 AuthorizationHandlerContext 對象,其中包含對表示當前用戶的 ClaimsPrincipal 的引用。ClaimsPrincipal 類不僅可用于檢查任何聲明的存在和/或值,還可用于使用、轉(zhuǎn)換和/或轉(zhuǎn)換這些值以滿足我們的所有需求。同樣,此策略可用作某些假設(shè) [Authorize] 屬性的參數(shù),以通過以下方式將某些控制器、操作方法和/或最小 API 方法限制為 18 歲以上的用戶:

[Authorize(Policy = "MinAge18")]

此策略還需要對我們現(xiàn)有的代碼進行大量重構(gòu),因為我們目前不詢問(并收集)注冊用戶的出生日期。出于這個原因,我將在這里停止,將前面的代碼僅供參考。

一些有用的授權(quán)相關(guān)參考

有關(guān) [授權(quán)] 屬性及其使用方法的其他信息,請參閱 http://mng.bz/Nmj2 中的指南。

要了解有關(guān) RBAC、CBAC 和 PBAC 的更多信息,請查看以下指南:

  • http://mng.bz/DZj9
  • http://mng.bz/lJnM
  • http://mng.bz/Bl8g

本節(jié)結(jié)束了我們的測試運行,以及我們進入 ASP.NET 核心身份驗證和授權(quán)的旅程。重要的是要明白,我們只是觸及了這些龐大而復雜的主題的表面。我們在本章中整理的示例源代碼對于一些沒有敏感或有價值數(shù)據(jù)的基本 Web API 來說可能已經(jīng)足夠了,但除非我們使用一些額外的安全措施來支持它,否則它可能不適合更復雜的方案。

至于 ASP.NET 核心身份,我們只是觸及了框架可以做什么的表面,從PBAC到非JWT承載者,更不用說與第三方授權(quán)提供程序和協(xié)議(如OAuth2)的內(nèi)置集成,由于空間原因,我沒有處理。盡管如此,本章提供的廣泛概述仍應有助于我們了解 Core 身份驗證和授權(quán)的工作原理 ASP.NET 以及如何在典型的 Web API 方案中實現(xiàn)它們。

9.5 習題

將我們在本章中學到的知識印記下來的最好方法是用一些與標識相關(guān)的升級任務來挑戰(zhàn)自己,我們的產(chǎn)品所有者可能希望分配給我們。與往常一樣,練習的解決方案可以在GitHub上的/Chapter_09/Exercises/文件夾中找到。若要測試它們,請將 MyBGList 項目中的相關(guān)文件替換為該文件夾中的文件,然后運行應用。

9.5.1 添加新角色

使用強類型方法將新的“SuperAdmin”角色添加到我們用于定義角色名稱的靜態(tài)類中。然后修改種子控制器的 AuthData 方法,以確保將創(chuàng)建新角色(如果該角色尚不存在)。

9.5.2 創(chuàng)建新用戶

使用帳戶/注冊端點創(chuàng)建新的“TestSuperAdmin”用戶,就像我們對 TestUser、TestAdministratoror 和 TestAdministrator 用戶所做的那樣。隨意選擇您自己的密碼(但請確保您會記住它以備將來使用)。

9.5.3 為用戶分配角色

修改 SeedController 的 AuthData 方法,將審閱人、管理員和超級管理員角色分配給測試超級管理員用戶。

9.5.4 實現(xiàn)測試端點

使用最小 API 添加新的 /auth/test/4 端點,并將其訪問權(quán)限限制為具有超級管理員角色的授權(quán)用戶。

9.5.5 測試 RBAC 流

使用帳戶/登錄終結(jié)點恢復 TestSuperAdmin 帳戶的 JWT,并使用它來嘗試訪問 /auth/test/4 終結(jié)點,并確保新用戶和角色按預期工作。

總結(jié)

  • 身份驗證是一種驗證實體(或個人)是否是它(或他們)聲稱的機制。授權(quán)定義了實體(或個人)能夠做什么。
    • 這兩個進程在任何需要限制對內(nèi)容、數(shù)據(jù)和/或終結(jié)點的訪問的 Web 應用或服務中都起著關(guān)鍵作用。
  • 在大多數(shù)實現(xiàn)方法中,身份驗證過程通常在授權(quán)過程之前發(fā)生,因為系統(tǒng)需要在分配其權(quán)限集之前標識調(diào)用客戶端。
    • 但是某些身份驗證技術(shù)(如持有者令牌)強制實施自包含的授權(quán)機制,從而允許服務器授權(quán)客戶端,而不必每次都對其進行身份驗證。
    • 持有者令牌的自包含授權(quán)方法在多功能性方面有幾個優(yōu)點,但如果服務器或客戶端無法保護令牌免受第三方訪問,則可能會引發(fā)一些安全問題。
  • ASP.NET 核心標識框架提供了一組豐富的 API 和高級抽象,可用于在任何 ASP.NET 核心應用中管理和存儲用戶帳戶,這使其成為在任何 ASP.NET 核心應用中實現(xiàn)身份驗證和授權(quán)機制的絕佳選擇。
    • 此外,借助多個內(nèi)置類、幫助程序和擴展方法,它可以輕松地與 EF Core 集成。
  • ASP.NET 核心標識提供了多種執(zhí)行授權(quán)檢查的方法:
    • RBAC,它易于實現(xiàn),通常足以滿足大多數(shù)需求。
    • CBAC,實施起來稍微復雜一些,但用途更廣,因為它可以用來檢查任何索賠。
    • PBAC是RBAC和CBAC使用的基礎(chǔ)結(jié)構(gòu),可以直接訪問以設(shè)置更高級的授權(quán)要求。
  • 身份驗證和授權(quán)是復雜的主題,尤其是從 IT 安全的角度來看,因為它們是黑客攻擊、DoS 攻擊和其他惡意活動的主要目標。
    • 因此,應非常謹慎地使用本章中描述的技術(shù),始終檢查更新,并與 ASP.NET 核心社區(qū)和IT安全標準提供的最佳實踐一起使用。

相關(guān)新聞

聯(lián)系我們
聯(lián)系我們
公眾號
公眾號
在線咨詢
分享本頁
返回頂部
五家渠市| 临洮县| 西峡县| 佛坪县| 枝江市| 睢宁县| 天峨县| 靖宇县| 龙州县| 余庆县| 隆林| 孝感市| 苍山县| 武汉市| 同德县| 沿河| 信阳市| 武川县| 东莞市| 怀化市| 泸定县| 京山县| 南和县| 扎赉特旗| 郎溪县| 怀集县| 合作市| 莱西市| 宁蒗| 自治县| 潍坊市| 广昌县| 莎车县| 会昌县| 丰原市| 万州区| 凌云县| 玉龙| 宜城市| 乌恰县| 莱西市|