ASP.NET Core 控制器/动作级过滤器未授权时返回JSON响应的实现方案
解决方案:ASP.NET Core 混合认证下控制器级自定义未授权JSON响应
实现思路
你之前尝试的全局Cookie事件、Handler重写逻辑并不是真的只能全局生效,只需要在逻辑中增加端点元数据判断,就可以仅针对标记了指定特性的控制器/动作走自定义JSON响应逻辑,其余场景保持原有跳转/原生401逻辑不变,不需要新增混合认证方案,也不需要修改现有认证校验逻辑。
具体实现步骤
1. 定义标记特性
先定义一个空的特性类,用于标记需要返回自定义未授权JSON的控制器或动作,同时支持自定义错误信息:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] public class ReturnUnauthorizedJsonAttribute : Attribute { /// <summary> /// 自定义错误提示 /// </summary> public string Message { get; set; } = "身份认证失败,请提供有效的认证信息"; /// <summary> /// 自定义业务错误码 /// </summary> public int ErrorCode { get; set; } = 40100; }
2. 修改Cookie认证事件分支逻辑
修改原有AddCookie的配置,在跳转事件中增加端点特性判断,有标记的返回JSON,无标记的走原有跳转逻辑:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { // 原有配置保留不变 options.Cookie.Name = "_auth"; options.Cookie.HttpOnly = true; options.LoginPath = new PathString("/Account/Login"); options.LogoutPath = new PathString("/Account/LogOff"); options.AccessDeniedPath = new PathString("/Account/Login"); options.ExpireTimeSpan = TimeSpan.FromHours(4); options.SlidingExpiration = true; // 新增跳转逻辑分支判断 options.Events.OnRedirectToLogin = context => { // 获取当前请求的端点元数据 var endpoint = context.HttpContext.GetEndpoint(); var jsonAttr = endpoint?.Metadata.GetMetadata<ReturnUnauthorizedJsonAttribute>(); if (jsonAttr != null) { // 有标记则返回JSON context.Response.StatusCode = StatusCodes.Status401Unauthorized; context.Response.ContentType = "application/json;charset=utf-8"; return context.Response.WriteAsJsonAsync(new { code = jsonAttr.ErrorCode, msg = jsonAttr.Message }); } // 无标记走原有跳转逻辑 context.Response.Redirect(context.RedirectUri); return Task.CompletedTask; }; options.Events.OnRedirectToAccessDenied = context => { var endpoint = context.HttpContext.GetEndpoint(); var jsonAttr = endpoint?.Metadata.GetMetadata<ReturnUnauthorizedJsonAttribute>(); if (jsonAttr != null) { context.Response.StatusCode = StatusCodes.Status403Forbidden; context.Response.ContentType = "application/json;charset=utf-8"; return context.Response.WriteAsJsonAsync(new { code = 40300, msg = "权限不足,无法访问当前资源" }); } context.Response.Redirect(context.RedirectUri); return Task.CompletedTask; }; }) .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
3. 修改Basic认证Handler的挑战逻辑
在你自定义的BasicAuthenticationHandler中重写HandleChallengeAsync方法,同样增加端点特性判断:
protected override Task HandleChallengeAsync(AuthenticationProperties properties) { var endpoint = Context.GetEndpoint(); var jsonAttr = endpoint?.Metadata.GetMetadata<ReturnUnauthorizedJsonAttribute>(); if (jsonAttr != null) { Response.StatusCode = StatusCodes.Status401Unauthorized; Response.ContentType = "application/json;charset=utf-8"; return Response.WriteAsJsonAsync(new { code = jsonAttr.ErrorCode, msg = jsonAttr.Message }); } // 无标记走原有Basic认证挑战逻辑,返回WWW-Authenticate响应头 Response.Headers.WWWAuthenticate = "Basic realm=\"你的站点标识\""; return base.HandleChallengeAsync(properties); }
使用方法
直接在需要支持双认证+自定义JSON响应的控制器/动作上叠加标记即可,动作上的标记会覆盖类上的全局配置:
// 控制器级别标记,所有动作生效 [ApiController] [Route("api/[controller]")] [Authorize(AuthenticationSchemes = "BasicAuthentication,Cookies")] [ReturnUnauthorizedJson(Message = "API接口需要身份认证", ErrorCode = 40101)] public class OrderApiController : ControllerBase { [HttpGet("list")] public IActionResult GetList() { return Ok(); } // 动作级别标记,覆盖类的配置 [HttpPost("create")] [ReturnUnauthorizedJson(Message = "创建订单需要管理员权限", ErrorCode = 40102)] public IActionResult CreateOrder() { return Ok(); } }
方案优势
- 完全不影响原有页面的Cookie跳转逻辑、原生Basic认证逻辑
- 控制器/动作粒度的控制,支持自定义错误信息和业务错误码
- 不需要新增混合认证方案,原有认证校验逻辑无需修改
- 性能损耗极低,仅多一次特性判断开销
内容的提问来源于stack exchange,提问作者Dunge
相关产品推荐
相关产品推荐

