前面我們已經弄好了使用者角色這塊內容,接下來就是我們的授權策略。在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
歡迎進群催更。