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

Blazor项目中RedirectToLogin组件引发无限重定向循环问题

问题描述

目标

未授权访问时自动路由到登录页面,技术栈为 MudBlazor WebApp + ASP.NET + Entity Framework。

当前问题

浏览器持续提示:

The page isn’t redirecting properly

通过断点排查发现,RedirectToLogin.razor 的 OnInitializedAsync 方法触发了无限循环。

已尝试的方案

  • 直接在 <NotAuthorized> 中渲染 <LoginPage/>:地址栏不会显示登录页的 URI
  • 用 LoginLayout 包裹 RedirectToLogin:同样触发无限循环
  • 使用 @attribute [Authorize]:会固定重定向到 Account/Login?ReturnUrl=%2F
  • 调整 LoginPage 路由为 @page "/Account/Login" 并配合特定 AuthorizeView 代码:虽能运行但无法自定义登录路由

现有代码

Routes.razor

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeView>
                <Authorized>
                    <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
                </Authorized>
                <NotAuthorized>
                    <RedirectToLogin/>
                </NotAuthorized>
            </AuthorizeView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

MainLayout.razor

@inherits LayoutComponentBase

<MudLayout>
    <MudAppBar Elevation="1">
        <MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@(e => DrawerToggle())" />
        <MudSpacer />
        <MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Edge="Edge.End" />
    </MudAppBar>
    <MudDrawer @bind-Open="_drawerOpen" Elevation="2">
        <MudDrawerHeader>
            <MudText Typo="Typo.h5" Class="mt-1">MyApp</MudText>
        </MudDrawerHeader>
        <NavMenu/>
    </MudDrawer>
    <MudMainContent>
        @Body
    </MudMainContent>
</MudLayout>

@code {
    bool _drawerOpen = true;

    void DrawerToggle()
    {
        _drawerOpen = !_drawerOpen;
    }
}

RedirectToLogin.razor

@inject NavigationManager NavigationManager

@code {
    protected override async Task OnInitializedAsync()
    {
        NavigationManager.NavigateTo("Login", forceLoad: true);
    }
}

LoginLayout.razor

<MudLayout>
    <p>LoginLayout</p>
    <MudMainContent>
        @Body
    </MudMainContent>
</MudLayout>

@code {
}

LoginPage.razor

@page "/Login"

<h3>LoginPage</h3>

@code {

}

App.razor

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />

    <link rel="stylesheet" href="CompanyKnowledgeAIClient.styles.css" />
    <link rel="icon" type="image/png" href="favicon.ico" />

    @* Mudblazor - Add font and style references *@
    <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
    <link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />

    <HeadOutlet />
</head>

<body>
    <Routes />
    @* Mudblazor - Add script reference *@
    <script src="_framework/blazor.web.js"></script>
    <script src="_content/MudBlazor/MudBlazor.min.js"></script>
</body>

</html>

Program.cs

var builder = WebApplication.CreateBuilder(args);

// Add DbContexts
builder.Services.AddDbContext<PostgresDbContext>();
builder.Services.AddDbContext<MySqlDbContext>();

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

// MudBlazor Services
builder.Services.AddMudServices();

builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();

// Detailed error output on circuit
builder.Services.AddServerSideBlazor().AddCircuitOptions(options => { options.DetailedErrors = true; });

// Authentication
builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = IdentityConstants.ApplicationScheme;
        options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
    })
    .AddIdentityCookies();

// Identity
builder.Services.AddIdentityCore<IdentityUser>()
    .AddEntityFrameworkStores<MySqlDbContext>()
    .AddSignInManager()
    .AddDefaultTokenProviders();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

解决方案

无限循环的核心原因是:登录页本身也会触发 <AuthorizeView> 的 <NotAuthorized> 分支,导致再次重定向到 Login,形成闭环。

1. 让 LoginPage 跳过授权验证

在 LoginPage.razor 顶部添加 @attribute [AllowAnonymous],确保未授权用户可以直接访问登录页:

@page "/Login"
@attribute [AllowAnonymous]

<h3>LoginPage</h3>

@code {

}

2. 优化 RedirectToLogin 组件

修改 RedirectToLogin.razor,增加返回 URL 参数,同时避免重复重定向:

@inject NavigationManager NavigationManager
@inject AuthenticationStateProvider AuthenticationStateProvider

@code {
    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
        // 仅当用户未授权且当前页面不是登录页时,才执行重定向
        if (!authState.User.Identity.IsAuthenticated 
            && !NavigationManager.Uri.EndsWith("/Login", StringComparison.OrdinalIgnoreCase))
        {
            var returnUrl = Uri.EscapeDataString(NavigationManager.Uri);
            NavigationManager.NavigateTo($"Login?returnUrl={returnUrl}", forceLoad: false);
        }
    }
}

3. 调整 Routes.razor 逻辑(可选)

保留现有结构即可,因为 LoginPage 已添加 [AllowAnonymous],访问登录页时不会进入 <NotAuthorized> 分支,自然打破循环。

4. 自定义登录路由(可选)

如果需要使用自定义路由(如 /SignIn),只需修改 LoginPage 的 @page 指令,并同步更新 RedirectToLogin 中的导航地址,无需受限于 Account/Login。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 13:27:34