Net Core 和我一直在尝试创建一个小型 Blazor Server 应用程序,以期将一些较大的内部应用程序(当前在 .Net WebForms 中)迁移到其中。我们混合使用 Windows 身份验证(用户只有在我们的域中才能访问应用程序)和来自数据库中 UserRoles 表的角色。 我已成功创建一个 Blazor 服务器应用程序,该应用程序使用 Windows 身份验证并使用 IClaimsTransformation 类从数据库中获取用户的角色并将其添加为声明。一切似乎都运行良好,我可以使用这些角色来授权内容。 我对其工作原理的理解是,每次应用程序需要应用授权规则时,它最终都会通过 IClaimsTransformation 进行调用,而 IClaimsTransformation 又会调用我的方法来查询数据库中的用户角色。
但是,在调试我的应用程序时,我注意到我的 IClaimsTransformation 类中的方法在启动应用程序时被多次调用(每次都调用数据库),但随后再也不会被调用。当我进入应用程序中具有授权规则的部分时,它们会被强制执行,所以我认为默认情况下会缓存角色? (我没有设置任何缓存)
这意味着,如果用户在使用应用程序时角色发生更改,则应用程序中持有的声明不会更新。强制新角色生效的唯一方法是关闭并重新打开应用程序,这会导致 IClaimsTransformation 类中的方法被多次调用并获取新角色。
真的,我只是好奇我所看到的这种行为是否是我的场景所预期的,或者我是否在配置中遗漏了某些内容。
我的 IClaimsTransformation 课程:
public class ClaimsTransformer : IClaimsTransformation
{
private readonly UserProfileService _userProfileService;
public ClaimsTransformer(UserProfileService userProfileService)
{
_userProfileService = userProfileService;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
// User is not authenticated so just return right away
if (principal.Identity?.IsAuthenticated is false)
{
return principal;
}
ClaimsIdentity claimsIdentity = new ClaimsIdentity();
//below calls method in userProfileService that returns list of roles for the given username
List<Role> roleList = (await _userProfileService.GetRolesForUserNameAsync(principal.Identity.Name.ToList();
foreach (Role r in roleList) {
if (!principal.HasClaim(claim => claim.Type == r.RoleName))
{
claimsIdentity.AddClaim(new Claim(claimsIdentity.RoleClaimType, r.RoleName));
}
}
principal.AddIdentity(claimsIdentity);
return principal;
}
Program.cs的相关部分:
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy.
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
授权页面中使用的视图:
<AuthorizeView Roles="Admin">
<Authorized>
<p>You are an Admin</p>
</Authorized>
<NotAuthorized>
<p>You are not an Admin</p>
</NotAuthorized>
</AuthorizeView>
这种行为是可以预料的。
而是执行如下所示的操作,
public class ApplicationAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly AuthenticationStateProvider _authenticationStateProvider;
private readonly IUserService _userService;
public ApplicationAuthenticationStateProvider(AuthenticationStateProvider AuthenticationStateProvider, IUserService userService)
{
_authenticationStateProvider = AuthenticationStateProvider;
_userService = userService;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
AuthenticationState authenticationState = await _authenticationStateProvider.GetAuthenticationStateAsync();
if (authenticationState.User.Identity?.IsAuthenticated != true)
{
return authenticationState;
}
// Check if user already has the UserId claim
if (authenticationState.User.HasClaim(x => x.Type == ApplicationClaimTypes.UserId))
{
return authenticationState;
}
// TODO: Get user from database using IUserService and current claims
string applicationUserId = "someUserId"
ClaimsIdentity claimsIdentity = new ClaimsIdentity();
claimsIdentity.AddClaim(new Claim(ApplicationClaimTypes.UserId, applicationUserId));
authenticationState.User.AddIdentity(claimsIdentity);
return authenticationState;
}
}
注册
ApplicationAuthenticationStateProvider
,
builder.Services.AddScoped<ApplicationAuthenticationStateProvider>();
使用,
// This is some service where you want to use authenticationState
public class BlazorIdentityService : IIdentityService
{
private readonly ApplicationAuthenticationStateProvider _applicationAuthenticationStateProvider;
public BlazorIdentityService(ApplicationAuthenticationStateProvider applicationAuthenticationStateProvider)
{
_applicationAuthenticationStateProvider = applicationAuthenticationStateProvider;
}
public async Task<string> GetCurrentUserId()
{
AuthenticationState authenticationState = await _applicationAuthenticationStateProvider?.GetAuthenticationStateAsync();
return authenticationState.User?.Claims?.SingleOrDefault(x => x.Type == ApplicationClaimTypes.UserId)?.Value;
}
}