动态更改用户声明/身份验证状态 blazor 服务器

问题描述 投票:0回答:1

我正在开发一个应用程序,允许管理员类型用户更改普通用户的访问权限。

我正在使用 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 分钟内注销/除非声明很重要,并且它会立即将用户注销。

代码很混乱,因为我对编码相当陌生,只是想通过自己的方式完成它。

authentication authorization blazor-server-side
1个回答
0
投票

该死。从字面上看,就在发布后,我继续尝试并找到了解决方案!好吧,如果其他人尝试这样做,这里是方法。当然简单。

我使用我刚刚发现的 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();
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.