本文介绍如何使用示例应用保护 Blazor Web App Windows 身份验证 。 有关详细信息,请参阅在 ASP.NET Core 中配置 Windows 身份验证。
应用程序规范如下 Blazor Web App:
- 采用 具有全局交互性的交互式服务器呈现模式。
- 为 Windows 安全标识符建立授权策略以访问安全页。
示例应用
使用以下链接通过示例存储库中的 Blazor 最新版本文件夹访问示例。 此示例位于 BlazorWebAppWinAuthServer .NET 9 或更高版本的文件夹中。
配置
示例应用不需要配置在本地运行。
部署到主机(如 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";
}
}
}