You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义AuthenticationStateProvider注册为Scoped报错,如何正确配置?

问题描述

将自定义AuthenticationStateProvider注册为Scoped服务时:

services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();

触发以下错误:

System.InvalidOperationException: GetAuthenticationStateAsync was called before SetAuthenticationState

注册为Singleton时可正常运行,但Singleton实例会在应用域生命周期内存在,不符合业务需求。请问如何将自定义AuthenticationStateProvider注册为Scoped服务,同时避免上述错误?

编辑补充
以下是CustomAuthenticationStateProvider实现代码:

public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
    private readonly IServiceScopeFactory _scopeFactory;

    public CustomAuthenticationStateProvider(ILoggerFactory loggerFactory, IServiceScopeFactory scopeFactory)
        : base(loggerFactory) =>
        _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));

    protected override TimeSpan RevalidationInterval { get; } = TimeSpan.FromMinutes(30);

    protected override async Task<bool> ValidateAuthenticationStateAsync(
        AuthenticationState authenticationState, CancellationToken cancellationToken)
    {
        // Get the user from a new scope to ensure it fetches fresh data
        var scope = _scopeFactory.CreateScope();
        try
        {
            var userManager = scope.ServiceProvider.GetRequiredService<IUsersService>();
            return await ValidateUserAsync(userManager, authenticationState?.User);
        }
        finally
        {
            if (scope is IAsyncDisposable asyncDisposable)
            {
                await asyncDisposable.DisposeAsync();
            }
            else
            {
                scope.Dispose();
            }
        }
    }

    private async Task<bool> ValidateUserAsync(IUsersService userManager, ClaimsPrincipal? principal)
    {
        if (principal is null)
        {
            return false;
        }

        var userIdString = principal.FindFirst(ClaimTypes.UserData)?.Value;
        if (!int.TryParse(userIdString, out var userId))
        {
            return false;
        }

        var user = await userManager.FindUserAsync(userId);
        return user is not null;
    }
}

程序配置与服务注册代码:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();

    #region Authentication
    //Authentication
    services.AddDbContextFactory<ApplicationDbContext>(options =>
    {
        options.UseSqlServer(
            Configuration.GetConnectionString("LocalDBConnection"),
            serverDbContextOptionsBuilder =>
            {
                var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                serverDbContextOptionsBuilder.CommandTimeout(minutes);
                serverDbContextOptionsBuilder.EnableRetryOnFailure();
            })
            .AddInterceptors(new CorrectCommandInterceptor()); ;
    });
    //add policy
    services.AddAuthorization(options =>
    {
        options.AddPolicy(CustomRoles.Admin, policy => policy.RequireRole(CustomRoles.Admin));
        options.AddPolicy(CustomRoles.User, policy => policy.RequireRole(CustomRoles.User));
    });
    // Needed for cookie auth.
    services
        .AddAuthentication(options =>
        {
            options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        })
        .AddCookie(options =>
        {
            options.SlidingExpiration = false;
            options.LoginPath = "/";
            options.LogoutPath = "/login";
            //options.AccessDeniedPath = new PathString("/Home/Forbidden/");
            options.Cookie.Name = ".my.app1.cookie";
            options.Cookie.HttpOnly = true;
            options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
            options.Cookie.SameSite = SameSiteMode.Lax;
            options.Events = new CookieAuthenticationEvents
            {
                OnValidatePrincipal = context =>
                {
                    var cookieValidatorService = context.HttpContext.RequestServices.GetRequiredService<ICookieValidatorService>();
                    return cookieValidatorService.ValidateAsync(context);
                }
            };
        });
    #endregion

    //AutoMapper
    services.AddAutoMapper(typeof(MappingProfile).Assembly);

    //CustomAuthenticationStateProvider
    services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
    .
    .
}

解决方案

1. 修改自定义认证状态提供者类

重写GetAuthenticationStateAsync方法,确保在初始状态未设置时,先从当前请求的HttpContext获取认证状态并初始化,避免基类抛出异常。同时注入IHttpContextAccessor以获取请求上下文:

public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly IHttpContextAccessor _httpContextAccessor;

    // 更新构造函数,注入IHttpContextAccessor
    public CustomAuthenticationStateProvider(ILoggerFactory loggerFactory, IServiceScopeFactory scopeFactory, IHttpContextAccessor httpContextAccessor)
        : base(loggerFactory)
    {
        _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
        _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
    }

    // 重写GetAuthenticationStateAsync方法,初始化认证状态
    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var httpContext = _httpContextAccessor.HttpContext;
        if (httpContext != null)
        {
            var initialAuthState = new AuthenticationState(httpContext.User);
            SetAuthenticationState(Task.FromResult(initialAuthState));
        }
        return await base.GetAuthenticationStateAsync();
    }

    // 保留原有ValidateAuthenticationStateAsync和ValidateUserAsync方法
    protected override TimeSpan RevalidationInterval { get; } = TimeSpan.FromMinutes(30);

    protected override async Task<bool> ValidateAuthenticationStateAsync(
        AuthenticationState authenticationState, CancellationToken cancellationToken)
    {
        var scope = _scopeFactory.CreateScope();
        try
        {
            var userManager = scope.ServiceProvider.GetRequiredService<IUsersService>();
            return await ValidateUserAsync(userManager, authenticationState?.User);
        }
        finally
        {
            if (scope is IAsyncDisposable asyncDisposable)
            {
                await asyncDisposable.DisposeAsync();
            }
            else
            {
                scope.Dispose();
            }
        }
    }

    private async Task<bool> ValidateUserAsync(IUsersService userManager, ClaimsPrincipal? principal)
    {
        if (principal is null)
        {
            return false;
        }

        var userIdString = principal.FindFirst(ClaimTypes.UserData)?.Value;
        if (!int.TryParse(userIdString, out var userId))
        {
            return false;
        }

        var user = await userManager.FindUserAsync(userId);
        return user is not null;
    }
}

2. 注册IHttpContextAccessor服务

在ConfigureServices中添加IHttpContextAccessor的Scoped注册:

services.AddHttpContextAccessor();

3. 调整服务注册顺序

确保AddServerSideBlazor()之后再注册自定义AuthenticationStateProvider,避免覆盖Blazor默认的Scoped实例处理逻辑:

services.AddServerSideBlazor();
// ...其他服务注册
services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();

原理说明

  • IHttpContextAccessor用于在Scoped实例中获取当前请求的上下文,从而拿到初始的用户认证信息。
  • 重写GetAuthenticationStateAsync后,会先检查并设置初始认证状态,避免基类因未初始化状态而抛出GetAuthenticationStateAsync was called before SetAuthenticationState异常。
  • 保持Scoped生命周期,确保每个用户会话拥有独立的认证状态提供者实例,避免Singleton带来的共享状态风险。

内容的提问来源于stack exchange,提问作者Sadabadi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 02:25:20