本文說明如何使用範例應用程式透過 Blazor Web App 來保護 。 如需詳細資訊,請參閱在 ASP.NET Core 中設定 Windows 驗證。
Blazor Web App的應用程式規格:
- 採用具有全域互動 的Interactive Server 轉譯模式。
- 為 Windows 安全性識別子 建立 授權原則,以存取安全頁面。
範例應用程式
使用下列連結,透過 Blazor 範例存放庫中的最新版本資料夾存取範例。 此範例位於 .NET 9 或更新版本的 BlazorWebAppWinAuthServer 資料夾中。
檢視或下載範例程式碼 \(英文\) (如何下載)
設定
範例應用程式不需要設定才能在本機執行。
部署至 IIS 之類的主機時,應用程式必須採用模擬,才能在使用者帳戶下執行。 如需詳細資訊,請參閱在 ASP.NET Core 中設定 Windows 驗證。
範例應用程式的程式碼
檢查範例應用程式中 Program 檔案是否有下列 API 呼叫。
AddAuthentication 會使用 NegotiateDefaults.AuthenticationScheme 驗證機制來呼叫。 AddNegotiate 將 AuthenticationBuilder 設定為使用 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";
}
}
}