自定义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
相关产品推荐
相关产品推荐

