You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 13:55:11