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

ASP.NET Core 8.0 Blazor Server登录后提交操作失效问题求助

问题: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 13:35:58