ASP.NET Core 8.0 Blazor Server登录后提交操作失效问题求助
我正在开发一个ASP.NET Core 8.0 Blazor Server应用,使用Microsoft Identity Framework Core实现授权,仅在用户登录成功后加载菜单项。在<NavMenu.razor>和<Home.razor>组件中使用了如下授权逻辑:
<AuthorizeView> <Authorized> ........ </Authorized> <NotAuthorized> <Components.Account.Pages.Login> </Components.Account.Pages.Login> </NotAuthorized> </AuthorizeView>
应用启动时会要求登录,登录成功后侧边栏会显示菜单项,但当我在任意页面执行提交操作后,所有菜单项消失,页面再次跳转到登录页。
相关代码
Program.cs
using GetTutorsOnline.Components; using GetTutorsOnline.Components.Account; using GetTutorsOnline.Data; using GTO.IModels.IModelRepos; using GTO.Infrastructure.Data; using GTO.Infrastructure.Repositories; using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents().AddInteractiveServerComponents(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddScoped<IdentityUserAccessor>(); builder.Services.AddScoped<IdentityRedirectManager>(); builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>(); builder.Services.AddAuthentication(options => { options.DefaultScheme = IdentityConstants.ApplicationScheme; options.DefaultSignInScheme = IdentityConstants.ExternalScheme; }) .AddIdentityCookies(); var conStr = builder.Configuration.GetConnectionString("DifriPediaSQLDb"); builder.Services.AddDbContextFactory<GTODbContext>(options => options.UseSqlServer(conStr)); builder.Services.AddDbContext<IdentityContext>(options => options.UseSqlServer(conStr)); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddIdentityCore<GTOAppUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<IdentityContext>() .AddSignInManager() .AddDefaultTokenProviders(); builder.Services.AddSingleton<IEmailSender<GTOAppUser>, IdentityNoOpEmailSender>(); builder.Services.AddScoped<ISubjectNameRepo, SubjectNameRepo>(); builder.Services.AddScoped<IRegionRepo, RegionRepo>(); builder.Services.AddScoped<IAssessmentMonthRepo, AssessmentMonthRepo>(); builder.Services.AddScoped<IELevelRepo, ELevelRepo>(); builder.Services.AddScoped<ITeacherRepo, TeacherRepo>(); builder.Services.AddScoped<ICoordinatorRepo, CoordinatorRepo>(); builder.Services.AddScoped<IManagerRepo, ManagerRepo>(); builder.Services.AddScoped<IStudentRepo, StudentRepo>(); builder.Services.AddScoped<IParentRepo, ParentRepo>(); builder.Services.AddScoped<IEvaluationTweekRepo, EvaluationTweekRepo>(); builder.Services.AddScoped<ISubjectAllocationRepo, SubjectAllocationRepo>(); builder.Services.AddScoped<IDayNameRepo, DayNameRepo>(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); //app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(); app.MapAdditionalIdentityEndpoints(); app.Run();
IdentityRevalidatingAuthenticationStateProvider.cs
using GetTutorsOnline.Data; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Server; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using System.Security.Claims; namespace GetTutorsOnline.Components.Account { // This is a server-side AuthenticationStateProvider that revalidates the security stamp for the connected user // every 30 minutes an interactive circuit is connected. internal sealed class IdentityRevalidatingAuthenticationStateProvider( ILoggerFactory loggerFactory, IServiceScopeFactory scopeFactory, IOptions<IdentityOptions> options) : RevalidatingServerAuthenticationStateProvider(loggerFactory) { protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30); protected override async Task<bool> ValidateAuthenticationStateAsync( AuthenticationState authenticationState, CancellationToken cancellationToken) { // Get the user manager from a new scope to ensure it fetches fresh data await using var scope = scopeFactory.CreateAsyncScope(); var userManager = scope.ServiceProvider.GetRequiredService<UserManager<GTOAppUser>>(); return await ValidateSecurityStampAsync(userManager, authenticationState.User); } private async Task<bool> ValidateSecurityStampAsync(UserManager<GTOAppUser> userManager, ClaimsPrincipal principal) { var user = await userManager.GetUserAsync(principal); if (user is null) { return false; } else if (!userManager.SupportsUserSecurityStamp) { return true; } else { var principalStamp = principal.FindFirstValue(options.Value.ClaimsIdentity.SecurityStampClaimType); var userStamp = await userManager.GetSecurityStampAsync(user); return principalStamp == userStamp; } } } }
排查与解决方案
1. 修正NotAuthorized逻辑:使用重定向而非直接嵌入Login组件
直接在NotAuthorized块中嵌入<Components.Account.Pages.Login>组件会导致登录上下文异常,正确做法是使用模板自带的RedirectToLogin组件导航到登录页:
<AuthorizeView> <Authorized> <!-- 授权后内容 --> </Authorized> <NotAuthorized> <RedirectToLogin /> </NotAuthorized> </AuthorizeView>
2. 调整中间件顺序,确保Antiforgery配置正确
Blazor Server的中间件顺序需严格遵循规范,将UseAntiforgery移到UseAuthentication和UseAuthorization之后,修改Program.cs中的中间件部分:
app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAuthentication(); app.UseAuthorization(); app.UseAntiforgery(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(); app.MapAdditionalIdentityEndpoints();
3. 检查提交操作是否修改了SecurityStamp并更新会话
如果提交操作中更新了用户实体(如调用userManager.UpdateAsync(user)),Identity会自动更新用户的SecurityStamp,此时需同步刷新用户的认证会话,避免验证失败:
// 更新用户后调用以下代码 await _signInManager.RefreshSignInAsync(user);
4. 确保表单提交使用Blazor EditForm而非原生HTML Form
如果使用原生HTML <form>标签提交数据,需手动添加Antiforgery令牌,否则会触发验证失败导致会话失效:
@inject IAntiforgery Antiforgery <form method="post" action="/your-action"> @Antiforgery.GetHtml() <!-- 表单内容 --> </form>
推荐优先使用Blazor的EditForm组件,它会自动处理Antiforgery令牌,无需手动配置。
5. 验证SecurityStamp验证逻辑
在IdentityRevalidatingAuthenticationStateProvider中,若用户存在但SecurityStamp不匹配,会返回false触发授权失效。可添加日志排查是否是Stamp不匹配导致:
private async Task<bool> ValidateSecurityStampAsync(UserManager<GTOAppUser> userManager, ClaimsPrincipal principal) { var user = await userManager.GetUserAsync(principal); if (user is null) { return false; } else if (!userManager.SupportsUserSecurityStamp) { return true; } else { var principalStamp = principal.FindFirstValue(options.Value.ClaimsIdentity.SecurityStampClaimType); var userStamp = await userManager.GetSecurityStampAsync(user); // 添加日志排查 if (principalStamp != userStamp) { var logger = scope.ServiceProvider.GetRequiredService<ILogger<IdentityRevalidatingAuthenticationStateProvider>>(); logger.LogWarning("SecurityStamp mismatch for user {UserId}", user.Id); } return principalStamp == userStamp; } }
内容的提问来源于stack exchange,提问作者Fayyaz Ahmed

