什麼是Claim-Based Identity

在Web應用程式開發的過程中,我們常常會聽到所謂的 Claim-Based Authentication或Authorization之類似的用詞,中文常直白的翻成『基於聲明的驗證或授權』。

翻譯沒錯,但『基於聲明的…』中的這個『聲明(Claim)』,有點不好理解。

簡單的說,你可以暫時先把所謂的『聲明(Claim)』理解成一張駕駛執照(或身分證),由監理單位(或戶政單位)發出,發出Claim的單位我們叫 Issuer。

在傳統的 asp.net core web應用程式當中,身分驗證與授權(authentication and authorization),預設常常都是走 Claim-Based 來實踐的。其實應該說,當我們進入了WEB 2.0時代之後,網站開始需要與用戶端互動,伺服器端需要識別用戶是誰,並針對不同的用戶給予不同的權限,這時,基於聲明的Claim-Based Identity就逐漸成為主流。

在解釋之前,先簡單提一下驗證(authentication)和授權(authorization),一般來說,『驗證』是確定你是誰(who),而『授權』則是確定你能做(或能看、能存取)什麼(What)。

為了實現這件事情,一般的網站在開發時,大致會這麼做:
圖片

用戶在使用某個網站時,會先透過瀏覽器以匿名方式進到一個登入頁面。進入該頁面後,會先以帳號密碼(或是其他機制、例如email、SMS)進行登入(Login)工作,這就是驗證。通過驗證之後,網頁系統一般會建立一個由伺服器端發出的Claim,這個Claim就像我們剛才說的駕駛執照一樣,可能描述(聲明)著用戶的個人資訊,以及該用戶可以駕駛的車種(也就是權限之類的訊息)。為了確保安全,一般來說,Claim常會被編碼或加密,然後包裹成為一個Token,甚至加入一些檢核機制,以便於能夠確保該Claim(Token)的合法性(例如足以識別該Token是由誰Issue的)。接著,應用程式就可以拿著這個Token(或實作成cookie),作為憑證來存取特定的資源。

以上這樣的機制,就是Claim-Based Identity。

你可能會覺得疑惑,那我們在開發 asp.net core 網站的時候,有這麼複雜嗎? 感覺好像沒有做那麼多步驟啊!? 其實有,只是 .net 框架主動為你做了大半,所以開發人員常常不知道已經做了這些。

當我們在開發一個 Claim-Based Identity 的 asp.net core web應用程式,在程式運行的過程當中,我們讓用戶輸入帳號密碼,來判斷用戶身分是否正確,這個過程就是驗證(authentication),完成驗證之後,伺服器端會建立出一組Claim,在該Claim中就包含著可以識別用戶的資訊,然後,系統會把這組Claim包裝起來(變成cookie),傳遞到用戶端(就是瀏覽器):
圖片

接著,當用戶未來透過瀏覽器與伺服器端互動(例如 : 查詢或輸入資料、上傳檔案…etc.),瀏覽器都會在http header中自動帶著這個 cookie,如此一來,伺服器端(的網站)就可以透過該cookie中的資訊(Claim),來識別當前的用戶是誰。

這就是Web 2.0時代之後(一直到今天),絕大部分瀏覽器和伺服器上的網站之間的溝通模式。如今,這個通訊過程一律會走 SSL 加密,且我們信任瀏覽器和作業系統(這是前提),這樣才能夠確保整個身分驗證與授權是真正安全的。

有了上面這樣的觀念之後,我們就具體來看,到底程式碼當中什麼時候有建立這個 Claim 機制的?

你可以依照底下這段指令,透過CLI,以 .net core 6 以上環境,建立範例程式:

dotnet new install isRock.Web.Core.Razor.Example
md basiclogin
cd basiclogin
dotnet new webapp
dotnet new isLoginPage
#open project with vs code
code .

開啟 vs code 之後,編輯 Program.cs,修改底下程式碼,其中第 1, 6, 22, 23 行,是我們後來加進去的:
圖片

第6行 builder.Services.AddAuthentication 表明了我們將預設採用 cookies 機制作為 Claim 的存放與傳輸,builder.Services.指令指定了我們將用預設的CookieAuthenticationDefaults來實踐這個可抽換的DI身分驗證實作。而底下 22, 23 行的 app. 則是指定Middleware要在運行時執行 CookiePolicy 與 Authentication 機制,這是用來支撐我們實作 Claim-Based Identity。

完成後的完整程式碼如下:

using Microsoft.AspNetCore.Authentication.Cookies;

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
           
var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();

app.Run();

接著,請調整一下UI,我們修改 Pages\Shared_Layout.cshtml 其中的 header 區段,在其中加入登入功能,並且顯示當前已登入(若有的話)的用戶名稱:

<header>
    <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
        <div class="container">
            <a class="navbar-brand" asp-area="" asp-page="/Index">testwebapp</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse"
                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                <ul class="navbar-nav flex-grow-1">
                    <li class="nav-item">
                        <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link text-dark" asp-area="" asp-page="/Login">Login/Logout</a>
                    </li>
                </ul>
            </div>
            <div>
                current user : @User.Identity.Name
            </div>
        </div>
    </nav>
</header>

另外,我們剛才在CLI中執行的 dotnet new isLoginPage ,會在專案中建立 Login.cshtml 與 Login.cshtml.cs ,這是我們寫好的登入頁面。

如果你看裡面的程式碼,其中有著底下這麼一段:
圖片
上面這段程式碼當中,39行(我們沒有實作)可以寫成去資料庫抓取用戶的帳號密碼,進行身分驗證,驗證完成後,41-48行就是在建立我們前面說過的 Claim(聲明),50行在建立ClaimsIdentity(用於包裹Claims,有需要時,一個ClaimsIdentity中可以包含多個Claim),53行在建立ClaimsPrincipal(用於包裹ClaimsIdentity),然後57行的HttpContext.SignInAsync(),就會把這個ClaimsPrincipal以 cookie 的形式,傳到用戶端,寫入瀏覽器,而這就是登入機制的實作

這個以 cookie 的形式儲存的ClaimsPrincipal,概念上類似於 Token 的用途,後續瀏覽器會拿著這個資訊,在每次與伺服器端交互的過程中,提供給伺服器端看,以便於讓伺服器端確認我們的身分,以及這身份的合法性。ClaimsPrincipal和Token兩者都會包裹著 Claims,但兩者的實作方式有所不同,後面我們講到 Token 的時候,再來說明其中的具體差異。

總的來說,asp.net core 最基本的登入功能,就是透過這樣的 Claim-Based Identity 來實踐的。而開發框架就將其更進一步的,與SDK整合在一起,如此一來,在後續的開發階段,開發人員就可以輕鬆地透過

User.Identity.IsAuthenticated
或
User.Identity.Name

來判斷用戶是否登入,並取得用戶名稱。

這就是 asp.net core 透過 Claim 來識別用戶的身分(甚至群組、角色)的方式,此即為 Claim-Based Identity ,以這個為基礎,我們就可以接著做後續的RBAC或PBAC授權(權限控管)機制了。

enter image description here

留言

這個網誌中的熱門文章

在POC或迷你專案中使用 LiteDB

使用Qdrant向量資料庫實作語意相似度比對

專業的價值...

讓 LINE Bot 對談機器人顯示 "Loading..." 動畫

周末讀書會 - 一如既往