ASP.NET Core MVC 授權的擴充套件:自定義 Authorize Attribute 和 IApplicationModelProvide
一、概述
ASP.NET Core MVC
提供了基於角色( Role
)、宣告( Chaim
) 和策略 ( Policy
) 等的授權方式。在實際應用中,可能採用部門( Department
, 本文采用使用者組 Group
)、職位 ( 可繼續沿用 Role
)、許可權( Permission
)的方式進行授權。要達到這個目的,僅僅通過自定義 IAuthorizationPolicyProvider
是不行的。本文通過自定義 IApplicationModelProvide
二、PermissionAuthorizeAttribute : IPermissionAuthorizeData
AuthorizeAttribute
類實現了 IAuthorizeData
介面:
1 |
namespace Microsoft.AspNetCore.Authorization |
使用 AuthorizeAttribute 不外乎如下幾種形式:
1 |
[Authorize] |
當然,引數還可以組合起來。另外,Roles 和 AuthenticationSchemes 的值以半形逗號分隔,是 Or
And
的關係;Policy 、Roles 和 AuthenticationSchemes 如果同時使用,也是 And
的關係。
如果要擴充套件 AuthorizeAttribute,先擴充套件 IAuthorizeData 增加新的屬性:
1 |
public interface IPermissionAuthorizeData : IAuthorizeData |
然後定義 AuthorizeAttribute:
1 |
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] |
現在,在 Controller 或 Action 上就可以這樣使用了:
1 |
[PermissionAuthorize(Roles = "經理,副經理")] // 經理或部門經理 |
資料已經準備好,下一步就是怎麼提取出來。通過擴充套件 AuthorizationApplicationModelProvider 來實現。
三、PermissionAuthorizationApplicationModelProvider : IApplicationModelProvider
AuthorizationApplicationModelProvider
類的作用是構造 AuthorizeFilter
物件放入 ControllerModel
或 ActionModel
的 Filters
屬性中。具體過程是先提取 Controller 和 Action 實現了 IAuthorizeData
介面的 Attribute,如果使用的是預設的DefaultAuthorizationPolicyProvider
,則會先建立一個 AuthorizationPolicy
物件作為 AuthorizeFilter
建構函式的引數。
建立 AuthorizationPolicy
物件是由 AuthorizationPolicy
的靜態方法 public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData)
來完成的。該靜態方法會解析 IAuthorizeData
的資料,但不懂解析 IPermissionAuthorizeData
。
因為 AuthorizationApplicationModelProvider
類對 AuthorizationPolicy.CombineAsync
靜態方法有依賴,這裡不得不做一個類似的 PermissionAuthorizationApplicationModelProvider
類,在本類實現 CombineAsync
方法。暫且不論該方法放在本類是否合適的問題。
1 |
public static AuthorizeFilter GetFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authData) |
if(authorizeDatum is IPermissionAuthorizeData permissionAuthorizeDatum )
為擴充套件部分。
四、Startup
註冊 PermissionAuthorizationApplicationModelProvider
服務,需要在 AddMvc
之後替換掉 AuthorizationApplicationModelProvider
服務。
1 |
services.AddMvc(); |
五、Jwt 示例
1 |
[Route("api/[controller]")] |
六、問題
AuthorizeFilter
類顯示實現了 IFilterFactory
介面的 CreateInstance
方法:
1 |
IFilterMetadata IFilterFactory.CreateInstance(IServiceProvider serviceProvider) |
竟然對 AuthorizationApplicationModelProvider.GetFilter
靜態方法產生了依賴。慶幸的是,如果通過 AuthorizeFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData)
或 AuthorizeFilter(AuthorizationPolicy policy)
建立 AuthorizeFilter
物件不會產生什麼不良影響。
七、下一步
[PermissionAuthorize(Groups = "研發部,生產部", Roles = "經理", Permissions = "請假審批"]
這種形式還是不夠靈活,哪怕用多個 Attribute, And
和 Or
的邏輯組合不一定能滿足需求。可以在 IPermissionAuthorizeData
新增一個 Rule
屬性,實現類似的效果:
1 |
[PermissionAuthorize(Rule = "(Groups:研發部,生產部)&&(Roles:請假審批||Permissions:超級許可權)"] |
通過 Rule
計算複雜的授權。
八、如果通過自定義 IAuthorizationPolicyProvider 實現?
另一種方式是自定義 IAuthorizationPolicyProvider
,不過還需要自定義 AuthorizeFilter
。因為當不是使用 DefaultAuthorizationPolicyProvider
而是自定義 IAuthorizationPolicyProvider
時,AuthorizationApplicationModelProvider
(或前文定義的 PermissionAuthorizationApplicationModelProvider
)會使用 AuthorizeFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData)
建立 AuthorizeFilter
物件,而不是 AuthorizeFilter(AuthorizationPolicy policy)
。這會造成 AuthorizeFilter
物件在 OnAuthorizationAsync
時會間接呼叫 AuthorizationPolicy.CombineAsync
靜態方法。
這可以說是一個設計上的缺陷,不應該讓 AuthorizationPolicy.CombineAsync
靜態方法存在,哪怕提供個 IAuthorizationPolicyCombiner
也好。另外,上文提到的 AuthorizationApplicationModelProvider.GetFilter
靜態方法同樣不是一種好的設計。等微軟想通吧。
參考資料
排版問題:http://blog.tubumu.com/2018/11/28/aspnetcore-mvc-extend-authorization/