造輪子之自定義授權策略

前面我們已經弄好了用戶角色這塊內容,接下來就是我們的授權策略。在asp.net core中提供了自定義的授權策略方案,我們可以按照需求自定義我們的權限過濾。
這裏我的想法是,不需要在每個Controller或者Action打上AuthorizeAttribute,自動根據ControllerName和ActionName匹配授權。只需要在Controller基類打上一個AuthorizeAttribute,其他Controller除了需要匿名訪問的,使用統一的ControllerName和ActionName匹配授權方案。
話不多說,開整。

IPermissionChecker

首先我們需要一個PermissionChecker來作爲檢查當前操作是否有權限。很簡單,只需要傳入ControllerName和ActionName。至於實現,後續再寫。

namespace Wheel.Authorization
{
    public interface IPermissionChecker
    {
        Task<bool> Check(string controller, string action);
    }
}

PermissionAuthorizationHandler

接下來我們則需要實現一個PermissionAuthorizationHandler和PermissionAuthorizationRequirement,繼承AuthorizationHandler抽象泛型類。

using Microsoft.AspNetCore.Authorization;

namespace Wheel.Authorization
{
    public class PermissionAuthorizationRequirement : IAuthorizationRequirement
    {
        public PermissionAuthorizationRequirement()
        {
        }

    }
}
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Controllers;
using Wheel.DependencyInjection;

namespace Wheel.Authorization
{
    public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionAuthorizationRequirement>, ITransientDependency
    {
        private readonly IPermissionChecker _permissionChecker;

        public PermissionAuthorizationHandler(IPermissionChecker permissionChecker)
        {
            _permissionChecker = permissionChecker;
        }

        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement)
        {
            if (context.Resource is HttpContext httpContext)
            {
                var actionDescriptor = httpContext.GetEndpoint()?.Metadata.GetMetadata<ControllerActionDescriptor>();
                var controllerName = actionDescriptor?.ControllerName;
                var actionName = actionDescriptor?.ActionName;
                if (await _permissionChecker.Check(controllerName, actionName))
                {
                    context.Succeed(requirement);
                }
            }
        }
    }
}

在PermissionAuthorizationHandler中注入IPermissionChecker。
然後通過重寫HandleRequirementAsync進行授權策略的校驗。
這裏使用HttpContext獲取請求的ControllerName和ActionName,再使用IPermissionChecker進行檢查,如果通過則放行,不通過則自動走AspNetCore的其他AuthorizationHandler流程,不需要調用context.Fail方法。

PermissionAuthorizationPolicyProvider

這裏除了AuthorizationHandler,還需要實現一個PermissionAuthorizationPolicyProvider,用於在匹配到我們自定義Permission的時候,就使用PermissionAuthorizationHandler做授權校驗,否則不會生效。

using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Wheel.DependencyInjection;

namespace Wheel.Authorization
{
    public class PermissionAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider, ITransientDependency
    {
        public PermissionAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options) : base(options)
        {
        }
        public override async Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
        {
            var policy = await base.GetPolicyAsync(policyName);
            if (policy != null)
            {
                return policy;
            }
            if (policyName == "Permission")
            {
                var policyBuilder = new AuthorizationPolicyBuilder(Array.Empty<string>());
                policyBuilder.AddRequirements(new PermissionAuthorizationRequirement());
                return policyBuilder.Build();
            }
            return null;
        }
    }
}

很簡單,只需要匹配到policyName == "Permission"時,添加一個PermissionAuthorizationRequirement即可。

PermissionChecker

接下來我們來實現IPermissionChecker的接口。

namespace Wheel.Permission
{
    public class PermissionChecker : IPermissionChecker, ITransientDependency
    {
        private readonly ICurrentUser _currentUser;
        private readonly IDistributedCache _distributedCache;

        public PermissionChecker(ICurrentUser currentUser, IDistributedCache distributedCache)
        {
            _currentUser = currentUser;
            _distributedCache = distributedCache;
        }

        public async Task<bool> Check(string controller, string action)
        {
            if (_currentUser.IsInRoles("admin"))
                return true;
            foreach (var role in _currentUser.Roles)
            {
                var permissions = await _distributedCache.GetAsync<List<string>>($"Permission:R:{role}");
                if (permissions is null)
                    continue;
                if (permissions.Any(a => a == $"{controller}:{action}"))
                    return true;
            }
            return false;
        }
    }
}

通過當前請求用戶ICurrentUser以及分佈式緩存IDistributedCache做權限判斷,避免頻繁查詢數據庫。
這裏ICurrentUser如何實現後續文章再寫。
很簡單,先判斷用戶角色是否是admin,如果是admin角色則默認所有權限放行。否則根據緩存中的角色權限進行判斷。如果通過則放行,否則拒絕訪問。

創建抽象Controller基類

創建WheelControllerBase抽象基類,添加[Authorize("Permission")]的特性頭部,約定其餘所有Controller都繼承這個控制器。

    [Authorize("Permission")]
    public abstract class WheelControllerBase : ControllerBase
    {
        
    }

接下來我們測試一個需要權限的API。
image.png
image.png
image.png
image.png
通過DEBUG可以看到我們正常走了校驗並響應401。

就這樣我們完成了我們自定義的授權策略配置。

輪子倉庫地址https://github.com/Wheel-Framework/Wheel
歡迎進羣催更。

image.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章