Blazor WebAssembly .NET7双认证方案下组件授权失效问题求助
问题背景
我在使用Blazor WebAssembly .NET7时,尝试通过Secure认证方案保护Blazor页面:
- 直接使用
@attribute [Authorize(AuthenticationSchemes = CustomAuthenticationSchemesNames.Secure)]时,抛出错误:NotSupportedException: The authorization data specifies an authentication scheme with value 'Secure'. Authentication schemes cannot be specified for components. Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.EnsureNoAuthenticationSchemeSpecified(IAuthorizeData[] authorizeData).
- 改用基于
SecureScheme的SecurePolicy策略后,Razor页面可以正常生效,但Blazor组件仍无法正确限制仅通过默认Cookie Scheme认证的用户访问。
期望的授权流程:
- 用户仅通过默认Cookie Scheme认证时,无法访问目标Blazor组件
- 用户通过
SecureScheme认证时,可以访问目标Blazor组件 - 用户同时通过两种Scheme认证时,也可以访问目标Blazor组件
原因分析
Blazor的默认AuthenticationStateProvider仅会使用**默认认证方案(DefaultAuthenticateScheme)**获取用户身份,不会自动处理多认证方案的验证逻辑。而Razor Page在服务器端处理授权时,会根据策略中指定的Scheme直接验证对应的认证状态,因此可以正常工作。
解决方案
步骤1:添加HttpContextAccessor服务
在Program.cs中注册IHttpContextAccessor,用于在授权逻辑中访问当前HttpContext:
builder.Services.AddHttpContextAccessor();
步骤2:创建自定义授权要求和处理程序
创建专门验证Secure Scheme认证状态的授权逻辑:
// 自定义授权要求 public class SecureAuthenticationRequirement : IAuthorizationRequirement { } // 授权处理程序 public class SecureAuthenticationHandler : AuthorizationHandler<SecureAuthenticationRequirement> { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IAuthenticationService _authenticationService; public SecureAuthenticationHandler(IHttpContextAccessor httpContextAccessor, IAuthenticationService authenticationService) { _httpContextAccessor = httpContextAccessor; _authenticationService = authenticationService; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, SecureAuthenticationRequirement requirement) { var httpContext = _httpContextAccessor.HttpContext; if (httpContext == null) { context.Fail(); return; } // 直接验证Secure Scheme的认证状态 var secureAuthResult = await _authenticationService.AuthenticateAsync(httpContext, CustomAuthenticationSchemesNames.Secure); if (secureAuthResult.Succeeded) { context.Succeed(requirement); } else { context.Fail(); } } }
步骤3:更新授权策略并注册处理程序
在Program.cs中调整授权策略,替换为自定义要求,并注册处理程序:
builder.Services.AddAuthorization(options => { options.AddPolicy("SecurePolicy", policy => { // 使用自定义授权要求替代原Scheme绑定的策略 policy.Requirements.Add(new SecureAuthenticationRequirement()); }); }); // 注册自定义授权处理程序 builder.Services.AddScoped<IAuthorizationHandler, SecureAuthenticationHandler>();
(可选)自定义AuthenticationStateProvider(如需在组件中显示Secure认证状态)
如果需要在Blazor组件中获取用户是否通过Secure Scheme认证的信息,可以自定义AuthenticationStateProvider,合并多个Scheme的身份:
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Server; using System.Security.Claims; public class CustomAuthenticationStateProvider : ServerAuthenticationStateProvider { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IAuthenticationService _authenticationService; public CustomAuthenticationStateProvider(IHttpContextAccessor httpContextAccessor, IAuthenticationService authenticationService) { _httpContextAccessor = httpContextAccessor; _authenticationService = authenticationService; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { var httpContext = _httpContextAccessor.HttpContext; if (httpContext == null) { return new AuthenticationState(new ClaimsPrincipal()); } // 分别验证两个Scheme的认证状态 var defaultAuthResult = await _authenticationService.AuthenticateAsync(httpContext, CookieAuthenticationDefaults.AuthenticationScheme); var secureAuthResult = await _authenticationService.AuthenticateAsync(httpContext, CustomAuthenticationSchemesNames.Secure); ClaimsPrincipal user; // 存在Secure认证时,合并两个Scheme的Claims if (secureAuthResult.Succeeded) { var combinedIdentity = new ClaimsIdentity(); combinedIdentity.AddClaims(defaultAuthResult.Principal?.Claims ?? Enumerable.Empty<Claim>()); combinedIdentity.AddClaims(secureAuthResult.Principal.Claims); user = new ClaimsPrincipal(combinedIdentity); } else { // 仅使用默认Scheme的身份 user = defaultAuthResult.Principal ?? new ClaimsPrincipal(); } return new AuthenticationState(user); } }
然后在Program.cs中替换默认的Provider:
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
验证效果
- 用户仅登录默认Cookie Scheme:访问Blazor组件时,自定义授权处理程序会验证
SecureScheme认证失败,拦截访问并跳转到SecureScheme的登录页。 - 用户登录
SecureScheme:授权处理程序验证通过,允许访问。 - 用户同时登录两个Scheme:授权处理程序验证通过,允许访问。
内容的提问来源于stack exchange,提问作者Alex

