前面我們已經弄好了用戶角色這塊內容,接下來就是我們的授權策略。在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。
通過DEBUG可以看到我們正常走了校驗並響應401。
就這樣我們完成了我們自定義的授權策略配置。
輪子倉庫地址https://github.com/Wheel-Framework/Wheel
歡迎進羣催更。