我正在开发一个应用程序,允许管理员类型用户更改普通用户的访问权限。
我正在使用 blazor 服务器和 mudblazor。我目前已将其设置为管理员可以单击用户并查看可用站点权限列表,这些权限存储为用户声明。
我希望更改立即生效,而不影响登录用户。
到目前为止我几乎可以让它工作了。问题是当为登录用户设置新的 authstate 时,它会将 authstate ClaimsPrincipal.Identity 设置为 null。
这与这个问题类似。但我不想劫持线程。
我正在使用自定义 RevalidatingServerAuthenticationStateProvider 类。
public class RevalidatingIdentityAuthenticationStateProvider<TUser>
: RevalidatingServerAuthenticationStateProvider where TUser : class
{
private readonly ILoggerFactory _loggerFactory;
private readonly IServiceScopeFactory _scopeFactory;
private readonly IdentityOptions _options;
public RevalidatingIdentityAuthenticationStateProvider(
ILoggerFactory loggerFactory,
IServiceScopeFactory scopeFactory,
IOptions<IdentityOptions> optionsAccessor)
: base(loggerFactory)
{
_scopeFactory = scopeFactory;
_options = optionsAccessor.Value;
_loggerFactory = loggerFactory;
}
protected override TimeSpan RevalidationInterval => TimeSpan.FromSeconds(10);
/// <summary>
/// Validates the authentication state asynchronously.
/// </summary>
/// <param name="authenticationState">The authentication state.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// A <see cref="TaskTResult"/> representing the result of the asynchronous operation.
/// </returns>
protected override async Task<bool> ValidateAuthenticationStateAsync(
AuthenticationState authenticationState, CancellationToken cancellationToken)
{
// Create a logger to log any errors
var logger = _loggerFactory.CreateLogger(nameof(ValidateAuthenticationStateAsync));
// Create a new scope to ensure fresh data is fetched
var scope = _scopeFactory.CreateScope();
try
{
//logger.LogInformation("{authenticationstate}", (authenticationState.User is null));
// Get the user manager from the scope
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<TUser>>();
// Validate the security stamp of the user
return await ValidateSecurityStampAsync(userManager, authenticationState.User);
}
finally
{
// Dispose of the scope
if (scope is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
}
/// <summary>
/// Validates the security stamp of a user.
/// </summary>
/// <param name="userManager">The user manager.</param>
/// <param name="principal">The claims principal.</param>
/// <returns>True if the security stamp is valid, false otherwise.</returns>
private async Task<bool> ValidateSecurityStampAsync(UserManager<TUser> userManager, ClaimsPrincipal principal)
{
// Create a logger to log information
var logger = _loggerFactory.CreateLogger(nameof(ValidateSecurityStampAsync));
// Get the user from the user manager
var user = await userManager.GetUserAsync(principal);
logger.LogInformation("user name test: {test}", principal.Identity.Name);
// If the user is null, return false
if (user == null)
{
logger.LogInformation("user is null?");
return false;
}
// If the user manager does not support user security stamp, return true
else if (!userManager.SupportsUserSecurityStamp)
{
return true;
}
// Otherwise, get the security stamp from the principal and the user
else
{
var principalStamp = principal.FindFirstValue(_options.ClaimsIdentity.SecurityStampClaimType);
var userStamp = await userManager.GetSecurityStampAsync(user);
logger.LogInformation("this thing {is}, principal stamp {stamp1}, authtype {type}, user stamp {stamp2}", _options.ClaimsIdentity.SecurityStampClaimType, principalStamp, principal.Identity.AuthenticationType, userStamp);
// Return true if the security stamps match
return principalStamp == userStamp;
}
}
public async Task RequestReauthentication(ClaimsPrincipal claimsPrincipal, CancellationToken cancellationToken)
{
// Create a logger to log information
var logger = _loggerFactory.CreateLogger(nameof(RequestReauthentication));
logger.LogInformation("get auth method called");
// Create a new scope to ensure fresh data is fetched
var scope = _scopeFactory.CreateScope();
try
{
// Get the user manager from the scope
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<TUser>>();
var user = await userManager.GetUserAsync(claimsPrincipal);
if (user is not null && claimsPrincipal.Identity is not null)
{
var claims = await userManager.GetClaimsAsync(user);
var newPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, claimsPrincipal.Identity.AuthenticationType));
var authState = Task.FromResult(new AuthenticationState(newPrincipal));
NotifyAuthenticationStateChanged(authState);
}
}
catch (Exception ex)
{
logger.LogError("exception: {message}", ex.Message);
}
finally
{
// Dispose of the scope
if (scope is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
}
}
对声明发生更改的用户调用 RequestReauthentication。该用户在登录时将动态地看到页面上的更改。但是,当调用 ValidateAuthenticationStateAsync(又调用 ValidateSecurityStampAsync)时,用户将返回 null。然后状态就变成未授权了。
我能做些什么来让它工作吗?或者我应该采取不同的路线并通知用户并在大约 10 分钟内注销/除非声明很重要,并且它会立即将用户注销。
代码很混乱,因为我对编码相当陌生,只是想通过自己的方式完成它。
该死。从字面上看,就在发布后,我继续尝试并找到了解决方案!好吧,如果其他人尝试这样做,这里是方法。当然简单。
我使用我刚刚发现的 Clone() 方法克隆了输入标识。我之前曾尝试过只传递身份,但没有成功。
public async Task RequestReauthentication(ClaimsPrincipal claimsPrincipal, CancellationToken cancellationToken)
{
// Create a logger to log information
var logger = _loggerFactory.CreateLogger(nameof(RequestReauthentication));
logger.LogInformation("get auth method called");
// Create a new scope to ensure fresh data is fetched
var scope = _scopeFactory.CreateScope();
try
{
// Get the user manager from the scope
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<TUser>>();
var user = await userManager.GetUserAsync(claimsPrincipal);
if (user is not null && claimsPrincipal.Identity is not null)
{
// Clone current identity
var clone = claimsPrincipal.Clone();
var newIdentity = (ClaimsIdentity)clone.Identity;
var claims = await userManager.GetClaimsAsync(user);
var newPrincipal = new ClaimsPrincipal(new ClaimsIdentity(newIdentity, claims));
//var newPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, claimsPrincipal.Identity.AuthenticationType));
var authState = Task.FromResult(new AuthenticationState(newPrincipal));
NotifyAuthenticationStateChanged(authState);
}
}
catch (Exception ex)
{
logger.LogError("exception: {message}", ex.Message);
}
finally
{
// Dispose of the scope
if (scope is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
}