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

