如何实现Blazor Server应用认证过期自动跳转至登录页
解决方案:Blazor Server认证过期自动跳转至登录页
方案1:组件级监控(最直接可控)
创建独立的AuthMonitor.razor组件嵌入全局布局,利用组件生命周期管理定时器,避免被提前释放:
@inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager NavManager @implements IDisposable @using System.Security.Claims @using YourNamespace.Constants // 替换为你的Constants命名空间 @code { private Timer _authCheckTimer; private readonly TimeSpan _checkInterval = TimeSpan.FromSeconds(2); // 按需调整检查频率 protected override async Task OnInitializedAsync() { _authCheckTimer = new Timer(async _ => await CheckAuthState(), null, TimeSpan.Zero, _checkInterval); await base.OnInitializedAsync(); } private async Task CheckAuthState() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var user = authState.User; if (user.Identity?.IsAuthenticated == true) { var expiryClaim = user.FindFirst(ClaimTypes.Expiration); if (expiryClaim != null && DateTime.TryParse(expiryClaim.Value, out var expiryTime)) { if (DateTime.UtcNow >= expiryTime) { // 确保在Blazor UI线程执行导航,避免跨线程异常 await InvokeAsync(() => { NavManager.NavigateTo(Constants.Routes.LOGIN, true); }); } } } } public void Dispose() { _authCheckTimer?.Dispose(); } }
在MainLayout.razor中引入该组件:
<div class="main"> <div class="content px-4"> @Body </div> </div> <AuthMonitor /> <!-- 全局监控组件 -->
方案2:客户端JS定时器(更稳定)
利用浏览器JS定时器监控过期时间,不受Blazor组件生命周期影响:
- 在
wwwroot/js/auth-utils.js中添加JS逻辑:
window.authUtils = { setExpiryRedirect: function(expirySeconds, loginPath) { setTimeout(() => { window.location.href = loginPath; }, expirySeconds * 1000); } };
- 登录成功后调用JS设置定时器:
// 登录代码片段 DateTime expiry = DateTime.UtcNow.AddSeconds(10); var claims = new List<Claim>() { new(ClaimTypes.Name, Constants.DEFAULT_AUTH_USER), new(ClaimTypes.Role, Constants.DEFAULT_AUTH_ROLE), new(ClaimTypes.Expiration, expiry.ToString("o", CultureInfo.InvariantCulture)) }; var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity)); // 计算剩余有效时间(留1秒缓冲) var remainingSeconds = (int)(expiry - DateTime.UtcNow).TotalSeconds - 1; await JSRuntime.InvokeVoidAsync("window.authUtils.setExpiryRedirect", remainingSeconds, Constants.Routes.LOGIN); NavigationManager.NavigateTo("/", true);
- 在
_Host.cshtml中引入JS文件:
<script src="~/js/auth-utils.js"></script>
方案3:自定义AuthenticationStateProvider触发状态变化
改进CircuitHandler方案,通过自定义认证状态提供者主动推送过期事件:
- 实现自定义认证状态提供者:
using Microsoft.AspNetCore.Components.Server; using Microsoft.AspNetCore.Components.Authorization; using System.Security.Claims; public class ExpiryAuthStateProvider : RevalidatingServerAuthenticationStateProvider { private readonly IServiceScopeFactory _scopeFactory; public ExpiryAuthStateProvider(ILoggerFactory loggerFactory, IServiceScopeFactory scopeFactory) : base(loggerFactory) { _scopeFactory = scopeFactory; } protected override TimeSpan RevalidationInterval => TimeSpan.FromSeconds(2); protected override async Task<bool> ValidateAuthenticationStateAsync( AuthenticationState authenticationState, CancellationToken cancellationToken) { var user = authenticationState.User; if (!user.Identity.IsAuthenticated) return true; var expiryClaim = user.FindFirst(ClaimTypes.Expiration); if (expiryClaim != null && DateTime.TryParse(expiryClaim.Value, out var expiryTime)) { if (DateTime.UtcNow >= expiryTime) { // 通知所有订阅者认证状态变化 NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); return false; } } return true; } }
- 注册服务:
builder.Services.AddScoped<AuthenticationStateProvider, ExpiryAuthStateProvider>();
- 在
MainLayout.razor中监听状态变化并跳转:
@inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager NavManager @implements IDisposable @using YourNamespace.Constants @code { private IDisposable _authSubscription; protected override void OnInitialized() { _authSubscription = AuthStateProvider.AuthenticationStateChanged += HandleAuthStateChanged; base.OnInitialized(); } private async void HandleAuthStateChanged(Task<AuthenticationState> task) { var authState = await task; if (!authState.User.Identity.IsAuthenticated) { await InvokeAsync(() => { NavManager.NavigateTo(Constants.Routes.LOGIN, true); }); } } public void Dispose() { _authSubscription?.Dispose(); } }
内容的提问来源于stack exchange,提问作者peterc
相关产品推荐
相关产品推荐

