共用方式為


安全性:ASP.NET Web Forms 和 Blazor 中的驗證和授權

提示

本內容節錄自《Blazor for ASP NET Web Forms Developers for Azure》電子書,可以從 .NET Docs 取得,也可以免費下載 PDF 離線閱讀。

Blazor-for-ASP-NET-Web-Forms-Developers 電子書封面縮圖。

從 ASP.NET Web Forms 應用程式移轉到 Blazor 時,如果應用程式已設定驗證,幾乎肯定需要更新驗證和授權的執行方式。 本章將說明如何從 ASP.NET Web Forms 通用提供者模型 (適用於成員資格、角色和使用者設定檔) 進行移轉,以及如何從 Blazor 應用程式使用 ASP.NET Core 身分識別。 雖然本章將說明高階步驟和考量,但您也可以在參考文件中找到詳細的步驟和指令碼。

ASP.NET 通用提供者

自 ASP.NET 2.0 起,ASP.NET Web Forms 平台支援各種功能的提供者模型,包括成員資格。 通用成員資格提供者及選擇性角色提供者通常會隨 ASP.NET Web Forms 應用程式一起部署。 這提供健全且安全的方法來管理驗證和授權,以便在今日也能繼續正常運作。 這些通用提供者的最新供應項目是以 NuGet 套件 (Microsoft.AspNet.Providers) 形式提供。

通用提供者可搭配 SQL 資料庫結構描述使用,包括 aspnet_Applicationsaspnet_Membershipaspnet_Rolesaspnet_Users 等資料表。 當透過執行 aspnet_regsql.exe 命令來設定時,提供者會安裝資料表和預存程序,以提供所有必要的查詢和命令來使用基礎資料。 資料庫結構描述和這些預存程序與較新版的 ASP.NET Identity 和 ASP.NET Core 身分識別系統不相容,因此必須將現有的資料移轉到新系統。 圖 1 顯示為通用提供者設定的範例資料表結構描述。

通用提供者架構

通用提供者會處理使用者、成員資格、角色和設定檔。 使用者獲指派全域唯一識別碼,其基本資訊 (例如 userId、userName 等) 會儲存在 aspnet_Users 資料表中。 驗證資訊 (例如密碼、密碼格式、密碼 Salt、鎖定計數器和詳細資料等) 會儲存在 aspnet_Membership 資料表中。 角色只包含名稱和唯一識別碼,這會透過 aspnet_UsersInRoles 關聯資料表指派給使用者,以提供多對多關聯性。

如果您的現有系統除了成員資格之外還使用角色,則需要將使用者帳戶、相關聯的密碼、角色和角色成員資格移轉到 ASP.NET Core 身分識別。 您也可能需要更新目前使用 if 陳述式執行角色檢查的程式碼,改用宣告式篩選、屬性和 (或) 標籤協助程式。 我們將在本章結尾更詳細地檢閱移轉考量。

Web Form 中的授權組態

若要設定 ASP.NET Web Forms 應用程式中特定頁面的授權存取,您通常會指定特定頁面或資料夾無法由匿名使用者存取。 此組態是在 web.config 檔案中完成:

<?xml version="1.0"?>
<configuration>
    <system.web>
      <authentication mode="Forms">
        <forms defaultUrl="~/home.aspx" loginUrl="~/login.aspx"
          slidingExpiration="true" timeout="2880"></forms>
      </authentication>

      <authorization>
        <deny users="?" />
      </authorization>
    </system.web>
</configuration>

authentication 組態區段會設定應用程式的表單驗證。 authorization 區段用來禁止匿名使用者存取整個應用程式。 不過,您可以針對每個位置提供更細微的授權規則,並套用角色型授權檢查。

<location path="login.aspx">
  <system.web>
    <authorization>
      <allow users="*" />
    </authorization>
  </system.web>
</location>

上述組態與第一個組態結合時,可讓匿名使用者存取登入頁面,以覆寫非驗證使用者的全網站限制。

<location path="/admin">
  <system.web>
    <authorization>
      <allow roles="Administrators" />
      <deny users="*" />
    </authorization>
  </system.web>
</location>

上述組態與其他組態結合時,可限制只有「系統管理員」角色的成員才能存取 /admin 資料夾及其中的所有資源。 您也可以將個別 web.config 檔案放在 /admin 資料夾根目錄內來套用這項限制。

Web Form 中的授權碼

除了使用 web.config 設定存取權之外,您也可以透過程式設計方式在 Web Form 應用程式中設定存取權和行為。 例如,您可以根據使用者的角色限制執行特定作業或檢視特定資料的能力。

此程式碼可用於程式碼後置邏輯以及頁面本身:

<% if (HttpContext.Current.User.IsInRole("Administrators")) { %>
  <a href="/admin">Go To Admin</a>
<% } %>

除了檢查使用者角色成員資格之外,您也可以判斷使用者是否經過驗證 (但通常更好的做法是使用上述以位置為基礎的組態)。 以下示範此方法。

protected void Page_Load(object sender, EventArgs e)
{
    if (!User.Identity.IsAuthenticated)
    {
        FormsAuthentication.RedirectToLoginPage();
    }
    if (!Roles.IsUserInRole(User.Identity.Name, "Administrators"))
    {
        MessageLabel.Text = "Only administrators can view this.";
        SecretPanel.Visible = false;
    }
}

在上述程式碼中,使用角色型存取控制 (RBAC) 來判斷頁面的特定元素 (例如 SecretPanel) 是否根據目前使用者的角色顯示。

一般而言,ASP.NET Web Forms 應用程式會在 web.config 檔案中設定安全性,然後視需要在 .aspx 頁面及其相關的 .aspx.cs 程式碼後置檔案中新增其他檢查。 大多數的應用程式都會利用通用成員資格提供者,並經常搭配其他角色提供者。

ASP.NET Core 身分識別

雖然仍需負責驗證和授權,但與通用提供者相比,ASP.NET Core 身分識別採用一組不同的抽象概念和假設。 例如,新的身分識別模型支援第三方驗證,允許使用者使用社交媒體帳戶或其他受信任的驗證提供者進行驗證。 ASP.NET Core 身分識別支援登入、登出和註冊等常用頁面的 UI。 其會利用 EF Core 進行資料存取,並使用 EF Core 移轉來產生支援資料模型所需的必要結構描述。 ASP.NET Core 上的身分識別簡介有助於您概要了解 ASP.NET Core 身分識別所包含的項目,以及如何開始使用。 如果您尚未在應用程式及其資料庫中設定 ASP.NET Core 身分識別,這可協助您著手進行。

角色、宣告和原則

通用提供者和 ASP.NET Core 身分識別都支援角色的概念。 您可以為使用者建立角色,並將使用者指派給角色。 使用者可以屬於任意數目的角色,且您可以在授權實作中驗證角色成員資格。

除了角色之外,ASP.NET Core 身分識別還支援宣告和原則的概念。 相對於角色必須特別對應至該角色中使用者所能存取的一組資源,宣告只是使用者身分識別的一部分。 宣告是成對的名稱和數值,代表主體的身分,而不是主體可以執行的動作。

您可以直接檢查使用者的宣告,並根據這些值判斷使用者是否應該獲得資源的存取權。 不過,這類檢查通常是重複的,且散佈在整個系統中。 一個更好的方法是定義「原則」

授權原則是由一或多個需求組成。 原則會在 Startup.csConfigureServices 方法中註冊為授權服務組態的一部分。 例如,下列程式碼片段會設定名為 "CanadiansOnly" 的原則,要求使用者的 Country 宣告具有值 "Canada"。

services.AddAuthorization(options =>
{
    options.AddPolicy("CanadiansOnly", policy => policy.RequireClaim(ClaimTypes.Country, "Canada"));
});

您可以在此文件中深入了解如何建立自訂原則

無論您使用的是原則還是角色,都可以透過 @attribute 指示詞所套用的 [Authorize] 屬性來指定 Blazor 應用程式中的特定頁面需要該角色或原則。

需要角色:

@attribute [Authorize(Roles ="administrators")]

需要滿足原則:

@attribute [Authorize(Policy ="CanadiansOnly")]

如果您需要在程式碼中存取使用者的驗證狀態、角色或宣告,有兩個主要方式可達成這項功能。 第一個是以串聯參數形式接收驗證狀態。 第二個是使用插入的 AuthenticationStateProvider 來存取狀態。 如需每個方法的詳細資料,請參閱 Blazor 安全性文件中的說明。

下列程式碼示範如何以串聯參數形式接收 AuthenticationState

[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }

有了此參數,您就可以使用下列程式碼來取得使用者:

var authState = await authenticationStateTask;
var user = authState.User;

下列程式碼會示範如何插入 AuthenticationStateProvider

@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider

有了此提供者,您就可以使用下列程式碼來存取使用者:

AuthenticationState authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
ClaimsPrincipal user = authState.User;

if (user.Identity.IsAuthenticated)
{
  // work with user.Claims and/or user.Roles
}

注意:本章稍後說明的 AuthorizeView 元件,可讓您以宣告方式來控制使用者在頁面或元件上看到的內容。

若要處理使用者和宣告 (在 Blazor Server 應用程式中),您可能還需要插入 UserManager<T> (使用 IdentityUser 作為預設值),以便用來列舉和修改使用者的宣告。 首先插入此類型,並將它指派給屬性:

@inject UserManager<IdentityUser> MyUserManager

然後使用它來處理使用者的宣告。 下列範例示範如何新增和保存使用者的宣告:

private async Task AddCountryClaim()
{
    var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
    var user = authState.User;
    var identityUser = await MyUserManager.FindByNameAsync(user.Identity.Name);

    if (!user.HasClaim(c => c.Type == ClaimTypes.Country))
    {
        // stores the claim in the cookie
        ClaimsIdentity id = new ClaimsIdentity();
        id.AddClaim(new Claim(ClaimTypes.Country, "Canada"));
        user.AddIdentity(id);

        // save the claim in the database
        await MyUserManager.AddClaimAsync(identityUser, new Claim(ClaimTypes.Country, "Canada"));
    }
}

如果您需要處理角色,請遵循相同的方法。 您可能需要插入 RoleManager<T> (使用 IdentityRole 作為預設類型),以列出和管理角色本身。

注意:在 Blazor WebAssembly 專案中,您必須提供伺服器 API 來執行這些作業 (而不是直接使用 UserManager<T>RoleManager<T>)。 Blazor WebAssembly 用戶端應用程式會安全地呼叫針對此用途公開的 API 端點來管理宣告和 (或) 角色。

移轉指南

從 ASP.NET Web Forms 和通用提供者移轉到 ASP.NET Core 身分識別需要執行幾個步驟:

  1. 在目的地資料庫中建立 ASP.NET Core 身分識別資料庫結構描述
  2. 將資料從通用提供者結構描述移轉到 ASP.NET Core 身分識別結構描述
  3. 將組態從 web.config 移轉到中介軟體和服務,通常是在 Program.cs (或 Startup 類別) 中
  4. 更新使用控制項和條件的個別頁面,以使用標籤協助程式和新的身分識別 API。

下列各節將詳細說明這些每一個步驟。

建立 ASP.NET Core 身分識別結構描述

有數種方式可以建立用於 ASP.NET Core 身分識別的必要資料表結構。 最簡單方式是建立新的 ASP.NET Core Web 應用程式。 選擇 Web 應用程式,然後將 [驗證類型] 變更為使用 [個別帳戶]。

具有個別帳戶的新專案

從命令列,您可以透過執行 dotnet new webapp -au Individual 來執行相同的動作。 建立應用程式之後,請執行應用程式並在網站上註冊。 您應該會觸發如下所示的頁面:

套用移轉頁面

按一下 [套用移轉] 按鈕,這應該會為您建立必要的資料庫資料表。 此外,移轉檔案應該會出現在您的專案中,如下所示:

移轉檔案

您可以使用下列命令列工具來自行執行移轉,而不需執行 Web 應用程式:

dotnet ef database update

如果您想要執行指令碼來將新的結構描述套用至現有資料庫,您可以從命令列編寫這些移轉的指令碼。 執行下列命令以產生指令碼:

dotnet ef migrations script -o auth.sql

上述命令會在輸出檔案 auth.sql 中產生 SQL 指令碼,之後可針對您想要的任何資料庫執行。 如果您在執行 dotnet ef 命令時有任何問題,請確定您已在系統上安裝 EF Core 工具

如果來源資料表上有其他資料行,則您必須在新的結構描述中識別這些資料行的最佳位置。 一般而言,在 aspnet_Membership 資料表上找到的資料行應該對應至 AspNetUsers 資料表。 aspnet_Roles 上的資料行應該對應至 AspNetRolesaspnet_UsersInRoles 資料表上的任何其他資料行都會新增至 AspNetUserRoles 資料表。

此外,也值得考慮將任何其他資料行放在不同的資料表上。 如此一來,未來的移轉就不需要考慮預設身分識別結構描述的這類自訂。

將資料從通用提供者移轉到 ASP.NET Core 身分識別

準備好目的地資料表結構描述之後,下一個步驟是將使用者和角色記錄移轉到新的結構描述。 您可以在這裡找到結構描述差異的完整清單,包括哪些資料行對應至哪些新的資料行。

若要將使用者從成員資格移轉到新的身分識別資料表,您應該遵循文件中所述的步驟進行。 遵循這些步驟和指令碼之後,您的使用者必須在下次登入時變更其密碼。

您可以移轉使用者密碼,但流程會更加複雜。 要求使用者在移轉流程中更新其密碼,並鼓勵他們使用新的唯一密碼,可能會增強應用程式的整體安全性。

將安全性設定從 web.config 移轉到應用程式啟動檔案

如上所述,ASP.NET 成員資格和角色提供者是在應用程式的 web.config 檔案中設定。 由於 ASP.NET Core 應用程式未繫結至 IIS 並使用不同的系統進行設定,因此必須在其他地方進行這些設定。 在大部分情況下,ASP.NET Core 身分識別會在 Program.cs 檔案中設定。 開啟稍早建立的 Web 專案 (用以產生身分識別資料表結構描述),並檢閱其 Program.cs (或 Startup.cs) 檔案。

下列程式碼新增 EF Core 和身分識別的支援:

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options =>
    options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

AddDefaultIdentity 擴充方法會用來設定身分識別使用預設 ApplicationDbContext 和架構的 IdentityUser 類型。 如果您使用的是自訂 IdentityUser,請務必在此指定其類型。 如果這些擴充方法無法在應用程式中運作,請檢查您是否有適當的 using 指示詞,以及您是否具有必要的 NuGet 套件參考。 例如,您的專案應該會參考某些版本的 Microsoft.AspNetCore.Identity.EntityFrameworkCoreMicrosoft.AspNetCore.Identity.UI 套件。

此外,在 Program.cs 中,您應該會看到為網站設定的必要中介軟體。 特別是應該在適當的位置設定 UseAuthenticationUseAuthorization

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    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.UseAuthentication();
app.UseAuthorization();

//app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

ASP.NET 身分識別不會設定 Program.cs 中位置的匿名或角色型存取。 您必須將任何位置特定的授權組態資料移轉到 ASP.NET Core 中的篩選。 記下哪些資料夾和頁面需要這類更新。 您將在下一節進行這些變更。

更新個別頁面以使用 ASP.NET Core 身分識別抽象概念

在 ASP.NET Web Forms 應用程式中,如果您有拒絕匿名使用者存取特定頁面或資料夾的 web.config 設定,您可以透過將 [Authorize] 屬性新增至這類頁面來移轉這些變更:

@attribute [Authorize]

如果您進一步拒絕除非屬於特定角色之使用者的存取,您同樣可以透過新增指定角色的屬性來移轉此行為:

@attribute [Authorize(Roles ="administrators")]

[Authorize] 屬性僅適用於透過 Blazor 路由器到達的 @page 元件。 該屬性不適用於子元件,應該改用 AuthorizeView

如果您在頁面標記內有判斷是否要向特定使用者顯示某些程式碼的邏輯,您可以將此邏輯取代為 AuthorizeView 元件。 AuthorizeView 元件會根據使用者是否獲得授權以查看某個 UI 來選擇性地顯示該 UI。 它也會公開可用來存取使用者資訊的 context 變數。

<AuthorizeView>
    <Authorized>
        <h1>Hello, @context.User.Identity.Name!</h1>
        <p>You can only see this content if you are authenticated.</p>
    </Authorized>
    <NotAuthorized>
        <h1>Authentication Failure!</h1>
        <p>You are not signed in.</p>
    </NotAuthorized>
</AuthorizeView>

您可以透過從使用 [CascadingParameter] 屬性設定的 Task<AuthenticationState 存取使用者,來存取程序性邏輯內的驗證狀態。 此組態會讓您有權存取使用者,這可讓您判斷使用者是否經過驗證,以及使用者是否屬於特定角色。 如果您需要在程序上評估原則,您可以插入 IAuthorizationService 的執行個體,並在其上呼叫 AuthorizeAsync 方法。 下列範例程式碼示範如何取得使用者資訊,並允許授權使用者執行受 content-editor 原則限制的工作。

@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

<button @onclick="@DoSomething">Do something important</button>

@code {
    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }

    private async Task DoSomething()
    {
        var user = (await authenticationStateTask).User;

        if (user.Identity.IsAuthenticated)
        {
            // Perform an action only available to authenticated (signed-in) users.
        }

        if (user.IsInRole("admin"))
        {
            // Perform an action only available to users in the 'admin' role.
        }

        if ((await AuthorizationService.AuthorizeAsync(user, "content-editor"))
            .Succeeded)
        {
            // Perform an action only available to users satisfying the
            // 'content-editor' policy.
        }
    }
}

AuthenticationState 必須先設定為串聯值,才能繫結至這類串聯參數。 這通常是使用 CascadingAuthenticationState 元件來完成。 此組態通常會在 App.razor 中完成:

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData"
                DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

摘要

Blazor 會使用與 ASP.NET Core 相同的安全性模型,也就是 ASP.NET Core 身分識別。 從通用提供者移轉到 ASP.NET Core 身分識別相當簡單,但前提是未對原始資料結構描述套用太多自訂。 移轉資料之後,對於 Blazor 應用程式中的驗證和授權處理方式已詳加記載,且針對大部分安全性需求可進行設定並提供程式設計支援。

參考資料