ASP.NET Core自定义策略授权失败的异常处理与需求信息获取
我按照微软文档实现了自定义策略授权,代码如下:
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeRequirement requirement) { context.Fail(); //<-- 简化写法 return Task.CompletedTask; } }
当授权未调用context.Succeed(requirement)或显式执行context.Fail()时,会抛出InvalidOperationException异常,信息为:InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found。
我希望捕获包含MinimumAgeRequirement信息的异常,以显示类似“您的年龄不符合要求”的自定义错误,请问捕获异常时能否获取该需求信息?
嘿,这个问题我之前也碰到过,咱们一步步来拆解解决:
首先,为什么会抛出这个异常?
当授权失败(不管是显式context.Fail()还是没调用context.Succeed()),ASP.NET Core的授权中间件会尝试发起**挑战(Challenge)**流程,但如果你的项目里没配置默认的认证方案(比如Cookie、JWT),就会抛出这个InvalidOperationException。而且这个异常本身并不包含你要的MinimumAgeRequirement信息——因为它是挑战流程失败的错误,不是授权需求失败的直接错误。
那怎么获取到MinimumAgeRequirement的信息并返回自定义错误?
有两种靠谱的方式,推荐第二种更符合框架设计的方案:
方式一:自定义授权异常,在全局异常处理中捕获
你可以在授权处理器里抛出一个包含需求信息的自定义异常,然后在全局异常过滤器/中间件里捕获它,再返回自定义错误。
- 先定义一个带需求信息的自定义异常:
public class AuthorizationRequirementFailedException : Exception { public IAuthorizationRequirement FailedRequirement { get; } public AuthorizationRequirementFailedException(string message, IAuthorizationRequirement requirement) : base(message) { FailedRequirement = requirement; } }
- 修改你的授权处理器,抛出这个异常(替代直接
context.Fail()):
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeRequirement requirement) { // 这里可以先加入实际的年龄判断逻辑,比如从context.User.Claims里取生日计算年龄 // 假设年龄不符合要求,抛出自定义异常 throw new AuthorizationRequirementFailedException("年龄不符合要求", requirement); } }
- 在全局异常中间件里捕获并处理:
// 在Program.cs里添加异常处理中间件 app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { var exceptionFeature = context.Features.Get<IExceptionHandlerPathFeature>(); var exception = exceptionFeature?.Error; if (exception is AuthorizationRequirementFailedException authEx) { context.Response.StatusCode = StatusCodes.Status403Forbidden; context.Response.ContentType = "application/json"; string errorMessage; if (authEx.FailedRequirement is MinimumAgeRequirement) { errorMessage = "您的年龄不符合要求"; } else { errorMessage = authEx.Message; } await context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(new { error = errorMessage })); } else { // 处理其他类型的异常 context.Response.StatusCode = StatusCodes.Status500InternalServerError; await context.Response.WriteAsync("服务器内部错误"); } }); });
方式二:自定义授权结果处理器(推荐)
ASP.NET Core 3.0+提供了IAuthorizationMiddlewareResultHandler接口,这是官方推荐的扩展授权结果处理的方式,不需要抛出异常,更优雅。
- 实现自定义的结果处理器:
public class CustomAuthorizationResultHandler : IAuthorizationMiddlewareResultHandler { // 保留默认处理器处理其他情况 private readonly AuthorizationMiddlewareResultHandler _defaultHandler = new(); public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) { // 检查授权失败的情况 if (!authorizeResult.Succeeded) { // 获取所有失败的授权需求 var failedRequirements = authorizeResult.Failure?.FailedRequirements; if (failedRequirements != null && failedRequirements.Any(r => r is MinimumAgeRequirement)) { // 返回自定义的403错误 context.Response.StatusCode = StatusCodes.Status403Forbidden; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(new { error = "您的年龄不符合要求" })); return; } } // 其他授权结果(成功、其他失败场景)交给默认处理器处理 await _defaultHandler.HandleAsync(next, context, policy, authorizeResult); } }
- 在Program.cs里注册这个处理器:
builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, CustomAuthorizationResultHandler>();
最后说下两种方式的区别
- 方式一通过抛出异常处理,适合简单场景,但异常会中断请求流程,可能影响性能(毕竟异常处理开销不小)。
- 方式二是官方设计的扩展点,更灵活,能处理所有授权结果场景,不会抛出不必要的异常,是生产环境的推荐方案。
内容的提问来源于stack exchange,提问作者gsharp

