共用方式為


使用 Windows 驗證保護 ASP.NET Core Blazor Web App

本文說明如何使用範例應用程式透過 Blazor Web App 來保護 。 如需詳細資訊,請參閱在 ASP.NET Core 設定 Windows 驗證。

Blazor Web App的應用程式規格:

範例應用程式

使用下列連結,透過 Blazor 範例存放庫中的最新版本資料夾存取範例。 此範例位於 .NET 9 或更新版本的 BlazorWebAppWinAuthServer 資料夾中。

檢視或下載範例程式碼 \(英文\) (如何下載)

設定

範例應用程式不需要設定才能在本機執行。

部署至 IIS 之類的主機時,應用程式必須採用模擬,才能在使用者帳戶下執行。 如需詳細資訊,請參閱在 ASP.NET Core 設定 Windows 驗證。

範例應用程式的程式碼

檢查範例應用程式中 Program 檔案是否有下列 API 呼叫。

AddAuthentication 會使用 NegotiateDefaults.AuthenticationScheme 驗證機制來呼叫。 AddNegotiateAuthenticationBuilder 設定為使用 Negotiate(也稱為 Windows、Kerberos 或 NTLM)驗證,而驗證處理程式支援 Windows 和 Linux 伺服器上的 Kerberos:

builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
    .AddNegotiate();

AddAuthorization 新增授權策略服務。 AuthorizationOptions.FallbackPolicy 設定後援授權原則,此原則設定為默認原則 (AuthorizationOptions.DefaultPolicy)。 預設原則需要已驗證的使用者才能存取應用程式:

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

AddCascadingAuthenticationState 將串聯驗證狀態新增至服務集合。 這相當於將 CascadingAuthenticationState 元件放在應用程式元件的根目錄:

builder.Services.AddCascadingAuthenticationState();

新增了一個 授權原則 用於 Windows 安全性識別碼 (SID)。 下列範例中的 S-1-5-113 已知 SID 表示使用者是本機帳戶,它會將網路登入限制為本機帳戶,而不是「系統管理員」或對等帳戶:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("LocalAccount", policy =>
        policy.RequireClaim(
            "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid",
            "S-1-5-113"));   

授權原則是由 LocalAccountOnly 元件強制執行。

Components/Pages/LocalAccountOnly.razor

@page "/local-account-only"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize("LocalAccount")]

<h1>Local Account Only</h1>

<p>
    You can only reach this page by satisfying the
    <code>LocalAccount</code> authorization policy.
</p>

元件 UserClaims 會列出使用者的宣告和角色,包括使用者的 Windows 安全性識別碼 (SID) 與 SID 轉譯。

Components/Pages/UserClaims.razor

@page "/user-claims"
@using System.Security.Claims
@using System.Security.Principal
@using Microsoft.AspNetCore.Components.QuickGrid

<PageTitle>User Claims & Roles</PageTitle>

<h1>User Claims & Roles</h1>

<QuickGrid Items="claims" Pagination="pagination">
    <Paginator State="pagination" />
    <PropertyColumn Property="@(p => p.Type)" Sortable="true" />
    <PropertyColumn Property="@(p => p.Value)" Sortable="true" />
    <PropertyColumn Property="@(p => GetClaimAsHumanReadable(p))" Sortable="true" Title="Translation" />
    <PropertyColumn Property="@(p => p.Issuer)" Sortable="true" />
</QuickGrid>

<h1>User Roles</h1>

@if (roles.Any())
{
    <ul>
        @foreach (var role in roles)
        {
            <li>@role</li>
        }
    </ul>
}
else
{
    <p>No roles available.</p>
}

@code {
    private IQueryable<Claim> claims = Enumerable.Empty<Claim>().AsQueryable();
    private IEnumerable<string> roles = Enumerable.Empty<string>();
    PaginationState pagination = new PaginationState { ItemsPerPage = 10 };

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

    protected override async Task OnInitializedAsync()
    {
        if (AuthState == null)
        {
            return;
        }

        var authState = await AuthState;

        claims = authState.User.Claims.AsQueryable();

        roles = authState.User.Claims
            .Where(claim => claim.Type == ClaimTypes.Role)
            .Select(claim => claim.Value);
    }

    private string GetClaimAsHumanReadable(Claim claim)
    {
        if (!OperatingSystem.IsWindows() ||
            claim.Type is not (ClaimTypes.PrimarySid or ClaimTypes.PrimaryGroupSid
                or ClaimTypes.GroupSid))
        {
            // We're either not on Windows or not dealing with a SID Claim that
            // can be translated
            return string.Empty;
        }

        SecurityIdentifier sid = new SecurityIdentifier(claim.Value);

        try
        {
            // Throw an exception if the SID can't be translated
            var account = sid.Translate(typeof(NTAccount));

            return account.ToString();
        }
        catch (IdentityNotMappedException)
        {
            return "Could not be mapped";
        }
    }
}

其他資源