4

造轮子之自定义授权策略 - 饭勺oO

 11 months ago
source link: https://www.cnblogs.com/fanshaoO/p/17752194.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前面我们已经弄好了用户角色这块内容,接下来就是我们的授权策略。在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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK