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
的關系;多個 Authorize 是 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
靜態方法同樣不是一種好的設計。等微軟想通吧。
參考資料
https://docs.microsoft.com/zh-cn/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-2.1
排版問題:http://blog.tubumu.com/2018/11/28/aspnetcore-mvc-extend-authorization/
ASP.NET Core MVC 授權的擴展:自定義 Authorize Attribute 和 IApplicationModelProvide