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

.NET 6/7中如何动态返回401/403自定义认证响应?

.NET 6/7 动态返回401/403自定义响应实现方案

问题背景

在.NET 6/7开发中,已基于JWT实现401自定义响应,但需要根据实际错误场景动态返回401(未授权)和403(禁止访问)状态码,而非硬编码固定值。控制器方法使用[Authorize(Permissions.Master.Read)]属性做权限控制,需实现该动态响应需求。

现有JWT认证配置代码:

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>{
    o.RequireHttpsMetadata = true;
    o.SaveToken = true;
    o.TokenValidationParameters = new TokenValidationParameters
    {  
        ValidateIssuerSigningKey = true,
        // ...其他配置
        IssuerSigningKey = new SymmetricSecurityKey(key)
    };                    
});

当前自定义401的事件处理代码:

o.Events = new JwtBearerEvents
{
    OnChallenge = async context =>
    {
        // 跳过默认响应逻辑
        var s = context.HttpContext.Response.StatusCode;
        // ***这里获取到的是200状态码***
        context.HandleResponse();

        var httpContext = context.HttpContext;
        var statusCode = StatusCodes.Status401Unauthorized;

        var routeData = httpContext.GetRouteData();
        var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());

        var factory = httpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
        var problemDetails = factory.CreateProblemDetails(httpContext, statusCode);

        var result = new ObjectResult(problemDetails) { StatusCode = statusCode };
        await result.ExecuteResultAsync(actionContext);
    }
};

实现方案

401对应认证失败(无token、token无效/过期),403对应授权失败(token有效但无对应权限),需分别处理两种场景:

1. 优化认证挑战事件,支持动态状态码

修改JwtBearerEvents的OnChallenge方法,通过上下文信息区分认证/授权失败场景:

o.Events = new JwtBearerEvents
{
    OnChallenge = async context =>
    {
        context.HandleResponse();
        var httpContext = context.HttpContext;

        int statusCode;
        string title;
        string detail;

        // 认证失败(无有效凭证)返回401
        if (context.AuthenticateFailure != null)
        {
            statusCode = StatusCodes.Status401Unauthorized;
            title = "未授权";
            detail = "请提供有效的身份凭证";
        }
        // 授权失败(无对应权限)返回403
        else if (httpContext.Items.ContainsKey("AuthorizationFailure"))
        {
            statusCode = StatusCodes.Status403Forbidden;
            title = "禁止访问";
            detail = "您无此操作权限";
        }
        // 默认 fallback
        else
        {
            statusCode = StatusCodes.Status401Unauthorized;
            title = "访问被拒绝";
            detail = "身份验证未通过";
        }

        var routeData = httpContext.GetRouteData();
        var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());

        var factory = httpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
        var problemDetails = factory.CreateProblemDetails(
            httpContext,
            statusCode,
            title,
            detail: detail
        );

        var result = new ObjectResult(problemDetails) { StatusCode = statusCode };
        await result.ExecuteResultAsync(actionContext);
    },
    // 可选:捕获token验证失败日志
    OnAuthenticationFailed = context =>
    {
        // 可添加日志记录逻辑
        return Task.CompletedTask;
    }
};

2. 自定义授权处理,标记授权失败状态

实现IAuthorizationMiddlewareResultHandler,在授权失败时向上下文添加标记,传递给认证挑战事件:

步骤1:定义自定义授权结果处理器

public class CustomAuthorizationResultHandler : IAuthorizationMiddlewareResultHandler
{
    private readonly AuthorizationMiddlewareResultHandler _defaultHandler = new();

    public async Task HandleAsync(
        RequestDelegate next,
        HttpContext context,
        AuthorizationPolicy policy,
        PolicyAuthorizationResult authorizeResult)
    {
        if (authorizeResult.Forbidden)
        {
            // 标记授权失败
            context.Items["AuthorizationFailure"] = true;
            // 触发认证挑战,进入OnChallenge逻辑
            await context.ChallengeAsync(JwtBearerDefaults.AuthenticationScheme);
            return;
        }

        // 其他场景使用默认处理逻辑
        await _defaultHandler.HandleAsync(next, context, policy, authorizeResult);
    }
}

步骤2:注册授权策略与处理器

在Program.cs中添加权限策略,并注册自定义处理器:

// 注册权限授权策略
services.AddAuthorization(options =>
{
    options.AddPolicy("Permissions", policy =>
        policy.RequireAssertion(context =>
        {
            // 从用户Claims中获取已拥有的权限
            var userPermissions = context.User.Claims
                .Where(c => c.Type == "Permission")
                .Select(c => c.Value)
                .ToList();

            // 获取当前请求要求的权限
            var filterContext = context.Resource as AuthorizationFilterContext;
            var authorizeAttr = filterContext?.ActionDescriptor.EndpointMetadata
                .OfType<AuthorizeAttribute>()
                .FirstOrDefault();

            if (authorizeAttr != null && !string.IsNullOrEmpty(authorizeAttr.Policy))
            {
                // 匹配用户权限与要求的权限
                return userPermissions.Contains(authorizeAttr.Policy);
            }
            return false;
        }));
});

// 注册自定义授权结果处理器
services.AddSingleton<IAuthorizationMiddlewareResultHandler, CustomAuthorizationResultHandler>();

3. 控制器中使用权限授权属性

确保控制器方法的Authorize属性指定对应策略:

[Authorize(Policy = Permissions.Master.Read)]
public IActionResult GetMasterData()
{
    // 业务逻辑实现
    return Ok();
}

效果验证

  • 无token、token过期/无效时,触发认证失败,返回401自定义响应
  • token有效但用户无对应权限时,触发授权失败,返回403自定义响应

内容的提问来源于stack exchange,提问作者Lajith

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 05:36:00