.NET 6自定义声明授权属性:认证与权限跳转异常问题
解决方案:区分未认证与权限不足的自定义跳转逻辑
针对你的.NET 6应用场景,我们可以通过自定义授权结果处理器结合授权处理程序的状态区分,实现未认证跳转到OIDC提供商、权限缺失跳自定义拒绝页的需求,具体步骤如下:
1. 完善自定义授权体系(属性+需求+处理程序)
先补全自定义授权的核心代码,重点是在处理程序中区分「未认证」和「权限不足」两种失败场景:
自定义授权属性
public class PrivilegeAuthorizeAttribute : AuthorizeAttribute { public PrivilegeAuthorizeAttribute(string privilege) { // 用统一格式标记策略,方便后续识别 Policy = $"Privilege:{privilege}"; } }
自定义授权需求
public class PrivilegeRequirement : IAuthorizationRequirement { public string Privilege { get; } public PrivilegeRequirement(string privilege) { Privilege = privilege; } }
授权处理程序(区分失败场景)
public class PrivilegeAuthorizationHandler : AuthorizationHandler<PrivilegeRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PrivilegeRequirement requirement) { // 先判断用户是否已完成认证 if (!context.User.Identity.IsAuthenticated) { context.Fail(); return Task.CompletedTask; } // 检查是否存在目标权限声明 if (context.User.HasClaim(c => c.Type == "privilege" && c.Value == requirement.Privilege)) { context.Succeed(requirement); } else { // 标记为权限不足的失败 context.Fail(new AuthorizationFailureReason(this, "缺失必要权限")); } return Task.CompletedTask; } }
2. 自定义授权结果处理器(核心跳转逻辑)
实现IAuthorizationMiddlewareResultHandler接口,根据授权结果的不同状态,执行对应的跳转逻辑:
public class CustomAuthorizationResultHandler : IAuthorizationMiddlewareResultHandler { // 保留默认处理器处理其他场景 private readonly AuthorizationMiddlewareResultHandler _defaultHandler = new(); public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) { // 未认证:触发OIDC挑战,跳转到外部身份提供商登录 if (authorizeResult.Challenged) { await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme); return; } // 权限不足:重定向到自定义访问拒绝页 if (authorizeResult.Forbidden) { context.Response.Redirect("/AccessDenied"); return; } // 其他授权结果,使用默认处理逻辑 await _defaultHandler.HandleAsync(next, context, policy, authorizeResult); } }
3. 注册服务与配置认证授权
在Program.cs中完成服务注册和认证授权配置,确保自定义处理器生效:
var builder = WebApplication.CreateBuilder(args); // 添加控制器视图服务 builder.Services.AddControllersWithViews(); // 注册自定义授权组件 builder.Services.AddScoped<IAuthorizationHandler, PrivilegeAuthorizationHandler>(); builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, CustomAuthorizationResultHandler>(); // 配置授权:动态识别Privilege策略 builder.Services.AddAuthorization(options => { // 动态策略提供器,自动为每个Privilege创建对应策略 options.AddDynamicPolicyProvider<PrivilegePolicyProvider>(options.DefaultPolicyName); }); // 配置认证体系:Cookie+OIDC builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; // 默认挑战走OIDC }) .AddCookie() .AddOpenIdConnect(options => { // 替换为你的OIDC提供商配置 options.Authority = "你的身份提供商地址"; options.ClientId = "你的ClientID"; options.ClientSecret = "你的ClientSecret"; options.ResponseType = "code"; options.SaveTokens = true; // 映射JWT中的privilege声明到用户Claims options.ClaimActions.MapJsonKey("privilege", "privilege"); options.GetClaimsFromUserInfoEndpoint = true; }); var app = builder.Build(); // 中间件配置 if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); // 默认路由 app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); // 自定义访问拒绝页路由 app.MapGet("/AccessDenied", () => Results.View("/Views/Shared/AccessDenied.cshtml")); app.Run();
4. 创建自定义访问拒绝页
在Views/Shared目录下创建AccessDenied.cshtml,添加自定义拒绝提示内容:
@{ ViewData["Title"] = "访问拒绝"; } <h1>访问拒绝</h1> <p>你没有权限访问此页面,请联系管理员申请权限。</p>
关键说明
- 授权处理程序中,未认证的
context.Fail()会触发Challenged状态,权限不足的context.Fail(AuthorizationFailureReason)会触发Forbidden状态; - 自定义结果处理器通过识别这两种状态,分别执行OIDC挑战和自定义页跳转;
- 认证配置中
DefaultChallengeScheme设为OIDC scheme,确保未认证时自动跳转到外部登录页。
内容的提问来源于stack exchange,提问作者JED
相关产品推荐
相关产品推荐

