今天在为项目编写API统一返回结果的代码时,发现不能通过Filter来定义授权失败后的响应结果,于是我翻看了一下官方文档和aspnetcore源码,原来需要自定义实现IAuthorizationMiddlewareResultHandler接口。
Asp.Net Core 5自带的验权中间件,在验权失败后,是直接返回一个401。这对于前端来说不太友好,所以我的需求是改为返回200的自定义结果。
我通过搜索引擎查阅了一下别人实现的代码,发现都比较复杂,我这里需求比较简单,就不做太复杂的判断。
环境:ASP.NET Core 5。
一、首先定义AuthorizationMiddlewareResultHandler类,并且实现IAuthorizationMiddlewareResultHandler接口。
1 using Microsoft.AspNetCore.Authorization; 2 using Microsoft.AspNetCore.Authorization.Policy; 3 using Microsoft.AspNetCore.Http; 4 using System.Threading.Tasks; 5 6 namespace DotNet.AspNetCore.WebApi 7 { 8 public class AuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler 9 { 10 public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) 11 { 12 //因为管道还没有走到Action 所以没有ActionResult使用 我们必须自己定义Response中的内容 13 //这里授权是否成功 14 if (!authorizeResult.Succeeded) 15 { 16 //将状态码定义为200 17 context.Response.StatusCode = 200; 18 //使用 WriteAsJsonAsync 写入一个自定义的返回对象 自动完成Json的序列化操作 19 //我这里用匿名类演示 实际项目中请替换成对应的返回对象 自定义状态码和提示信息 20 await context.Response.WriteAsJsonAsync(new { Code = 401, Message = "没有权限", Result = string.Empty }); 21 //注意一定要return 在这里短路管道 不要走到next 否则线程会进入后续管道 到达action中 22 return; 23 } 24 //如果授权成功 继续执行后续的中间件 记住一定记得next 否则会管道会短路 25 await next(context); 26 } 27 } 28 }
二、在Startup.cs中注入单例服务,将其作为IAuthorizationMiddlewareResultHandler的实现。
1 public void ConfigureServices(IServiceCollection services) 2 {
3 // IAuthorizationMiddlewareResultHandler 用来替换框架默认的授权返回结果 4 services.AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>();
5 }
代码非常简单,很容易就自定义了授权返回结果。但是,到这里就结束了吗?不不不,好奇的我想知道为什么要这样写,于是我去看了一下源码。
在查看了app.UseAuthentication()对应的AuthorizationMiddleware中间件后,发现在Invoke的最后有这么一段代码:
var authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticateResult!, context, resource); var authorizationMiddlewareResultHandler = context.RequestServices.GetRequiredService<IAuthorizationMiddlewareResultHandler>(); await authorizationMiddlewareResultHandler.HandleAsync(_next, context, policy, authorizeResult);
context.RequestServices.GetRequiredService<IAuthorizationMiddlewareResultHandler>()这段代码表明,AuthorizationMiddleware中间件是通过获取服务容器中IAuthorizationMiddlewareResultHandler的实例,来处理授权返回结果。而我们上面的AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>()则是替换掉了框架中原有的默认实现,从而达到了我们想要的返回结果。
啧啧啧,又学会了一招如何巧妙的设计中间件。
这里我把框架原有的实现也贴出来:
1 public class AuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler 2 { 3 /// <inheritdoc /> 4 public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) 5 { 6 if (authorizeResult.Challenged) 7 { 8 if (policy.AuthenticationSchemes.Count > 0) 9 { 10 foreach (var scheme in policy.AuthenticationSchemes) 11 { 12 await context.ChallengeAsync(scheme); 13 } 14 } 15 else 16 { 17 await context.ChallengeAsync(); 18 } 19 20 return; 21 } 22 else if (authorizeResult.Forbidden) 23 { 24 if (policy.AuthenticationSchemes.Count > 0) 25 { 26 foreach (var scheme in policy.AuthenticationSchemes) 27 { 28 await context.ForbidAsync(scheme); 29 } 30 } 31 else 32 { 33 await context.ForbidAsync(); 34 } 35 36 return; 37 } 38 39 await next(context); 40 } 41 }
看来还是要经常回顾一下以前的知识,持之以恒,夯实基础。