Blazor .NET 8 InteractiveServer模式下本地AD认证异常问题
Blazor InteractiveServer模式下AD认证用户信息二次渲染丢失问题
问题现象
开发基于本地AD(Active Directory)认证的Blazor应用,启用InteractiveServer渲染模式时出现异常:
- 页面先执行服务器预渲染,再进入客户端交互渲染
- 第一次触发
OnInitializedAsync()时能正确获取用户AD信息与角色权限 - 第二次触发方法时用户身份变为未认证状态,导致权限控制的内容被隐藏
- 使用普通用户账号模板测试功能正常,推测客户端未正确同步用户认证状态
已尝试操作
在App.Razor和页面分别设置InteractiveServer渲染模式,问题未解决
相关代码
Program.cs 核心配置
var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddServerSideBlazor(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddTransient<IPersonSvc, PersonSvc>(); builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) .AddNegotiate(options => { options.Events = new NegotiateEvents { OnAuthenticated = context => { var windowsIdentity = context.Principal.Identity as WindowsIdentity; var adGroups = windowsIdentity?.Groups.Translate(typeof(NTAccount)); if (adGroups != null) { foreach (var group in adGroups) { context.Principal.Identities.FirstOrDefault().AddClaim(new Claim(ClaimTypes.Role, group.Value)); } } return Task.CompletedTask; }, }; }).AddIdentityCookies(); builder.Services.Configure<AuthenticationOptions>(options => { options.DefaultScheme = NegotiateDefaults.AuthenticationScheme; }); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("No connection string in config."); builder.Services.AddDbContextFactory<ApplicationDbContext>((DbContextOptionsBuilder options) => options.UseSqlServer(connectionString)); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddRazorComponents(options => options.DetailedErrors = true); builder.Services.AddTelerikBlazor(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseMigrationsEndPoint(); } else { app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseHsts(); } app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(); app.Run();
页面代码(/PhoneDirectory)
@inject AuthenticationStateProvider AuthenticationStateProvider @inject IPersonSvc PersonService @attribute [Authorize] @rendermode InteractiveServer @page "/PhoneDirectory" Page Content here @code { [CascadingParameter] private Task<AuthenticationState>? authenticationState { get; set; } [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; List<Person>? GridData; private bool ViewAdditionalInfo { get; set; } private bool isInitialized = false; private ClaimsPrincipal user; protected override async Task OnInitializedAsync() { if (!isInitialized) { var authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); user = authenticationState.User; // 第一次有用户信息,第二次未认证且isInitialized重置为false await LoadData(); isInitialized = true; } await base.OnInitializedAsync(); } }
App.Razor 代码
<head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="/" /> <script src="_content/Telerik.UI.for.Blazor/js/telerik-blazor.js" defer></script> <link href="bootstrap/css/bootstrap.min.css" rel="stylesheet" /> <link href="/bootstrap-icons/font/bootstrap-icons.min.css" rel="stylesheet" /> <link rel="stylesheet" href="_content/Telerik.UI.for.Blazor/css/kendo-theme-default/all.css" /> <link rel="stylesheet" href="css/app.css" /> <link rel="stylesheet" href="InternalSite.styles.css" /> <link rel="icon" type="image/png" href="favicon.png" /> <HeadOutlet /> </head> <body> <Routes /> <script src="_framework/blazor.web.js"></script> </body>
问题根源与解决方案
1. 问题根源
InteractiveServer模式的双渲染机制导致:
- 预渲染阶段:HttpContext可用,能获取AD认证信息,但组件状态不会自动传递到交互阶段
- 交互阶段:SignalR连接建立后,原预渲染的组件状态重置,且直接调用
AuthenticationStateProvider.GetAuthenticationStateAsync()无法获取AD认证上下文 - 页面中同时使用
AuthenticationStateProvider注入和级联AuthenticationState,逻辑混乱;HttpContext仅在预渲染阶段可用,交互阶段为空
2. 分步解决方案
方案一:禁用预渲染(快速临时解决)
直接关闭预渲染,确保仅进入交互渲染阶段:
- 页面级修改:将
@rendermode InteractiveServer改为@rendermode InteractiveServer(prerender: false) - 全局修改:在Program.cs中更新映射配置:
此方法简单,但会损失预渲染的首屏加载速度优势。app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(options => options.DisablePrerendering = true);
方案二:正确传递认证状态到交互阶段(推荐)
步骤1:配置Program.cs确保认证状态传递
在AddInteractiveServerComponents()后添加SignalR连接的认证传递配置:
builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddHubOptions(options => { options.MaximumReceiveMessageSize = 1024 * 1024 * 10; });
确保AddCascadingAuthenticationState()已正确注册,它负责在组件树中传递认证状态。
步骤2:优化页面代码,使用级联认证状态
移除AuthenticationStateProvider注入和HttpContext级联参数,统一使用级联的AuthenticationStateTask:
@inject IPersonSvc PersonService @attribute [Authorize] @rendermode InteractiveServer @page "/PhoneDirectory" Page Content here @code { [CascadingParameter] private Task<AuthenticationState> AuthenticationStateTask { get; set; } = default!; List<Person>? GridData; private bool ViewAdditionalInfo { get; set; } private bool isInitialized = false; private ClaimsPrincipal user; protected override async Task OnInitializedAsync() { if (!isInitialized) { var authState = await AuthenticationStateTask; user = authState.User; if (user.Identity?.IsAuthenticated == true) { await LoadData(); } isInitialized = true; } await base.OnInitializedAsync(); } }
步骤3:更新App.Razor,添加级联认证状态包裹
将Routes组件用CascadingAuthenticationState包裹,确保认证状态传递到所有子组件:
<head> <!-- 原有head内容不变 --> </head> <body> <CascadingAuthenticationState> <Routes /> </CascadingAuthenticationState> <script src="_framework/blazor.web.js"></script> </body>
3. 额外注意事项
- 避免在交互阶段依赖
HttpContext,所有认证操作通过AuthenticationState处理 - 测试前清除浏览器缓存,确保认证Cookie正确传递到SignalR连接
- 移除页面中重复的认证相关注入,保持逻辑统一
内容的提问来源于stack exchange,提问作者Ryan O
相关产品推荐
相关产品推荐

