BlazorServer中AuthenticationStateProvider与AuthorizeAttribute异常
Blazor Server 自定义AuthenticationStateProvider认证授权问题
初始问题
在Blazor Server应用(非WebAssembly)中通过AuthenticationStateProvider实现认证授权,完成大部分功能后遇到两个核心问题:
- 页面刷新后授权完全丢失,必须重新登录;
Authorize Attribute机制失效,登录后访问带有@attribute [Authorize(Roles = "SuperAdmin")]的页面仍被重定向至登录页。
应用使用Blazorise组件,部分页面需启用交互模式。
初始版本代码
Program.cs
using Blazored.Toast; using Blazorise; using Blazorise.Bootstrap5; using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Components.Authorization; // 这是Blazor Server应用(非WebAssembly应用) var builder = WebApplication.CreateBuilder(args); // 添加服务默认配置及Aspire组件 builder.AddServiceDefaults(); // 向容器添加服务 builder.Services.AddAuthorization(); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.Name = "auth_cookie"; options.LoginPath = "/"; options.LogoutPath = "/logout"; options.Cookie.MaxAge = TimeSpan.FromMinutes(90); options.AccessDeniedPath = "/accessdenied"; }); builder.Services.ConfigureApplicationCookie(ops => { ops.ExpireTimeSpan = TimeSpan.FromMinutes(30); ops.SlidingExpiration = true; }); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddRazorComponents().AddInteractiveServerComponents(); builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>(); builder.Services.AddOutputCache(); builder.Services.AddSingleton<GlobalUserInfo>(); builder.Services.AddBlazoredToast(); builder.Services .AddBlazorise(options => { options.Immediate = true; }) .AddBootstrap5Providers() .AddFontAwesomeIcons(); builder.Services.ConfigureApplicationCookie(ops => { ops.ExpireTimeSpan = TimeSpan.FromMinutes(30); ops.SlidingExpiration = true; }); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); // 默认HSTS值为30天,生产环境可根据需求修改 app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); app.UseOutputCache(); app.MapRazorComponents<App>().AddInteractiveServerRenderMode(); app.MapDefaultEndpoints(); app.Run();
Login.razor
@code { async void Authenticate() { var result = await LoginApi.Login(login); ((CustomAuthStateProvider)AuthenticationStateProvider) .AuthenticateUser(user.UserName, user.Role); navigationManager.NavigateTo("/home"); } }
MyAttribute.razor
@attribute [Authorize(Roles = "SuperAdmin")]
更新后的状态
经过调试后,初始问题已解决:Authorize Attribute生效,页面刷新后授权保留,但出现新问题:同一浏览器仅支持单用户登录(跨浏览器多用户情况未验证)。
核心实现方式为拦截授权请求并与已登录用户对比,但CustomAuthStateProvider注册为单例导致多用户支持失效,改用Scoped模式也未解决该问题。
更新后代码
CustomAuthStateProvider.cs
using System.Security.Claims; using Microsoft.AspNetCore.Components.Authorization; namespace MyApp.Web.Utilities { public class CustomAuthStateProvider : AuthenticationStateProvider { private ClaimsIdentity _identity = new(); public override Task<AuthenticationState> GetAuthenticationStateAsync() { var user = new ClaimsPrincipal(_identity); return Task.FromResult(new AuthenticationState(user)); } public void AuthenticateUser(string userIdentifier, string role) { _identity = new ClaimsIdentity( [new Claim(ClaimTypes.Name, userIdentifier), new Claim(ClaimTypes.Role, role),], "Custom Authentication"); var user = new ClaimsPrincipal(_identity); NotifyAuthenticationStateChanged( Task.FromResult(new AuthenticationState(user))); } public async Task LogUserOut() { _identity = new ClaimsIdentity(); var user = new ClaimsPrincipal(_identity); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user))); } } }
CustomAuthorizationHandler.cs
using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Components.Authorization; namespace MyApp.Web.Utilities { public class CustomAuthorizationHandler : IAuthorizationMiddlewareResultHandler { private readonly AuthenticationStateProvider _authStateProvider; public CustomAuthorizationHandler(AuthenticationStateProvider authStateProvider) { _authStateProvider = authStateProvider; } public Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) { var result = _authStateProvider.GetAuthenticationStateAsync(); context.User = result.Result.User; return next(context); } } }
Program.cs(更新版)
using Blazored.Toast; using Blazorise; using Blazorise.Bootstrap5; using Blazorise.Icons.FontAwesome; using MyApp.Web.APIClients; using MyApp.Web.Components; using MyApp.Web.Utilities; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Components.Authorization; // 这是Blazor Server应用(非WebAssembly应用) var builder = WebApplication.CreateBuilder(args); // 添加服务默认配置及Aspire组件 builder.AddServiceDefaults(); // 向容器添加服务 builder.Services.AddAuthorization(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddRazorComponents().AddInteractiveServerComponents(); builder.Services.AddAuthorizationCore(); builder.Services.AddSingleton<AuthenticationStateProvider, CustomAuthStateProvider>(); // 处理认证逻辑 builder.Services.AddScoped<IAuthorizationMiddlewareResultHandler, CustomAuthorizationHandler>(); builder.Services.AddOutputCache(); builder.Services.AddBlazoredToast(); builder.Services .AddBlazorise(options => { options.Immediate = true; }) .AddBootstrap5Providers() .AddFontAwesomeIcons(); builder.Services.ConfigureApplicationCookie(ops => { ops.ExpireTimeSpan = TimeSpan.FromMinutes(30); ops.SlidingExpiration = true; }); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); // 默认HSTS值为30天,生产环境可根据需求修改 app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); app.UseOutputCache(); app.MapRazorComponents<App>().AddInteractiveServerRenderMode(); app.MapDefaultEndpoints(); app.Run();
内容的提问来源于stack exchange,提问作者Age of Empires
相关产品推荐
相关产品推荐

