上面的文章很好地解释了如何通过注册映射到声明的策略来利用声明,并使用这些策略来控制对各种端点的访问。
但是,当涉及到定义声明或向身份添加声明时,本文提供的所有内容是:
创建身份后,可能会为其分配由受信任方发出的一项或多项声明。
我们的声明可以通过 sql 查询访问,我想在身份验证后立即将用户声明嵌入到他们的 ClaimsPrincipal 中。
我见过通过中间件修改身份对象上的声明的示例。但是,中间件将在每个请求上执行,我不想从数据库中获取数据并修改每个请求的声明。我宁愿从一开始就执行一次,并且能够利用 api 端点和 blazor 组件的本机授权属性。
我见过的其他示例涉及在 blazor 客户端项目上实现自定义 AccountClaimsPrincipalFactory。这确实为我提供了一种在 CreateUserAsync 中附加我自己的自定义声明的方法,该声明将在身份验证时调用一次。然而,当涉及到注入依赖项以允许我进行必要的 api 调用来获取声明时,我无法在不破坏应用程序的情况下实现我自己的构造函数。从我看到的其他帖子来看,除了依赖注入之外,从该方法中进行 http 调用似乎存在挑战。
任何人都可以向我指出在 .net core blazor 应用程序中一次且仅添加一次自定义声明的任何实现的方向吗?
您可以像这样创建自定义
AuthenticationStateProvider
:
用户是一个
ClaimsPrincipal
,可以拥有任意数量的 ClaimsIdentity
对象。
public class CustomAuthenticationStateProvider : ServerAuthenticationStateProvider
{
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var authState = await base.GetAuthenticationStateAsync();
var user = authState.User;
// Get your custom data - in this case some roles
// add some new identities to the Claims Principal
user.AddIdentity(new ClaimsIdentity(new List<Claim>() { new Claim(ClaimTypes.Role, "Admin") }));
user.AddIdentity(new ClaimsIdentity(new List<Claim>() { new Claim(ClaimTypes.Role, "User") }));
// return the modified principal
return await Task.FromResult(new AuthenticationState(user));
}
}
最后在
Program
注册。
涉及的各种类型确实使得这在非托管独立 Blazor WebAssembly 应用程序中有点棘手。这是我的整个
AuthenticationStateProvider
子类:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
namespace BlazorWasmOIDC {
public class CustomAuthenticationStateProvider : RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions> {
[Obsolete]
public CustomAuthenticationStateProvider(IJSRuntime jsRuntime, IOptionsSnapshot<RemoteAuthenticationOptions<OidcProviderOptions>> options, NavigationManager navigation, AccountClaimsPrincipalFactory<RemoteUserAccount> accountClaimsPrincipalFactory) :
base(jsRuntime, options, navigation, accountClaimsPrincipalFactory) {}
public CustomAuthenticationStateProvider(IJSRuntime jsRuntime, IOptionsSnapshot<RemoteAuthenticationOptions<OidcProviderOptions>> options, NavigationManager navigation, AccountClaimsPrincipalFactory<RemoteUserAccount> accountClaimsPrincipalFactory, ILogger<RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>> logger) :
base(jsRuntime, options, navigation, accountClaimsPrincipalFactory, logger) {}
public override async Task<AuthenticationState> GetAuthenticationStateAsync() {
var authState = await base.GetAuthenticationStateAsync();
var user = authState.User;
AccessTokenResult tokenResult = await base.RequestAccessToken();
if (tokenResult.TryGetToken(out AccessToken token)) {
JwtSecurityToken jwt = new JwtSecurityToken(jwtEncodedString: token.Value);
foreach (Claim claim in jwt.Claims) {
if (claim.Type == "custom_user") { user.AddIdentity(new ClaimsIdentity(new List<Claim>() { claim })); }
if (claim.Type == "custom_admin") { user.AddIdentity(new ClaimsIdentity(new List<Claim>() { claim })); }
}
}
return (await Task.FromResult(new AuthenticationState(user)));
}
}
}
然后我像这样在
Program.cs
中注册它:
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
对于好奇的人(超出答案),就我而言,我这样做是为了解析原始的底层 JWT 并转发其中的自定义声明。然后,我可以使用 Blazor 内置的“策略”和“授权属性”机制以及我的自定义声明来控制授权(访问我的应用程序中的各种资源)。为此,我将以下内容添加到
Program.cs
:
builder.Services.AddAuthorizationCore(options => {
options.AddPolicy("IsCustomUser", policy => policy.RequireClaim("custom_user", "true"));
options.AddPolicy("IsCustomAdmin", policy => policy.RequireClaim("custom_admin", "true"));
});
然后我可以像这样限制访问(使用标准“Counter”演示剃须刀作为示例):
@page "/counter"
@using Microsoft.AspNetCore.Authorization;
@attribute [Authorize(Policy = "IsCustomUser")]
. . .