今天在為項目編寫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 //身份驗證是否通過 21 if (!context.User.Identity.IsAuthenticated) 22 await context.Response.WriteAsJsonAsync(new { Code = 401, Message = "身份驗證不通過", Result = string.Empty }); 23 else 24 await context.Response.WriteAsJsonAsync(new { Code = 403, Message = "沒有權限", Result = string.Empty }); 25 //注意一定要return 在這里短路管道 不要走到next 否則線程會進入后續管道 到達action中 26 return; 27 } 28 //如果授權成功 繼續執行后續的中間件 記住一定記得next 否則會管道會短路 29 await next(context); 30 } 31 } 32 }
二、在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 }
看來還是要經常回顧一下以前的知識,持之以恆,夯實基礎。